views:

249

answers:

6

I can't for the life of me figure out why the following produces the result it does.

use POSIX;
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil($n);
print "$c ($n)\n";

Sigil-tastic, I know — sorry.

I've solved this for my app as follows:

use POSIX;
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil("$n");
print "$c ($n)\n";

...but I'm bewildered as to why that's necessary here.

+6  A: 
use strict;
use warnings;
use POSIX;

my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;  # Should be exactly 3.

# But it's not.
print "Equals 3\n" if $n == 3;

# Check it more closely.
printf "%.18f\n", $n;

# So ceil() is doing the right thing after all.
my $c = ceil($n);
print "g=$g t=$t r=$r n=$n c=$c\n";
FM
+8  A: 

What's happening is this: $n contains a floating point value, and thus is not exactly equal to 3, on my computer it's 3.00000000000000044409. Perl is smart enough to round that to 3 when printing it, but when you explicitly use a floating point function, it will do exactly what it advertises: ceil that to the next integral number: 4.

This is a reality of working with floating point numbers, and by no means Perl specific. You shouldn't rely on their exact value.

Leon Timmermans
Many thanks all for your answers. I'm pleased it's a universal floating-point deviation from reality. But still, wow!
shedside
+4  A: 

Not a Perl problem, as such

#include <stdlib.h>
#include <math.h>
#include <stdio.h>
main()
{
  double n = (6.65 * 4.0 - 6.65) / 6.65;
  double c = ceil(n);
  printf("c is %g, n was %.18f\n", c, n);
}

c is 4, n was 3.000000000000000444
mobrule
+5  A: 

Obligatory Goldberg reference: What Every Computer Scientist Should Know About Floating-Point Arithmetic.

Using Perl, the ability to treat a string as a number in a numerical operation turns into an advantage because you can easily use sprintf to explicitly specify the amount of precision you want:

use strict; use warnings;

use POSIX qw( ceil );
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil( sprintf '%.6f', $n );
print "$c ($n)\n";

Output:

C:\Temp> g
3 (3)

The problem comes about because, given the finite number of bits available to represent a number, there are only a large but finite number of numbers that can be represented in floating point. Given that there are uncountably many numbers on the real line, this will result in approximation and rounding errors in the presence of intermediate operations.

Sinan Ünür
+3  A: 

Some numbers (like 6.65) have an exact representation in decimal, but cannot be represented exactly in the binary floating point that computers use (just like 1/3 has no exact decimal representation). As a result, floating point numbers are frequently slightly different than what you would expect. The result of your calculation is not 3, but about 3.000000000000000444.

The traditional way of handling this is to define some small number (called epsilon), and then consider two numbers equal if they differ by less than epsilon.

Your solution of ceil("$n") works because Perl rounds a floating point number to around 14 decimal places when converting it to a string (thus converting 3.000000000000000444 back to 3). But a faster solution would be to subtract epsilon (since ceil will round up) before computing ceil:

my $epsilon = 5e-15; # Or whatever small number you feel is appropriate
my $c = ceil($n - $epsilon);

A floating point subtraction should be faster than converting to a string and back (which involves a lot of division).

cjm
+1  A: 

The other answers have explained why the problem is there, there's two ways to make it go away.

If you can, you can compile Perl to use higher precision types. Configuring with -Duse64bitint -Duselongdouble will make Perl use 64 bit integers and long doubles. These have high enough precision to make most floating point problems go away.

Another alternative is to use bignum which will turn on transparent arbitrary precision number support. This is slower, but precise, and can be used lexically.

{
    use bignum;
    use POSIX;
    my $g = 6.65;
    my $t = $g * 4;
    my $r = $t - $g;
    my $n = $r / $g;
    my $c = ceil($n);
    print "$c ($n)\n";
}

You can also declare individual arbitrary precision numbers using Math::BigFloat

Schwern
Even long doubles aren't going to prevent this problem. You're still going to get some results that are just slightly bigger than the integer you expected, and `ceil` will round that up to the next integer. Long doubles just make "slightly bigger" a smaller quantity.
cjm