Как округлять двоичные числа
Все мы привыкли округлять числа в десятичной системе. В двоичной системе округление устроено похоже, но всё же способно запутать. Больше всего сложностей вызывают дроби. Например, далеко не сразу очевидно, почему дробь 0.11101 при округлении до 2 знаков после запятой превращается в целое 1. В этой статье разбираются общие правила округления двоичных дробей и объясняется, откуда они берутся. Те же правила применимы и к округлению двоичных целых чисел.
Стандарт IEEE-754 определяет пять режимов округления, и большинство из них довольно просты. Первые два округляют к ближайшему значению (при равном расстоянии — к чётному и при равном расстоянии — от нуля); остальные называются направленными округлениями: к нулю, к плюс бесконечности и к минус бесконечности. Эти правила проще всего понять на примере десятичной системы:

Направленные округления совсем простые. В этой статье мы подробно разберём правило «к ближайшему, при равном расстоянии — к чётному». Это режим по умолчанию для двоичной плавающей точки и рекомендуемый режим по умолчанию для десятичной. И понять его обычно не так легко, как направленные округления.
Ниже используются неотрицательные числа. При округлении отрицательного числа к ближайшему значению округлите его модуль, затем верните знак; округление модуля вверх делает результат более отрицательным.
Общее правило округления двоичных дробей до n-го знака предписывает посмотреть на цифру, следующую за n-м знаком. Если это 0, число всегда округляется вниз. Если же цифра равна 1 и хотя бы одна из последующих цифр тоже 1, число округляется вверх. Если же все последующие цифры — 0, то нужно применить правило выбора при равном расстоянии, и обычно это «при равном расстоянии — к чётному». Оно гласит, что округлять следует к числу, у которого на n-м знаке стоит 0.
Чтобы увидеть эти правила в действии, округлим несколько чисел до 2 знаков после двоичной запятой:
- 0.11001 — округляется вниз до 0.11, потому что цифра на 3-м знаке — 0
- 0.11101 — округляется вверх до 1.00, потому что цифра на 3-м знаке — 1 и дальше есть цифры 1 (5-й знак)
- 0.11100 — применяем правило выбора при равном расстоянии «к чётному» и округляем вверх, потому что цифра на 3-м знаке — 1, а все последующие цифры — 0.
Возможно, вам, как и мне, с первого взгляда неясно, откуда берутся эти правила. Похоже, ни в одной прочитанной мной статье об округлении эти правила не объясняются достаточно подробно и с наглядными иллюстрациями — а именно этого, на мой взгляд, и не хватает для ясного объяснения.
Варианты округления
Когда мы округляем число, приходится выбирать один из двух возможных вариантов: округлить вниз до ближайшего числа, которое меньше исходного, либо округлить вверх до ближайшего большего. У этих двух чисел не должно быть больше знаков после запятой, чем у результата округления.
Чтобы показать, как их найти, округлим десятичное число 0.42385 до 2 знаков. К каким числам его можно округлить? Это легко увидеть, если поместить его на числовую прямую вместе с дробями, имеющими два знака:

Диаграмма показывает, что исходное число попадает между 0.42 и 0.43. Так как 0.42385 = 0.42 + 0.00385, а меньшее число — 0.42, можно заключить, что для поиска меньшего числа достаточно отбросить дробную часть после второго знака. А чтобы найти большее число, можно взять уже найденное меньшее и увеличить его на один ULP, прибавив 1 к цифре последнего знака. Или, если посмотреть иначе, к меньшему числу нужно прибавить 0.01 — это 1, умноженная на 10 в степени минус 2, потому что мы округляем до 2 знаков. Если бы мы округляли до 3 знаков, то взяли бы меньшее число для 3 знаков и прибавили 1, умноженную на 10 в степени минус 3.
Обобщим это правило поиска вариантов округления:
Для неотрицательной дроби при округлении до n разрядов в системе с основанием b отбросьте цифры после n-го разряда, чтобы получить нижний вариант. Верхний на одну единицу последнего разряда (ULP) больше: прибавьте b⁻ⁿ. Если число уже точно представимо с этой точностью, оставьте его без изменений.
Теперь найдём варианты округления для двоичного числа 0.11011. Снова округляем до 2 знаков. Следуя той же схеме, что и для десятичных дробей, получаем 0.11 в качестве меньшего числа, поскольку 0.11011 = 0.11 + 0.00011. Затем находим большее число, прибавив 0.01 — это 1, умноженная на 2 в степени минус 2, потому что система двоичная, то есть основание равно 2, а округляем мы до 2 знаков. Результат этого вычисления — 0.11+0.01=1.00.
Поиск наименьшего расстояния
Правило округления «к ближайшему» говорит, что округлять нужно к тому числу, разница с которым минимальна. На числовой прямой это означает, что минимально расстояние между исходным числом и одним из двух возможных результатов округления. Поэтому, если поместить исходное число и оба варианта на числовую прямую и обозначить расстояния через x, нужно определить, какой x меньше:

