Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

int($x) gives strange result

by natol44 (Sexton)
on Aug 24, 2009 at 06:22 UTC ( [id://790749]=perlquestion: print w/replies, xml ) Need Help??

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

Hello,

#!/usr/bin/perl -w
use CGI; $q = new CGI;
print $q->header(-charset=>'UTF-8');
$x=$q->param("x");
print "x=$x *** ";
$x=(int($x*100))/100;
print "x=$x ***";
1;


http://site.com/x.cgi?x=1.15

1.15 becomes 1.14

How to do to get correct results?

Thank you

Replies are listed 'Best First'.
Re: int($x) gives strange result
by ikegami (Patriarch) on Aug 24, 2009 at 06:34 UTC

    Just like some numbers are periodic in decimal (1/3), some numbers are periodic in binary. 1.15 is one of them.

    ____ 1.15 (b10) = 1.0010010 (b2)

    It can't be represented exactly as a float without infinite storage.

    >perl -e"printf qq{%0.16e\n}, 1.15" 1.1499999999999999e+000

    Previously mentioned sprintf "works" because it rounds instead of truncating.

Re: int($x) gives strange result
by Anonymous Monk on Aug 24, 2009 at 06:40 UTC
    Those results are correct.

    perldoc -f int

    Returns the integer portion of EXPR. If EXPR is omitted, uses $_. You should not use this function for rounding: one because it truncates towards 0, and two because machine representations of floating point numbers can sometimes produce counterintuitive results. For example, "int(-6.725/0.025)" produces -268 rather than the correct -269; that's because it's really more like -268.99999999999994315658 instead. Usually, the "sprintf", "printf", or the "POSIX::floor" and "POSIX::ceil" functions will serve you better than will int().

    What Every Computer Scientist Should Know About Floating-Point Arithmetic

    print int( 1.15 * 100 ),"\n"; print int( 1.15001 * 100 ), "\n"; print int( 1.14999 * 100 ), "\n"; __END__ 114 115 114
      That's misleading. floor and ceil suffer the same problem as int in this area.
      $ perl -MPOSIX -wle'$x = 6.725/0.025; print for $x, int $x, floor $x' 269 268 268

        It's not really fair to argue that a post is misleading when the post just quotes the function documentation in it's entirety, and emphasizes (bolded) the part of the documentation that is relevant to the discussion. That doesn't imply the rest of the documentation is on point -- if anything it suggests that the rest of the documentation is not on point, otherwise all/none of it would have been emphasized.

Re: int($x) gives strange result
by busunsl (Vicar) on Aug 24, 2009 at 06:31 UTC
    If you just want to cut off decimal places you can use the sprintf function:
    $x = sprintf('%.2f', $x);
Re: int($x) gives strange result
by SFLEX (Chaplain) on Aug 24, 2009 at 07:35 UTC
    This seems to produce the results of (x=1.15 x=1.15)
    If that is what you wanted:
    my $x = 1.15; print "x=$x *** "; $x=(int($x*100.1))/100; # note: 100.1 print "x=$x ***";

    What did the Pro-Perl programmer say to the Perl noob?
    You owe me some hair.
      In general that's a very bad idea. If the number is larger, multiplying by 100.1 will severely skew the result:
      $ perl -wle ' my $x = 123456.15; print +(int($x*100.1))/100;' 123579.6

      So it turned 123456.15 into 123579.6 just to get rid of rounding errors - and produced errors which are about a thousand times larger.

      Instead you can add (not multiply) a small constant before calling int:

      $ $ perl -wle ' my $x = 1.15; print +(int($x*100 + 1e-8))/100;' 1.15

      Depending on the range of the used numbers you'd have to think a bit more about the size of the small number you add.

      Perl 6 projects - links to (nearly) everything that is Perl 6.
        ++moritz

        I was playing with it to the thousandth and found about the same "BAD" result:
        my $x = 1.159; print +(int($x*100.1))/100;

        What did the Pro-Perl programmer say to the Perl noob?
        You owe me some hair.

      Given that int() truncates, if you wanted the result rounded to two decimal places you might:

      print int($x*100 + 0.5)/100 ;
      but, as brother ikegami points out, the underlying problem is that most decimal fractions are not exact in binary floating form, which catches everybody out most of the time. So, if we run:
      sub show { my ($y, $p) = @_ ; my $x = $y + 0 ; my $t = $p + 4 ; print "Original value = $y, format = %${t}.${p}f\n" ; printf " \$x = %${t}.${p}f\n", $x ; printf " \$x*100 = %24.20f (%%24.20f)\n", $x * 100 ; printf " int(\$x*100)/100 = %${t}.${p}f\n", int($x*100)/100 ; printf "int(\$x*100 + 0.5)/100 = %${t}.${p}f\n", int($x*100 + 0.5)/10 +0 ; } ; show("1.15", 20) ;
      we get:
      Original value = 1.15, format = %24.20f
                         $x =   1.14999999999999991118
                     $x*100 = 114.99999999999998578915 (%24.20f)
            int($x*100)/100 =   1.13999999999999990230
      int($x*100 + 0.5)/100 =   1.14999999999999991118
      
      and we see that the binary floating value for 1.15 is just a little bit smaller, such that int(1.15*100) is 114 -- so what looks like a way of getting two decimal places is actually making things worse. Adding 0.5 to round rather than truncate gives us, in this case, the same value as we started with (assuming +ve values). So show("1.15", 2) gives:
      Original value = 1.15, format = %6.2f
                         $x =   1.15
                     $x*100 = 114.99999999999998578915 (%24.20f)
            int($x*100)/100 =   1.14
      int($x*100 + 0.5)/100 =   1.15
      
      ...so int($x*100 + 0.5)/100 seems to do the trick -- but note that it only rarely returns a value which is an exact two decimal places.

      Note that the multiplication and division are themselves introducing small rounding errors. Consider show("0.15", 20), which gives:

      Original value = 0.15, format = %24.20f
                         $x =   0.14999999999999999445
                     $x*100 =  15.00000000000000000000 (%24.20f)
            int($x*100)/100 =   0.14999999999999999445
      int($x*100 + 0.5)/100 =   0.14999999999999999445
      
      where the rounding after multiplying by 100 just happens to give exactly 15.

      If you want to truncate to two places of decimals, then something like int($x*100 + 0.0000001)/100 will generally do the trick -- but this is assuming a certain amount about (a) the precision of the floating point, and (b) the precision to which you want to work. (And (c) that the numbers are +ve.)

      Interestingly, using printf "%0.2f" isn't the same as doing explicit rounding. Consider show("0.235", 20), which gives:

      Original value = 0.235, format = %24.20f
                         $x =   0.23499999999999998668
                     $x*100 =  23.50000000000000000000 (%24.20f)
            int($x*100)/100 =   0.23000000000000000999
      int($x*100 + 0.5)/100 =   0.23999999999999999112
      
      while show("0.235", 2) gives:
      Original value = 0.235, format = %6.2f
                         $x =   0.23
                     $x*100 =  23.50000000000000000000 (%24.20f)
            int($x*100)/100 =   0.23
      int($x*100 + 0.5)/100 =   0.24
      
      ... because 0.235 is held as 0.234999..., the "%.2f" format gives 0.23. The explicitly rounded value is probably more what was expected.

      Even when the fraction is an exact binary fraction you can get unexpected results. show("0.125", 2) gives:

      Original value = 0.125, format = %6.2f
                         $x =   0.12
                     $x*100 =  12.50000000000000000000 (%24.20f)
            int($x*100)/100 =   0.12
      int($x*100 + 0.5)/100 =   0.13
      
      while show("0.375", 2) gives:
      Original value = 0.375, format = %6.2f
                         $x =   0.38
                     $x*100 =  37.50000000000000000000 (%24.20f)
            int($x*100)/100 =   0.37
      int($x*100 + 0.5)/100 =   0.38
      
      (0.125 = 1/8, 0.375 = 3/8 -- so both are exact binary floating point values.) You can see that "%.2f" has rounded 0.125 down to 0.12, but 0.375 up to 0.38. This is an effect of the default rounding mode ("round to even") in IEEE 754 standard floating point arithmetic.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://790749]
Approved by ikegami
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others learning in the Monastery: (6)
As of 2024-04-19 13:15 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found