Quick Tip #21: The rats in the machine

Let’s think about numbers. I’ve been inserting Kickstarter messages into my Quick Tips, but this post is about Kickstarter. Or, about their mistreatment of numbers. There’s no scandal, just those little inconsistencies that the experienced programmer notices because they have made the same mistakes. Perl 6 has features to make this easier.

First, here’s the current state of my Kickstarter campaign for the Learning Perl 6 book. The first image is from the daily email they send me and the second is the dashboard on the website.

The daily email report

The website dashboard

The metrics aren’t from the same instant, as you see from looking at the number of backers and the funding level. But, notice that the higher amount and higher backers has the lower percentage. Most of you probably immediately recognize this as two different strategies in turning Real numbers in Integers.

Funding Goal Decimal Reported Method
36,254 37,000 0.97983783783 97 Truncating
36,088 37,000 0.97535135135 98 Rounding

These differences seem small, but for the person running the campaign they can provide a moment of panic. If I see the higher percentage first then see the lower percentage, I wonder if people cancelled or adjusted their pledge. This is especially troubling when you get close to the end of a campaign because there are bad actors out there you like to give you that last little bit then dispute the charge later (and Kickstarter then charges my credit card to pay them back). And, there are plenty of Kickstarter spammers who back your project hoping you’ll back theirs, then cancel their pledge when you don’t. I know, weird.

Your pledge is just that. No money changes hands until the pledge total goes over the minimum funding. I haven’t captured any of that money, and I only expect to get about 90% of it through various credit card issues once Kickstarter collects in about two weeks.

But, back to programming. When you see two different ways of getting the same number, you know you have a code smell. We know that there should be a common routine that handles it. It seems simple to divide two numbers, but obviously it isn’t. There are other things, like normalization, that come into play.

Let’s divide some numbers in Perl 6. First, there’s the division operator and it might look like it divides two numbers. If I look at the type of thingy in $n, I see that it’s a Rat (rational number). If I look at the .perl representation, I see a fraction (in this case reduced):

$ perl6
To exit type 'exit' or '^D'
> my $n = 36088 / 37000
0.975351
> $n.WHAT
(Rat)
> $n.perl
<4511/4625>

That is, Perl 6 delays my decision to represent this number because it knows where I started. It doesn’t lose bits or accuracy because it allows a series of inexact operations. Perl 6 knows the numerator and denominator still.

I can represent the number as a whole number percentage in two ways. Perl 6’s Real class has methods to truncate and round:

> ($n * 100).truncate
97
> ($n * 100).round
98

But, I don’t want to make that decision everywhere in the code. If I were doing this, I would recognize the logical task. I want to pass in the numbers and get the same thing out every time. I can define a subroutine to do that:

> sub show_funding_percentage ( Rat $funding --> Int ) {
* ($funding * 100).round }
sub show_funding_percentage (Rat $funding --> Int) { #`(Sub+{Callable[Int]}|140349924897624) ... }
> show_funding_percentage( $n )
98

The trick is making your programmers use the proper interface rather than thinking they know better because division is simple. Showing the funding percentage is a logical task. Other things might need to happen. I shouldn’t have to think about all the steps to get there every time I want to use it.

That’s within one language though. There’s another issue that could be at play. Different languages or libraries might be in the path to the website and the path to email. They get the same inputs but they produce different outputs. There are different rounding strategies; it’s not the simple rules you learned in grade school. In this case I don’t think that’s the problem because most strategies round these two numbers the same way.

As a final note, consider what else rational numbers give us. We’re mostly used to inexact numbers due to the limitations of floating point numbers. With rationals, which are two integers, that isn’t a problem:

$ perl5 -e 'print +(0.1+0.1+0.1)==0.3 ? "True" : "False"'
False
$ perl6 -e 'say (0.1+0.1+0.1)==0.3'
True

Or, I should say, it’s not a problem until I make it one. I can turn a rational number into a string ($s) and turn that string back into a number ($t). I’ve now lost some precision. When I multiply by what I think is the denominator, I don’t get back the right numerator:

> my $s = ~$n
0.975351
> $s.WHAT
(Str)
> my $t = +$s;
0.975351
> $t.WHAT
(Rat)
> $t.perl
0.975351
> $t*37000
36087.987

And, we know what that small round errors lead to bigger problems.

Faulty rounding leads to nuclear war

4 comments

  1. I wonder if they chose the two roundings on purpose. For the Backers you get the rounded value, generally higher which makes the Kickstarter look closer to funded or more funded. I’m more likely to back something if it’s close to funded and that 1% might make an emotional difference.

    Whereas for the Creator a low percentage gives them a drive to advertise more and get more people in.

    Or they just had two different people code those two bits 🙂

  2. It probably makes sense to use `floor` instead of `round`, actually.
    You wouldn’t want it to show “100% funded” when you’re at $36818.

    1. I think if I were to implement this, I’d have different methods depending on where you where. For the last 2%, drop down to a single decimal place until the point where you show 99.9% until it is equal to or above the goal. But then, that’s my point. A central subroutine decides all that instead of the intern who has to send out the mail.

      But otherwise, yeah, I wouldn’t round up I don’t think.

Leave a Reply to Simon Proctor Cancel reply

Your email address will not be published. Required fields are marked *