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 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.
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 🙂
I don’t think the half a percent would matter that much to backers, but I hadn’t considered that.
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.
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.