You're experiencing a problem common to any programming language that relies on the underlying storage to represent numbers. n short, computers cannot store every number exactly. You can find discussions of this stuff at various places. Although it's common, it's also rare that it matters. However, you've found one of those rare instances, and a pretty insidious instance at that.
In your example, you think that you have 200 * 1.15, which you think is 230 exactly, but your computer can't represent 1.15 exactly, but you really have 229.99999999999997158 (most likely). Then, when you take 229.99999999999997158 % 5, Perl converts that 229.99999999999997158 to its integer form (as Sarah almost gets right), which is 229. Taking the integer form just chops off the fractional part instead of rounding. So, the result of 229 % 5 is 4.
I rewrote your example to better show what's happening:
#!/usr/bin/perl
use warnings;
use strict;
my $float = 200 * 1.15;
my $int = 230;
my $float_result = $float % 5; # 4 - WTF?
my $int_result = $int % 5; # 0
print "int_result is [$int_result]\n";
print "float_result is [$float_result]\n";
When I run this I get different results for what I think should be the same thing:
int_result is [0]
float_result is [4]
When I run into these sorts of problems, I want to see what perl thinks I'm doing. I can compile and immediately decompile my program with B::Deparse (see
Use B::Deparse to see what perl thinks the code is. in the
Effective Perler blog):
$ perl -MO=Deparse test
use warnings;
use strict 'refs';
my $float = 229.99999999999997158;
my $int = 230;
my $float_result = $float % 5;
my $int_result = $int % 5;
print "int_result is [$int_result]\n";
print "float_result is [$float_result]\n";
Now I can see that when perl compiled and constant-folded 200*1.15, it ended up with an inexact result.
The documentation mentions using the
integer pragma bypasses the perl modulo operator, but it also chops off the fractional portions of the numbers. 200 * 1.15 then turns into 200 * 1 because only integers are allowed. I'll get the answer I expect, 0, but for the wrong reason because I am using the wrong number, 200. I can't use it lexically because it still turns 229.99999999999997158 into 229 and you'll still get 4.
However, there is another pragma that can help. The
bignum pragma treats numbers of objects instead of using perl's architecture-dependent storage (see "Item 14: Handle big numbers with bignum" in my book
Effective Perl Programming, 2nd Edition):
#!/usr/bin/perl
use warnings;
use strict;
use bignum;
my $float = 200 * 1.15;
my $int = 230;
my $float_result = $float % 5; # 4 - WTF?
my $int_result = $int % 5; # 0
print "int_result is [$int_result]\n";
print "float_result is [$float_result]\n";
bignum is going to slow down my program since numbers are now objects with all of that overhead. The more numbery sorts of things you do, the more it will affect my program. But, slow and right is better than fast and wrong every time.
Part of your problem debugging this is the way Perl represents the number to you. You printed the number to see what you had (a first rate debugging technique), and it looks like it's exactly 230, but it's not. Perl's trying to show you the "human number" rather than the stored number because it's trying to correct for imprecision so you don't notice. In this case, that's not very helpful.
By default, the
print operator and numbers is really just the
%g
format from
sprintf. That's the format that tries to be maximally "do what I mean". When I look at the
%f
format, I still don't see the difference. I have to expand that out to specify the number of digits I'll tolerate after the decimal point. Since the difference is in the 14th decimal place, which is pretty insignificant (yet enough to mess up the modulo operator):
$ perl -le 'printf "%g\n", 200 * 1.15'
230
$ perl -e 'printf "%g\n", 200 * 1.15'
230
$ perl -e 'printf "%f\n", 200 * 1.15'
230.000000
$ perl -e 'printf "%.17f\n", 200 * 1.15'
229.99999999999997158
$ perl -e 'printf "%.17f\n", 230'
230.00000000000000000