Jump to content

Why are these results different in Perl?

jackliu's Photo
Posted Jun 24 2010 06:49 AM
4484 Views

Why are these different?
THANKS:


#!/usr/bin/perl
$x = "230";
#$x = $x * 200;
print "$x \n";
$size_T1 = $x;
print "$size_T1 \n";
$size_T1 = $size_T1 % 5;
print "$size_T1 \n";


display value "0"

#!/usr/bin/perl
$x = "1.15";
$x = $x * 200;
print "$x \n";
$size_T1 = $x;
print "$size_T1 \n";
$size_T1 = $size_T1 % 5;
print "$size_T1 \n";


display value should be "0", but it's "4". WHY?

jackliu

Tags:
4 Subscribe


4 Replies

-1
  msilver's Photo
Posted Jun 24 2010 02:56 PM

In your first block of code, the 3rd line from the top is commented out:

Quote

#$x = $x * 200;



In your second block of code, the 3rd line from the top is not commented out:

Quote

$x = $x * 200;


Might this be the hangup? I do not know anything really about programming, but that jumped out at me. I also don't know how that modulo operation is supposed to behave in the 2nd line from the bottom in both blocks of code, so maybe that's part of it?

Also now I feel like I just answered your homework for you :)
+ 2
  sarahkim's Photo
Posted Jun 24 2010 03:20 PM

I suspect the reason the results are different is that the multiplication with 1.15 makes the resulting value a non-integer.

This section of the perldoc says "Given integer operands $a and $b":

Quote

Binary "%" is the modulo operator, which computes the division remainder of its first argument with respect to its second argument. Given integer operands $a and $b : If $b is positive, then $a % $b is $a minus the largest multiple of $b less than or equal to $a . If $b is negative, then $a % $b is $a minus the smallest multiple of $b that is not less than $a (i.e. the result will be less than or equal to zero). If the operands $a and $b are floating point values and the absolute value of $b (that is abs($B)) is less than (UV_MAX + 1) , only the integer portion of $a and $b will be used in the operation (Note: here UV_MAX means the maximum of the unsigned integer type). If the absolute value of the right operand (abs($B)) is greater than or equal to (UV_MAX + 1) , "%" computes the floating-point remainder $r in the equation ($r = $a - $i*$B) where $i is a certain integer that makes $r have the same sign as the right operand $b (not as the left operand $a like C function fmod() ) and the absolute value less than that of $b . Note that when use integer is in scope, "%" gives you direct access to the modulo operator as implemented by your C compiler. This operator is not as well defined for negative operands, but it will execute faster.


Try changing:
$x = $x * 200;


to:
$x = int($x * 200);

+ 3
  brian_d_foy's Photo
Posted Jul 08 2010 05:48 PM

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

0
  monkeyvegas's Photo
Posted Jul 09 2010 09:08 AM

An alternative to using bignum or int per brian_d_foy's answer is to use Number::Fraction and use fractions instead of floating point numbers in your calculations, e.g. (based on brian_d_foy's code):

#!/usr/bin/perl
use warnings;
use strict;
use Number::Fraction ':constants';

# the string '115/100' (1.15) triggers the use of Number::Fraction's
# overloading of constants (could also be '23/20')
my $float = 200 * '115/100'; 
my $int   = 230;

my $float_result = $float % 5;   # no longer 4, now 0
my $int_result   = $int % 5;     # 0

print "int_result is [$int_result]\n";
print "float_result is [$float_result]\n";


Which produces the expected:
int_result is [0]
float_result is [0]


Or course rewriting $float to be 200 * 115 / 100 would also work, and not require any additional modules.