Выведите 0.1 + 0.2 в большинстве языков программирования — и получите 0,30000000000000004. Ничего не сломалось: так компьютеры хранят вещественные числа. В этом руководстве объясняется формат двойной точности IEEE 754, который используют SciKey и большинство вычислительных программ, как его ограничения проявляются в результатах и как калькулятор с ними справляется.
1. Двойная точность IEEE 754
Число двойной точности (binary64) занимает 64 бита.
| Часть | Биты | Назначение |
|---|---|---|
| Знак | 1 | Положительное или отрицательное |
| Порядок | 11 | Какая степень двойки |
| Мантисса | 52 | Значащие разряды (фактически 53 со скрытой старшей единицей) |
53 значащих бита соответствуют примерно 15–17 десятичным цифрам. Наибольшее представимое значение — около 1,797×10³⁰⁸, а расстояние между 1 и следующим числом двойной точности (машинный эпсилон) равно 2⁻⁵² ≈ 2,22×10⁻¹⁶.
2. У 0,1 нет точного двоичного представления
Как 1/3 = 0,333… никогда не заканчивается в десятичной системе, так и 1/10 не заканчивается в двоичной: 0,1 = 0,0001100110011…₂ — бесконечная периодическая дробь. Она округляется до 53 бит, поэтому на самом деле хранится значение
0,1000000000000000055511151231257827…
0,2 тоже хранится с небольшим избытком. Их сумма переносит обе погрешности и даёт 0,3000000000000000444…, а это другое число двойной точности, чем то, в котором хранится 0,3 (0,2999999999999999888…). Поэтому кратчайшая десятичная запись, однозначно задающая сумму, — 0,30000000000000004.
По той же причине 0.1×3 тоже равно 0,30000000000000004. Десятичные дроби со знаменателями — степенями двойки, такие как 0,5, 0,25 и 0,375, в двоичной системе точны и погрешности не несут.
3. Почему калькулятор всё же показывает 0,3
SciKey выводит не «сырое» внутреннее значение, а округляет его до заданного числа значащих цифр (по умолчанию 12). При округлении до 12 цифр 0,30000000000000004 становится 0,300000000000, то есть 0,3. Погрешность находится в 17-м разряде и до экрана не доходит.
Карманные инженерные калькуляторы работают так же: как правило, они считают примерно с 15 внутренними разрядами и показывают 10, скрывая мельчайшие погрешности на этапе вывода. Меняется только отображение, хранимое значение по-прежнему 0,30000000000000004. При дальнейших вычислениях обычно остаются погрешности порядка 10⁻¹⁶, которые на практике не важны.
4. Почему sin(π) не равен нулю
В режиме RAD sin(π) должен быть ровно 0. Но π иррационально, и ближайшее к π число двойной точности примерно на 1,22×10⁻¹⁶ меньше истинного значения. Вблизи π синус имеет наклон −1, поэтому sin(π в двойной точности) ≈ 1,2246×10⁻¹⁶. Сам синус вычислен точно; просто на входе никогда не было точного π.
SciKey исправляет это по следующим правилам:
- В режимах DEG и GRAD аргумент точно приводится по модулю 360° (400 град), а кратные 90° дают точные значения:
sin(180)= 0,cos(90)= 0, аtan(90)не определён и поэтому возвращает ошибку. - В режиме RAD, если аргумент совпадает с целым кратным π с относительной погрешностью 10⁻¹³, sin возвращает 0; если совпадает с нечётным кратным π/2, cos возвращает 0, а tan — ошибку.
Поэтому sin(π) в SciKey показывает 0. Коррекция действует только вблизи кратных π и не влияет ни на какие другие вычисления.
5. Большие числа: факториалы и переполнение
Факториалы растут быстро. 170! ≈ 7,25741561531×10³⁰⁶ ещё помещается в число двойной точности, а 171! ≈ 1,24×10³⁰⁹ уже превышает максимум 1,797×10³⁰⁸. Поэтому n! в SciKey принимает целые от 0 до 170, а 171! возвращает ошибку переполнения.
На практике большие факториалы обычно стоят в отношениях. Число сочетаний C(200, 3) равно 200!/(3!·197!), но nCr(200, 3) = 1313400 вычисляет его напрямую, без огромных промежуточных значений. В теории вероятностей также принято брать логарифмы и складывать их.
6. Предел целых чисел: 2⁵³
При 53 значащих битах все целые числа до 2⁵³ = 9007199254740992 представляются точно. Выше числа двойной точности начинают пропускать целые. 2⁵³ + 1 непредставимо и округляется обратно до 2⁵³, поэтому 2^53+1-2^53 даёт 0, а не 1: прибавленная единица исчезла при сохранении суммы.
Поэтому SciKey показывает двоичное, восьмеричное и шестнадцатеричное представления только для целых с модулем до 2⁵³; дальше младшие разряды не гарантированы. По той же причине номера карт или идентификаторы длиннее 16 цифр меняют последние цифры, если обращаться с ними как с числами.
7. Катастрофическое сокращение — самая опасная погрешность
Вычитание двух почти равных чисел уничтожает старшие разряды и оставляет в основном погрешность. Это называется катастрофическим сокращением (потерей значимости).
Пример 1. (1+1E-15)-1 должно давать 10⁻¹⁵, но двойная точность даёт 1,11022302463×10⁻¹⁵. При сохранении 1 + 10⁻¹⁵ возникает ошибка округления порядка 10⁻¹⁶, и после вычитания 1 она составляет 11 % результата.
Пример 2. При x = 10⁸ значение √(x²+1) − x примерно равно 5×10⁻⁹. При прямом вычислении √(10¹⁶+1) неотличимо от 10⁸, и результат равен 0. Умножение на сопряжённое даёт равносильную форму без вычитания:
√(x²+1) − x = 1 / (√(x²+1) + x)
Сохраните 10⁸ в x, и 1/(sqrt(x^2+1)+x) точно вернёт 5×10⁻⁹.
Пример 3. 1 − cos(10⁻⁸) примерно равно 5×10⁻¹⁷, но cos(10⁻⁸) округляется ровно до 1, и прямой результат — 0. С тождеством 1 − cos x = 2 sin²(x/2) выражение 2sin(0.5E-8)^2 даёт 5×10⁻¹⁷ (режим RAD).
Никакое число выводимых цифр этого не исправит. Формулу нужно переписать.
8. Практические советы
- Не проверяйте точное равенство. Чтобы сравнить два вычисленных результата, проверяйте, достаточно ли мала их разность (например, относительная погрешность 10⁻¹²).
- Деньги считайте в целых единицах. Работайте в наименьших денежных единицах (например, в копейках) как с целыми числами, чтобы дробные погрешности не накапливались.
- Избегайте вычитания почти равных чисел. Умножайте на сопряжённое, используйте тождества или преобразуйте выражение, чтобы убрать вычитание.
- Для больших чисел работайте с отношениями. Когда факториалы или степени «взрываются», используйте nCr, логарифмы или сначала сокращайте.
- Не округляйте промежуточные результаты. Погрешность двойной точности — около 10⁻¹⁶, а ручное округление до четырёх цифр вносит погрешность порядка 10⁻⁴. Бо́льшие ошибки обычно вносит человек, а не машина.
- Попробуйте увеличить вывод до 15 цифр. Если результат выглядит странно, покажите больше цифр, чтобы увидеть внутреннее значение и отличить погрешность в последних разрядах от действительно неверной формулы.