Числа №6. 0.1 в двоичном виде
В прошлой заметке был подвох. Чтобы привести десятичную дробь к двоичной, я
выбрал число 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? Ведь мы выяснили, что в двоичном виде оба они –
мешанина нулей и единиц. На это я отвечу в одной из следующих заметок.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter