Переходим к кульминации. В этой заметке мы рассмотрим, как складываются числа 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 с кучей нулей и четверкой на конце. Но и это — лишь примерное предствавление, о котором мы поговорим в следующей заметке.

Такой набор шагов выполняется каждый раз, когда вы складываете или вычитаете числа с плавающей запятой. Маловероятно, что у обоих чисел окажется одинаковая степень, поэтому битовых сдвигов вам не избежать. А сдвиги – это почти всегда потеря точности.

Пример показывает, как точность теряется во многих местах. Во-первых, оба числа приводятся к двоичному виду с потерей битов, которые не влазят мантиссу. Во-вторых, мы сдвинули первое число, чтобы добиться одинаковых экспонент. Это была еще одна потеря. В третий раз мы нормализовали результат, а это еще один сдвиг и хоть и малая, но потеря точности.