I have a simple for loop in Perl
for ($i=0; $i <= 360; $i += 0.01)
{
print "$i ";
}
Why is it that when I run this code I get the following output, where as soon as it gets to 0.81 it suddenly starts to add in a load more decimal places? I know I could simply round up to avoid this issue but I was wondering why it happens. An increment of 0.01 does not seem at all crazy to do.
0.77
0.78
0.79
0.8
0.81
0.820000000000001
0.830000000000001
0.840000000000001
0.850000000000001
0.860000000000001
0.870000000000001
Computers use binary representations. Not all decimal floating point numbers have exact representations in binary notation, so some error can occur (its actually a rounding difference). This is the same reason why you shouldn't use floating point numbers for monetary values:
(Picture taken from dailywtf)
Most elegant way to get around this issue is using integers for calculations, dividing them to the correct number of decimal places and using sprintf
to limit the number of decimal places printed. This will both make sure:
Try this code:
#!/usr/bin/perl
for ($i=0; $i <= 360*100; $i += 1) {
printf "%.2f \n", $i/100;
}
Basically, because the decimal number 0.01 does not have an exact representation in binary floating point, so over time, adding the best approximation to 0.01 deviates from the answer you'd like.
This is basic property of (binary) floating point arithmetic and not peculiar to Perl. What Every Computer Scientist Should Know About Floating-Point Arithmetic is the standard reference, and you can find it very easily with a Google search.
See also: C compiler bug (floating point arithmetic) and no doubt a myriad other questions.
Kernighan & Plauger say, in their old but classic book "The Elements of Programming Style", that:
They also say:
Both sayings point out that floating point arithmetic is not precise.
Note that some modern CPUs (IBM PowerPC) have IEEE 754:2008 decimal floating point arithmetic built-in. If Perl used the correct types (it probably doesn't), then your calculation would be exact.
To demonstrate Jonathan's answer, if you code up the same loop structure in C, you will get the same results. Although C and Perl may compile differently and be ran on different machines the underlying floating point arithmetic rules should cause consistent outputs. Note: Perl uses double-precision floating point for its floating point representation whereas in C the coder explicitly chooses float or double.
Loop in C:
#include <stdio.h>
int main() {
double i;
for(i=0;i<=1;i+=.01) {
printf("%.15f\n",i);
}
}
Output:
0.790000000000000
0.800000000000000
0.810000000000000
0.820000000000001
0.830000000000001
0.840000000000001
0.850000000000001
To demonstrate the point even further, code the loop in C but now use single-precision floating point arithmetic and see that the output is less precise and even more erratic.
Output:
0.000000000000000
0.009999999776483
0.019999999552965
0.029999999329448
0.039999999105930
0.050000000745058
0.060000002384186
0.070000000298023
0.079999998211861
0.089999996125698
0.099999994039536
1/10 is periodic in binary just like 1/3 is periodic in decimal. As such, it cannot be accurately stored in a floating point number.
>perl -E"say sprintf '%.17f', 0.1"
0.10000000000000001
Either work with integers
for (0*100..360*100) {
my $i = $_/100;
print "$i ";
}
Or do lots of rounding
for (my $i=0; $i <= 360; $i = sprintf('%.2f', $i + 0.01)) {
print "$i ";
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With