Числа с плавающей точкой - не действительные!
(В оригинале - Floating-point Numbers Aren't Real)
Числа с плавающей точкой не являются действительными числами в математическом смысле, хотя и называются «Real» в некоторых языках программирования. Действительные числа обладают бесконечной точностью и поэтому непрерывные. Числа с плавающей точкой обладают ограниченной точностью и скорее похожи на «плохо работающие» целые числа, поскольку они даже не занимают весь диапазон, отведенный под целые числа.
Для иллюстрации этого возьмите и присвойте целое число 2147483647 (это максимальное 32-битное целое) 32-х битной же переменной с плавающей точкой, скажем, х. Если вы напечатаете ее, то вы увидите 2147483648. А теперь напечатайте х – 64
. Напечатается опять 2147483648. А теперь попробуйте напечатать х – 65
, и получите 2147483520! Почему? Потому что расстояние между двумя соседними числами с плавающей точкой – 128, и результат округляется до ближайшего существующего числа.
По IEEE числа с плавающей точкой – это числа фиксированной длины, записываемые следующим образом: 11.d1d2...dp-1 × 2e, где р – это точность (24 для float, 53 для double). Расстояние между двумя ближайшими числами при этом - 21-p+e, что можно приблизительно выразить как ε|x|, где ε – машинный эпсилон (21-p)
Знание о расстоянии между числами поможет избежать классических грубых ошибок. Например, если вы делаете итеративное вычисление, например, поиск корней уравнения, то нет смысла задавать точность выше, чем это расстояние. Если вы зададите точность меньше этого, то цикл итерации будет длиться бесконечно.
Поскольку числа с плавающей точкой – лишь приближение к действительным числам, то при их использовании практически всегда будет присутствовать неточность. Эта неточность, называемая ошибкой округления, может приводить к удивительным результатам. Когда вы вычитаете близкие по значению числа, то наиболее значимые цифры взаимовычтутся, а наименее значимые (те, где ошибка скрывается ошибка округления) передвинутся на более значимые позиции, «загрязняя» дальнейшие вычисления (этот эффект получил название «размазывание», «smearing»). Вам нужно следить за применяемыми алгоритмами, чтобы не допустить этого. Например, представьте решение уравнения x2 - 100000x + 1 = 0. Поскольку один из корней требует такого вычитания -b + sqrt(b2 - 4), то можно вычислить сначала другой r1 = -b + sqrt(b2 - 4), а потом найти первый по формуле r2 = 1/r1, потому что для любого ax2 + bx + c = 0 корни удовлетворяют условию r1r2 = c/a.
Размазывание может случиться и в гораздо более тонких моментах. Представьте библиотеку, вычисляющую ex по формуле 1 + x + x2/2 + x3/3! + .... Это работает для положительных х, однако для больших отрицательных работать перестанет. Четные степени дадут большие положительные числа, и вычитание отрицательных вообще не окажет влияния на результат. Проблема здесь в том, что ошибка округления для больших положительных чисел оказывается значительно больше, чем правильный ответ. В результате вычисление по этой формуле даст в ответе бесконечность! Решение же очень простое – для отрицательных х вычислять ex = 1/e|x|.
И конечно же, даже и говорить не стоит, что числа с плавающей точкой нельзя использовать для финансовых вычислений. Для них специально разработаны десятичные классы (decimal) в языках вроде Python и C#. Числа с плавающей точкой предназначены для эффективных научных вычислений. Однако эффективность бесполезна без нужной точности, поэтому помните о возможных ошибках округления!
Автор оригинала - Chuck Allison