7

I'm not sure if this is a bug or not, so I thought that maybe you folks might want to take a look.

The problem lies with this code:

for i=0,1,.05 do
    print(i)
end

The output should be:

0
.05
.1
--snip--
.95
1

Instead, the output is:

0
.05
.1
--snip--
.95

This same problem happened with a while loop:

w = 0
while w <= 1 do
    print(w)
    w = w + .05
end
--output:
0
.05
.1
--snip--
.95

The value of w is 1, which can be verified by a print statement after the loop.

I have verified as much as possible that any step that is less than or equal .05 will produce this error. Any step above .05 should be fine. I verified that 1/19 (0.052631579) does print a 1. (Obviously, a decimal denominator like 19.9 or 10.5 will not produce output from [0,1] inclusive.) Is there a possibility that this is not an error of the language? Both the interpreter and a regular Lua file produce this error.

daurnimator
  • 4,091
  • 18
  • 34
T Suds
  • 317
  • 2
  • 7
  • 1
    As others have pointed to the rounding error, I'll point out a workaround. Just use a number slightly larger than 1 for your end point, e.g. `for i=0,1.01,0.05 do` ... – BMitch Aug 30 '11 at 01:38

4 Answers4

8

This is a rounding problem. The issue is that 0.05 is represented as a floating point binary number, and it does not have an exact representation in binary. In base 2 (binary), it is a repeating decimal similar to numbers like 1/3 in base 10. When added repeatedly, the rounding results in a number which is slightly more than 1. It is only very, very slightly more than 1, so if you print it out, it shows 1 as the output, but it is not exactly 1.

> x=0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05
> print(x)
1
> print(1==x)
false
> print(x-1)
2.2204460492503e-16

So, as you can see, although really close to 1, it is actually slightly more.

A similar situation can come up in decimal when we have repeating fractions. If we were to add together 1/3 + 1/3 + 1/3, but we had to round to six digits to work with, we would add 0.333333 + 0.333333 + 0.333333 and get 0.999999 which is not actually 1. This is an analogous case for binary math. 1/20 cannot be precisely represented in binary.

Note that the rounding is slightly different for multiplication so

> print(0.05*20-1)
0
> print(0.05*20==1)
true

As a result, you could rewrite your code to say

for i=0,20,1 do
    print(i*0.05)
end

And it would work correctly. In general, it's advisable not to use floating point numbers (that is, numbers with decimal points) for controlling loops when it can be avoided.

Keith Irwin
  • 5,628
  • 22
  • 31
  • As a quick work around, I made the loop run from 0 to 1+step when the step was <= .05. I guess that isn't a good work around because of the floating point rounding errors. Since I'm using i in the body of the loop for math, I'm going to need accuracy. Thanks. – T Suds Aug 30 '11 at 13:37
  • The problem is that sometimes the rounding rounds down. So if you're going to use floating point numbers, going to 1+step could result in an extra iteration of the loop. You'd be much better off going to 1+(step/2) which would be guaranteed to never overdo or underdo it unless step was really, really, really small. – Keith Irwin Aug 30 '11 at 17:22
3

This is a result of floating-point inaccuracy. A binary64 floating point number is unable to store 0.05 and so the result will be rounded to a number which is very slightly more than 0.05. This rounding error remains in the repeated sum, and eventually the final value will be slightly more than 1.0, and so will not be displayed.

Mankarse
  • 39,818
  • 11
  • 97
  • 141
2

This is a floating point thing. Computers don't represent floating point numbers exactly. Tiny rounding errors make it so that 20 additions of +0.05 does not result in precisely 1.0. Check out this article: "What every programmer should know about floating-point arithmetic."

To get your desired behavior, you could loop i over 1..20, and set f=i*0.05

AShelly
  • 34,686
  • 15
  • 91
  • 152
  • Actually, in this case, 20*0.05 == 1.0. It's just 0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05+0.05 which doesn't equal 1.0. It's the difference between rounding once (which happens to come out right) and rounding 19 times (which doesn't happen to come out right). For other examples, it could go the other way. – Keith Irwin Aug 30 '11 at 01:40
  • good point - I used "20*" as shorthand for what the code was doing, but that's not exactly equivalent in floating point calculations. – AShelly Aug 30 '11 at 19:30
1

This is not a bug in Lua. The same thing happens in the C program below. Like others have explained, it's due to floating-point inaccuracy, more precisely, to the fact that 0.05 is not a binary fraction (that is, does not have a finite binary representation).

#include <stdio.h>
int main(void)
{
    double i;
    for (i=0; i<=1; i+=0.05) printf("%g\n",i);
    return 0;
}
lhf
  • 70,581
  • 9
  • 108
  • 149