rduke15 has asked for the wisdom of the Perl Monks concerning the following question:

Just bumped into this problem.

Some calculations seem to make a number become a "hidden" real number instead of an integer. The number prints as an integer, but after a substraction with another integer, the result is a real.

#!/usr/bin/perl

use strict; use warnings;

my ($x1, $x2) = (256080, 258160);
my $diff      = $x2 - $x1;

print "x1=$x1, x2=$x2, diff=$diff\n"; # OK

$x2 = 1000*240 + 1000*18 + 1000*( 4/25 ); 
$diff = $x2 - $x1;
print "x1=$x1, x2=$x2, diff=$diff\n"; # Also OK

$x2 = 1000 * ( 4*60 + 18 + 4/25 );
$diff = $x2 - $x1;
print "x1=$x1, x2=$x2, diff=$diff\n"; # $x2 is integer, but $diff is real!

The output is:

x1=256080, x2=258160, diff=2080
x1=256080, x2=258160, diff=2080
x1=256080, x2=258160, diff=2080.00000000003

Or the short one-liner version: perl -e '$x1=256080; $x2 = 1000 * ( 4*60 + 18 + 4/25 ); $diff=$x2-$x1; print "x1=$x1, x2=$x2, diff=$diff, Perl=$^V\n";'

I tried this on Perl versions v5.14.2, v5.18.2 and v5.20.2 with the same result. It is disconcerting that $x2 prints as "258160", but becomes something else when used in a substraction.

Replies are listed 'Best First'.
Re: Integers sometimes turn into Reals after substraction
by LanX (Archbishop) on May 14, 2016 at 11:03 UTC
    This misunderstanding is common to many languages not only Perl, because people expect a decimal calculation behind decimal representation.

    But 4/25 isn't a number which can be accurately represented in a binary system and rounding errors come into play.

    See also Humans have too many fingers

    update

    To elaborate further

    (4*1000)/25 is an integer but 1000*(4/25) is a float with a very tiny error in the last bits of the mantissa.

    Perl will still print this float like an integer because the error is too small.

    But after the subtraction the leading zeros in the mantissa will cause a left shift (with adjustment of the exponent).

    This means the error shifts out of Perl's tolerance margin and is printed as (decimal) fraction.

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

      Well, I understand that. The problem is that it is inconsistent. The result depends not only on the type of calculation which led to $x2:
      perl -e '$x1=256080; $x2 = 1000 * ( 4*60 + 18 + 4/25 ); $diff=$x2-$x1; print "x1=$x1, x2=$x2, diff=$diff\n";'
      x1=256080, x2=258160, diff=2080.00000000003
      perl -e '$x1=256080; $x2 = 1000 * ( 4*60 + 18 ) + 1000*(4/25); $diff=$x2-$x1; print "x1=$x1, x2=$x2, diff=$diff\n";'
      x1=256080, x2=258160, diff=2080

      but also on the value of $x1 which has never been calculated.

      perl -e '$x1=25608; $x2 = 1000 * ( 4*60 + 18 + 4/25 ); $diff=$x2-$x1; print "x1=$x1, x2=$x2, diff=$diff\n";'
      x1=25608, x2=258160, diff=232552
        Have you seen the update in my reply?

        Floats are automatically adjusted to avoid leading zeros in the mantissa.

        update

        Think float, this

        x1 = 256080 x2 = 258160 diff = 2080.00000000003

        really just means (in slightly inaccurate decimal interpretation)

        x1 = 2.56080 e5 x2 = 2.5816000000000003 e5 (error ignored when printed) diff = 2.08000000000003 e3 (error visible when printed)

        As you can see the error (here decimal 3) is shifted to the left and out of error margin into printed "visibility".

        update

        after firing up my laptop, here a proof of concept

        DB<115> $x1 = 2.56080e5 => 256080 DB<116> $x2 = 2.5816000000000003e5 => 258160 # within error margin, ignored in normal di +splay DB<117> printf '%.11f', $x2 258160.00000000003 # forced into visibility, hence $x2 NOT an +integer DB<118> $x2-$x1 => 2080.00000000003 # error can't be ignored any more

        Cheers Rolf
        (addicted to the Perl Programming Language and ☆☆☆☆ :)
        Je suis Charlie!

