@krox
аспирант, программист, исследователь

Python: почему в процессе суммирования десятичных в середине интервала появляется ошибка?

Здравствуйте, уважаемые хабравчане.
Тему обозначил в заголовке. Код уровня первоклассника. НО в процессе суммирования (увеличения значений на константу) происходит накопление ошибки, которое потом пропадает.
>>> interv = 10
>>> start = -2
>>> stop = 2
>>> mas =[]
>>> step = 0.4
>>> mas.append(start)
>>> for i in range(1, interv):
	mas.append(mas[i-1]+step)


И в результате наблюдаются странности
>>> mas
[-2, -1.6, -1.2000000000000002, -0.8000000000000002, -0.40000000000000013, -1.1102230246251565e-16, 0.3999999999999999, 0.7999999999999999, 1.2, 1.6]

Конечно, можно использовать округление, но это не всегда выход и хотелось бы понять из-за чего возникает данная ошибка.
  • Вопрос задан
  • 3478 просмотров
Пригласить эксперта
Ответы на вопрос 5
alexmuz
@alexmuz
Почитайте: http://habrahabr.ru/post/112953/
Ответ написан
Комментировать
trapwalker
@trapwalker Куратор тега Python
Программист, энтузиаст
Вообще-то описанную проблему можно проиллюстрировать проще:
>>> 0.5+2.7, 0.4+2.8
(3.2, 3.1999999999999997)

Это нормальная ситуация для чисел с плавающей точкой. Она связана с тем, что для любой системы счисления (двоичной, десятичной, троичной) есть числа, которые нельзя записать в виде конечной последовательности n-ричных разрядов. Например, в десятичной системе число 1/3 будет выглядеть как бесконечная последовательность троек после запятой. Если мы остановимся, то получим не 1/3 а что-то поменьше. Зато в троичной системе это число замечательно представляется как 0.1. Одна девятая в троичной системе равна 0.01.
Та же проблема, например, у десятичного числа 0.2 при переводе в двоичную систему:
2/10==1/5==1/8+1/16+1/128+1/256+1/2048+1/4096+(...)==0,00110011(0011)[в двоичной системе]
Как видим это периодическое число и двоичные разряды "0011" повторяются в нём после запятой бесконечно.
Компьютерное представление чисел с плавающей точкой подразумевает их хранение в конечном количестве двоичных разрядов. Это значит, что многие вполне нормально записываемые в десятичной форме дробные числа просто не могут абсолютно точно храниться в компьютере (если речь идёт о floatpoint).

Имеенно поэтому программистов учат на первом курсе института никогда не сравнивать числа с плавающей точкой на строгие равенство и неравенство. С учетом погрешности из-за округления двоичных разрядов это просто бессмысленно и некорректно.
Зато, вместо сравнения на равенство, можно сравнивать abs(a-b)<eps, где a и b -- числа в формате с плавающей точкой, а eps -- это некоторая пренебрежительно малая величина. Вообще-то, думаю, можно вычислить даже минимальное значение этой величины, чтобы она покрыла все неприятности с округлением двоичных разрядов.
Ответ написан
Комментировать
По-моему ничего особенного здесь нет. Нормальная погрешность для вещественных чисел.
Ответ написан
Комментировать
@PaulOkopny
Вполне себе нормальная ситуация для чисел с плавающей точкой. Хотите избавиться - используйте decimal.
Ответ написан
Комментировать
ali_aliev
@ali_aliev
Разработчик на Django/Python, JavaScript
Причиной такого странного поведения является ограничение аппаратных средств, реализующих вещественную математику. Пример:

>>> f = 1/3.0
>>> f
0.3333333333333333
>>> '%4.2f' % f
'0.33'
>>>
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы