1

I have a for loop and I want to increment the variable by 0.1 each time, however the value changes differently from the increment and I am unsure as to why.

I've simplified the for loop and it still gives a strange output:

for (my $t = 1000; $t < 1500 ;$t+=0.1) { print "$t\n"; } 

It prints:

1034.9 1035 1035.1 1035.2 1035.3 1035.4 1035.49999999999 1035.59999999999 1035.69999999999 1035.79999999999 1035.89999999999 1035.99999999999 1036.09999999999 1036.19999999999 1036.29999999999 [it then goes on like this to 1500] 

I do not know where the decimal places are coming from. Is this a problem with my understanding of Perl?

Thanks in advance.

3
  • 6
    It's worse than that. It's a problem with your understanding of floating point arithmetic. Change your print statement to printf "%.18f\n",$t; for enlightenment. Commented Oct 29, 2013 at 18:19
  • 1
    Wait, what? No-one is linking to "What Every Programmer Should Know About Floating Point Arithmetic"? There should be a badge for this.... docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html Commented Oct 29, 2013 at 18:25
  • 4
    @PaulDixon, the Perl FAQ that was linked to has a link to that paper. Commented Oct 29, 2013 at 18:31

4 Answers 4

9

1/10 is a periodic number in binary like 1/3 is in decimal. It cannot be represented exactly as a floating point number.

$ perl -e'printf "%.20g\n", 0.1' 0.10000000000000001 

Never compare a floating pointer number to another without involving a tolerance, and be wary of accumulation of error.

The simple solution here to to do the arithmetic using integers, and generate the floating point numbers when needed

for (my $tx10 = 10000; $tx10 < 15000; ++$tx10) { my $t = $tx10/10; print "$t\n"; } 

which simplifies to

for my $tx10 (10000..14999) { my $t = $tx10/10; print "$t\n"; } 

 ____ ____ ____ 0.1 = 0.00011 0.4 = 0.0110 0.7 = 0.10110 ____ ____ 0.2 = 0.0011 0.5 = 0.1 0.8 = 0.11001 ____ ____ ____ 0.3 = 0.01001 0.6 = 0.1001 0.9 = 0.11100 
Sign up to request clarification or add additional context in comments.

Comments

2
for (my $t = 1000; $t < 1500 ;$t+=.1) { printf("%.1f\n", $t); } 

Comments

1

Alternative:

for (10000..14999) { my $t = $_/10; print "$t\n"; } 

Since 0.1 cannot be exactly specified in binary, rounding errors will accumulate in your code. In this answer, the amount always stays close enough to exact so that perl's internal number to string rounding will display the correct number. Lesson: use integers whenever possible.

1 Comment

This goes up to 1500.0, where the original code went up to 1499.9.
0

To test for the condition

perl -le 'for (my $t = 1000.0; $t < 1500.0 ;$t+=0.1) { print $t}'| perl -n -e '($a)=/(\d$)/; print "fail $a $b $_" if ($a eq $b); $b=$a' 

yet another way to fix it

perl -le 'for (my $t = 1000.0; $t < 1500.0 ;$t+=0.1) { $t=sprintf("%.1f", $t); print $t}'| perl -n -e '($a)=/(\d$)/; print "fail $a $b $_" if ($a eq $b); $b=$a' 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.