Re: Integers sometimes turn into Reals after substraction
by ww (Archbishop) on May 14, 2016 at 11:45 UTC
Re: Integers sometimes turn into Reals after substraction
by syphilis (Bishop) on May 14, 2016 at 14:03 UTC
    It is disconcerting that $x2 prints as "258160", but becomes something else when used in a subtraction

    I totally agree with you.
    The internal hex representation of the double 258160.0 is 410f838000000000, but the internal hex representation of $x2 is 410f838000000001 which should really be printed as 2.5816000000000003e5.
    Unfortunately, perl limits the printed output of floats to 15 decimal digits - and, as you can see, if you round $x2 to 15 decimal digits of precision you get 258160.0.

    There has been some talk about fixing this in perl-5.26.

    Cheers,
    Rob
Re: Integers sometimes turn into Reals after substraction
by ikegami (Pope) on May 14, 2016 at 15:23 UTC

    First of all, 4/25 is periodic in binary (just like 1/3 is periodic in decimal).

    ____________________ 4/25 = 0.00101000111101011100 base 2

    This means it can't be stored exactly in a floating point number.

    Sometimes, floating point number errors get canceled out or reduced to nothing. It turns out that this happens for one of the chains of operations but not the other.

    That's why you want to avoid checking if two floating point numbers are equal without allowing for an error margin. Replace

    $n1 == $n2

    with

    abs($n1 - $n2) < $epsilon
      Well Perl's error correction (or that of the underlying C lib) is better than it's reputation
      DB<186> $x1= 1000*(4/25) => 160 DB<187> $x2= 4000/25 => 160 DB<188> $x1 == $x2 => 1

      What's puzzling me is this behaviour:

      DB<205> for (254..263) {$x = 1000 * ( $_ + 4/25 ); printf "%.20f\n" +, $x} 254160.00000000000000000000 255160.00000000000000000000 256160.00000000002910383046 257160.00000000002910383046 258160.00000000002910383046 259160.00000000002910383046 260160.00000000002910383046 261160.00000000002910383046 262160.00000000000000000000 263160.00000000000000000000

      Cheers Rolf
      (addicted to the Perl Programming Language and ☆☆☆☆ :)
      Je suis Charlie!

      update

      interestingly this only seems to happen near some powers of 2

      DB<220> for (0..20) {$e=2**$_; $x = 1000 * ( $e + 4/25 ); printf "$ +_:$e => %.20f\n", $x if ($x-int($x))} 5:32 => 32159.99999999999636202119 8:256 => 256160.00000000002910383046 9:512 => 512159.99999999994179233909 10:1024 => 1024160.00000000011641532183 11:2048 => 2048159.99999999976716935635 15:32768 => 32768160.00000000372529029846 18:262144 => 262144159.99999997019767761230 19:524288 => 524288160.00000005960464477539 20:1048576 => 1048576159.99999988079071044922

      update

      in hindsight this may be an effect of precision and correction of the underlying processor and usage of arithmetic units, hence machine dependent.

        IEEE double-precision float is 53 bit float. 0.00000000002910383046 = 2**-35. With the 18 bits for the integral portion of 255160 or 256160, that's the full 53bits. So, apparently, when you take the 53bit fractional representation of 255.16 multiplied by 1000dec, round-off sets the final bit to 0; when you take 256.16 * 1000, round-off sets the final bit to 1. Now (when I have time) I'm going to have to figure out the formula to figure out which powers of two will have this error and which won't. It all depends on the 53rd bit of the product of 2**N+16/25 times 128 (same as the bits of times 1000, since the factor of 8 is a binary shift). Oh, I'm nearly there... the 125 is 0b111_1101... and that will just interact with the 53rd bit... urgh but I've got to leave now... I guess it's an exercise for later. :-)

Re: Integers sometimes turn into Reals after substraction
by Laurent_R (Canon) on May 14, 2016 at 15:50 UTC
    Running your code under Perl 6 displays the following output for the last line:
    x1=256080, x2=258160, diff=2080
    And you can even compare $diff with 2080:
    say $diff == 2080; # -> True
    The reason for the difference is that Perl 6 is not using floats for such calculations, but rationals, so that 4/25 is not stored internally as 0.16, but as 4/25, i.e. a numerator and a denominator.