Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl - Unexpected outcome of while loop

This is a simple program but I am unable to understand logic/work being done by while loop behind the scene.

Problem: Write a program that prints every number from 0 to 1 that has a single digit after the decimal place (that is, 0.1, 0.2, and so on).

So here is my code:

$num = 0;
while ( $num < 1 ) {
  print "$num \n";
  $num = $num + 0.1;
}

If I write it in this way, it is going to print

$num = 0;
while ( $num < 1 ) {
  $num = $num + 0.1;
  print "$num \n";
}

Output:

0.1 
0.2 
0.3 
0.4 
0.5 
0.6 
0.7 
0.8 
0.9 
1 
1.1 

Ideally speaking, 1 and 1.1 should not get printed in both code samples respectively. After printing 0.9 when 0.1 is added to it, it becomes 1.0 i.e. while (1.0 < 1). Hence condition in while loop is false, so it should not print 1 and 1.1. But that is what is happening.

Can someone please explain why while loop is working in this unexpected way i.e. printing 1 and 1.1 even when condition is false.

like image 264
curious Avatar asked Dec 31 '25 15:12

curious


1 Answers

1/10 is a periodic number in binary just like 1/3 is periodic in decimal.

          ____
1/10 = 0.00011 base 2

As such, it can't be represented exactly by a floating-point number.

$ perl -e'printf "%$.20e\n", 0.1;'
1.00000000000000005551e-01

This imprecision is the cause of your problem.

$ perl -e'my $i = 0; while ($i < 1) { printf "%1\$.3f  %1\$.20e\n", $i; $i += 0.1; }'
0.000  0.00000000000000000000e+00
0.100  1.00000000000000005551e-01
0.200  2.00000000000000011102e-01
0.300  3.00000000000000044409e-01
0.400  4.00000000000000022204e-01
0.500  5.00000000000000000000e-01
0.600  5.99999999999999977796e-01
0.700  6.99999999999999955591e-01
0.800  7.99999999999999933387e-01
0.900  8.99999999999999911182e-01
1.000  9.99999999999999888978e-01

Generally speaking, one can solve this by checking if the number is equal to another within some tolerance. But in this case, there's a simpler solution.

$ perl -e'for my $j (0..9) { my $i = $j/10; printf "%1\$.3f  %1\$.20e\n", $i; }'
0.000  0.00000000000000000000e+00
0.100  1.00000000000000005551e-01
0.200  2.00000000000000011102e-01
0.300  2.99999999999999988898e-01
0.400  4.00000000000000022204e-01
0.500  5.00000000000000000000e-01
0.600  5.99999999999999977796e-01
0.700  6.99999999999999955591e-01
0.800  8.00000000000000044409e-01
0.900  9.00000000000000022204e-01

The above solution not only performs the correct number of iterations, it doesn't accumulate error, so $i is always as correct as it can be.

like image 184
ikegami Avatar answered Jan 04 '26 15:01

ikegami



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!