Сделать это вычитанием совсем несложно:
Здесь ясно видно, что x1 меньше x2, значит округлять нужно до 0.11. Но что, если бы мы захотели округлить бесконечную двоичную дробь? В этом случае вычитание перестало бы быть такой простой операцией. Как оказывается, есть куда более лёгкий подход.
Сравнение с серединой
Другой, более простой подход — определить, больше исходное число или меньше середины между двумя вариантами округления. Посмотрим на диаграмму:

Число выше середины ближе к верхнему варианту, а ниже — к нижнему. В самой середине расстояния равны, и нужно правило выбора при равном расстоянии.
Как же найти эту середину между двумя вариантами? Мы знаем, что расстояние между 0.11 и 1.00 равно 0.01. Значит, достаточно найти половину от 0.01 и прибавить её к меньшему числу. Половина — это 0.001, и в сумме с меньшим числом она даёт 0.111 = 0.11 + 0.001. По сути, это равносильно тому, чтобы просто дописать 1 в конец меньшего числа. Вот диаграмма с нужными числами на числовой прямой:

Теперь остаётся лишь сравнить исходное число с серединой:
Побитовое лексикографическое сравнение показывает, что 0.11011 меньше середины. Значит, округляем вниз до 0.11 — тот же результат, что мы получили, вычисляя расстояния.
Округление к чётному при равном расстоянии
Иногда расстояния от исходного числа до обоих вариантов округления равны, поэтому одного правила «к ближайшему» недостаточно для выбора. Допустим, мы округляем число 0.11011 до 4 знаков после двоичной запятой. Мы знаем, что нужно найти два варианта округления и середину для сравнения. Чтобы найти меньшее число, просто отбрасываем оставшиеся цифры после 4-го знака, и получаем 0.1101. Теперь находим середину, дописав 1 в конец меньшего числа, — получаем 0.11011. И наконец, находим большее число, прибавив 1 ULP к меньшему: 0.1101+0.0001 = 0.1110.
Итак, у нас есть:
- исходное число — 0.11011
- число для округления вниз — 0.1101
- число для округления вверх — 0.1110
- середина между вариантами округления — 0.11011.
Исходное число равно середине, поэтому нужно правило выбора при равном расстоянии. «Чётность» здесь относится к целому, образованному сохранёнными битами: последний сохранённый бит должен быть 0. Из чисел 0.1101 и 0.1110 этому условию удовлетворяет второе, поэтому округляем вверх до 0.1110.
Выводим общие правила округления
Нужно ли сравнивать все цифры? Исходное число и середина имеют одинаковые первые n дробных разрядов. Далее у середины стоит 1 в разряде n+1, а за ней — нули, поэтому сравнение начинается с n+1.
Итак, общие правила округления, приведённые в начале статьи, вытекают из сравнения исходного числа с серединой между двумя вариантами округления. Повторю эти правила ещё раз:
Общее правило округления до n-го знака предписывает посмотреть на цифру, следующую за n-м знаком. Если это 0, число всегда округляется вниз. Если же цифра равна 1 и хотя бы одна из последующих цифр тоже 1, число округляется вверх. Если же все последующие цифры — 0, то нужно применить правило выбора при равном расстоянии, и обычно это «при равном расстоянии — к чётному». Оно гласит, что округлять следует к числу, у которого на n-м знаке стоит 0.