В прошлой заметке был подвох. Чтобы привести десятичную дробь к двоичной, я выбрал число 42.515625. Оно подобрано так, чтобы результат был коротким и без периода. Хорошо, а что нас ждет с другими числами? Давайте проверим.

Возьмем самое банальное число: 0.1 (одну десятую). Приведем ее к двоичной дроби. Напомню, что нужно умножать дробную часть на два в цикле. Если результат меньше единицы, пишем ноль. Если больше, то пишем один и вычитаем единицу. Продолжаем до тех пор, пока не получим ноль.

Для 0.1 получается следующая таблица:

| number | x2  | bit | next |
|--------|-----|-----|------|
| 0.1    | 0.2 | 0   | 0.2  |
| 0.2    | 0.4 | 0   | 0.4  |
| 0.4    | 0.8 | 0   | 0.8  |
| 0.8    | 1.6 | 1   | 0.6  |
| 0.6    | 1.2 | 1   | 0.2  |
| 0.2    | 0.4 | 0   | 0.4  |
| 0.4    | 0.8 | 0   | 0.8  |
| 0.8    | 1.6 | 1   | 0.6  |
| 0.6    | 1.2 | 1   | 0.2  |
| 0.2    | 0.4 | 0   | 0.4  |
| 0.4    | 0.8 | 0   | 0.8  |
| 0.8    | 1.6 | 1   | 0.6  |
| 0.6    | 1.2 | 1   | 0.2  |

...

0.1 = 0.0001100110011...
0.1 = 0.0(0011)

Очевидно, образовался повтор из 0011. Цикл никогда не закончится: это дробь с периодом. Как ни утруждайся, точно выразить ее в двоичной системе невозможно. Дело не в запасе точности: будь у нас хоть миллион битов, все равно мы потеряем какую-то часть после запятой.

Таблица для 0.1 весьма интересна. Из нее следует, что числа 0.2, 0.4, 0.6, 0.8 и другие тоже приводят к периодическим дробям. Например, 0.2 – это всего лишь 0.1 * 2, то есть очередной шаг итерации. В двоичной системе 0.2 дает 0.(0011), то есть то же самое, только без первого нуля.

Теперь ясно, что происходит, когда вы пишете в коде float f = 0.1. Десятичное 0.1 приводится к двоичной дроби, и получается 0.0(0011) Дробь нормализуется, отсекаются лишние биты, и получается 0.00011001100110011001101. Лишние биты могут как отсекаться, так и округлятся в зависимости от компилятора. Именно из-за округления на конце 01, а не 00.

Поэтому то, что вы видите как 0.1, в двоичном представлении является 0.00011001100110011001101. То же самое происходит с 0.2. Это косвенно дает ответ на вопрос об их сумме. Оба числа лишь приблизительно равны их десятичным литералам. Поэтому сумма тоже равна 0.3 лишь приблизительно.

Предлагаю подумать: почему при печати числа 0.1 и 0.2 выглядят адекватно, а их сумма – 0.30000000000000004? Ведь мы выяснили, что в двоичном виде оба они – мешанина нулей и единиц. На это я отвечу в одной из следующих заметок.