Числа №9. 0.1 + 0.2
Переходим к кульминации. В этой заметке мы рассмотрим, как складываются числа 0.1 и 0.2. Итак…
Напомню, что десятичное 0.1 в двоичном виде выглядит как бесконечная периодическая дробь:
0.0(0011) * 2^0
Нормализуем ее: сдвинем точку за первый единичный бит:
1.1(0011) e^-4
Обрежем дробную часть до 23 бит. Можно сделать это в лоб: выбросить все биты после двадцать третьего (пробел означает границу):
10011001100110011001100 11001100110011...
Современные компиляторы поступают иначе: они округляют биты, которые не поместились. Если первый отброшенный бит равен 1, он переносится к младшему разряду. Если там тоже единица, они складываются и таким образом передают перенос вперед.
Это наш случай: видим, что отброшенная часть начинается с 1. Это значит, единица переходит в конец фиксированной части. Она кончается на ноль, к нему прибавляется единица, и получается вот что:
10011001100110011001101
Это была мантиса. Экспонента равна -4, к ней прибавляется 127, в двоичном виде получается 1111011. В итоге десятичное 0.1 становится следующим массовом:
0 1111011 10011001100110011001101
Теперь 0.2. Это тоже периодическая дробь вида 0.(0011). Для нормализации ее нужно сдвинуть на три позиции:
1.1(0011) e^-3
Экспонента: -3 + 127 = 124 = 1111100 в двоичном виде. Разделим мантиссу по границе 23 бит. Видим, что она тоже округляется, потому что первый бит отброшенной части начинается с единицы.
10011001100110011001100 11001100110011...
10011001100110011001101
Итого, 0.2 = 0 1111100 10011001100110011001101 в битовом виде. Эти два массива
нужно сложить, но есть нюанс.
Для сложения и вычитания дробных чисел у них должна быть одинаковая экспонента. Сейчас экспоненты разные: -4 и -3. Поэтому одно из чисел денормализуют так, чтобы степени совпадали. Обратите внимание: это второй раз, когда число “плавает”. Первый раз мы двигали разделитель при нормализации, теперь делаем это опять.
Вопрос: какое число двигать? Если двигать биты влево, есть шанс, что потеряются левые биты, а если вправо – правые. Вспомним, что биты идут по убыванию значения. Самый левый бит оказывает наибольший вклад в число, а самый правый – наименьший. Поэтому если двигать число, то так, чтобы биты ехали вправо. Сдвиг вправо аналогичен делению на два, а значит – степень растет. Из этого вывод: берем то число, у которого степень меньше и подгоняем под ту, что больше.
В нашем случае это 0.1, потому что степень -4 меньше, чем -3 у 0.2. Поэтому приведем 0.1 к степени -3. Напомню, число 0.1 мы выразили так:
1.10011001100110011001101 e-4
Сдвиг:
0.110011001100110011001101 e-3
Отсекаем последний бит (в данном случае он не округляется, а именно отбрасывается, почему — понятно не до конца):
0.11001100110011001100110 -3
Теперь у обоих числа одинаковая степень:
0.11001100110011001100110 -3
1.10011001100110011001101 -3
Складываем их побитово в стобик:
1 11 11 11 11 11
0.11001100110011001100110 -3
1.10011001100110011001101 -3
10.01100110011001100110011 -3
Получилось 10.01100110011001100110011 e-3. Однако это не окончательный
результат – число не нормализовано. Результат операции нужно снова
нормализовать, и это уже третий раз, когда число плавает! Сдвинем разделитесь
влево и уменьшим степень. Получится вот что:
1.001100110011001100110011 e-2
Один правый бит не влезает и отсекается:
1.00110011001100110011001 1 e-2
Поскольку он равен единице, то происходит округление: он переходит к правому биту остатка, но там уже единица. Две единицы складываются, получается ноль, а перенос уходит в предпоследний бит, где сейчас ноль.
Результат:
1.00110011001100110011010 e-2
Последний шаг: битовая упаковка. Знак положительный, первый бит ноль. Экспонента
-2+127=125 = 01111101 в двоичном виде. Итого:
0 01111101 00110011001100110011010
Если привести эту штуку к десятичному виду, она даст примерно 0.3 с кучей нулей и четверкой на конце. Но и это — лишь примерное предствавление, о котором мы поговорим в следующей заметке.
Такой набор шагов выполняется каждый раз, когда вы складываете или вычитаете числа с плавающей запятой. Маловероятно, что у обоих чисел окажется одинаковая степень, поэтому битовых сдвигов вам не избежать. А сдвиги – это почти всегда потеря точности.
Пример показывает, как точность теряется во многих местах. Во-первых, оба числа приводятся к двоичному виду с потерей битов, которые не влазят мантиссу. Во-вторых, мы сдвинули первое число, чтобы добиться одинаковых экспонент. Это была еще одна потеря. В третий раз мы нормализовали результат, а это еще один сдвиг и хоть и малая, но потеря точности.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter