Странно для проблемы петли - PullRequest
7 голосов
/ 30 августа 2011

Я не уверен, является ли это ошибкой или нет, поэтому я подумал, что, возможно, вы, ребята, захотите посмотреть.

Проблема заключается в следующем коде:

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

Вывод должен быть:

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

Вместо этого вывод:

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

Эта же проблема произошла с циклом while:

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

Значение w равно 1, что можно проверить с помощью оператора print после цикла.

Я максимально проверял, что любой шаг, который меньше или равен 0,05, приведет к этой ошибке. Любой шаг выше .05 должен быть в порядке. Я проверил, что 1/19 (0.052631579) действительно печатает 1. (Очевидно, десятичный знаменатель, такой как 19.9 или 10.5, не будет производить вывод из [0,1] включительно.) Есть ли вероятность, что это не ошибка языка ? И интерпретатор, и обычный файл Lua выдают эту ошибку.

Ответы [ 4 ]

8 голосов
/ 30 августа 2011

Это проблема округления. Проблема в том, что 0,05 представляется в виде двоичного числа с плавающей запятой, и оно не имеет точного представления в двоичном виде. В базе 2 (двоичная) это повторяющаяся десятичная дробь, аналогичная числам типа 1/3 в базе 10. При многократном добавлении округление приводит к числу, которое немного больше 1. Это только очень, очень немного больше, чем 1 , поэтому, если вы распечатаете его, он покажет 1 как вывод, но это не точно 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

Итак, как вы можете видеть, хотя действительно близко к 1, на самом деле это немного больше.

Подобная ситуация может возникать в десятичной форме, когда у нас есть повторяющиеся дроби. Если бы мы сложили 1/3 + 1/3 + 1/3, но нам пришлось бы округлить до шести цифр для работы, мы бы добавили 0.333333 + 0.333333 + 0.333333 и получили бы 0.999999, что на самом деле не равно 1. Это аналогичный случай для двоичной математики. 1/20 не может быть точно представлена ​​в двоичном виде.

Обратите внимание, что округление немного отличается для умножения, поэтому

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

В результате вы можете переписать свой код так:

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

И это будет работать правильно. В общем, желательно не использовать числа с плавающей запятой (то есть числа с десятичными точками) для управления циклами, когда этого можно избежать.

3 голосов
/ 30 августа 2011

Это результат погрешности с плавающей точкой. Бинарное число с плавающей запятой 64 не может хранить 0,05, и поэтому результат будет округлен до числа, которое немного больше 0,05. Эта ошибка округления сохраняется в повторяющейся сумме, и в конечном итоге окончательное значение будет чуть больше 1,0 и поэтому не будет отображаться.

2 голосов
/ 30 августа 2011

Это вещь с плавающей точкой. Компьютеры не представляют числа с плавающей запятой точно. Крошечные ошибки округления приводят к тому, что 20 добавлений +0,05 не дают точно 1,0. Прочтите эту статью: " Что должен знать каждый программист об арифметике с плавающей точкой. "

Чтобы получить желаемое поведение, вы можете зациклить i над 1..20 и установить f = i * 0.05

1 голос
/ 31 августа 2011

Это , а не ошибка в Lua.То же самое происходит в программе на C ниже.Как объяснили другие, это связано с неточностью с плавающей запятой, точнее с тем, что 0,05 не является двоичной дробью (то есть не имеет конечного двоичного представления).

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