Числа №8. Битовый формат
В этой заметке мы рассмотрим, как числа с плавающей запятой хранятся в памяти. Напомню, что в прошлый раз мы говорили о нормализации, и это был второй этап (после приведения десятичной дроби к двоичной).
Нормализованную дробь нужно хранить в битовом контейнере и при этом выдержать несколько требований. Запись должна быть плотной, чтобы вместить больше информации. Борьба идет за каждый бит. В идеале запись должна упростить некоторые операции, например, чтобы мы проверяли только конкретные биты, а не все число.
Еще запись должна быть монотонной: накрутка битов как в целом числе должна монотонно увеличивать вещественное число.
Давайте запишем несколько нормализованных дробей:
1.110011101 * 2^3
-1.010101 * 2^10
1.001 * 2^6
-1.11101 * 2^100
Что между ними общего? Все их можно выразить тремя показателями:
- был ли спереди минус;
- степень двойки;
- биты дробной части.
Возьмем наше любимое число -42.515625. В нормализованном виде оно равно:
-1.01010100001 * 2^5
Тройка его параметров выглядит так:
- 1 (впереди минус);
- 5 (степень);
01010100001(биты).
Вопрос: почему не учитывается целая часть? Та самая единица перед
разделителем. Ответ – у нормализованной дроби целая часть всегда равна единице,
поэтому она не хранится, а подразумевается. Имея тройку (1, 5, 01010100001),
получим исходное число:
-1.01010100001 * 2^5
Далее тройка записывается в 32 бита (4 байта). Старший бит хранит знак, еще восемь бит – степень. На дробную часть остается 23 бита, она называется мантиссой. И напомню – целая часть дроби, равная единице, подразумевается.
Со степенью есть одна тонкость: она записывается со смещением 127 (половина размерности). Смысл в том, чтобы записать знаковое число как беззнаковое. Это необходимо, чтобы обеспечить монотонное возрастание числа при накрутке битов. Так, к степени 5 будет прибавлено 127 и получится 132. В двоичном виде это 10000100.
Итак, в битовом виде число -42.515625 равно:
1 10000100 01010100001000000000000
Пробелы разделяют смысловые части: знак, экспоненту с выравниванием, мантиссу. Битов 32, и они помещаются в 4 байта. Как с ними работать, рассмотрим позже.
Float определяет особые числа. Прежде всего это ноль, когда все биты нулевые. Особенность нуля в том, что его нельзя нормализовать. Помните, мы двигали разделитель, пока он не окажется за первым единичным битом? С нулем это невозможно – такого бита нет.
Еще одно интересное число – минус ноль:
1 00000000 00000000000000000000000
Минус ноль полезен в инженерных расчетах, например при сходимости рядов. Есть ряды, которые стремятся к нулю, но попеременно слева и справа. Минус ноль означает, что дальнейшие вычисления дают ноль и при этом мы были слева от нуля.
Другие интересные случаи:
- экспонента 11111111, мантисса = 0 -> бесконечность (может быть отрицательной за счет первого бита)
- экспонента 11111111, мантисса не равна 0 -> NaN
- экспонента 0000000, мантисса не равна 0 -> субнормальные числа.
Субнормальные числа выровнены так, что экспонента равна нулю. Они поддерживаются процессорами, однако требуют внутренних преобразований.
Особые числа нельзя получить в лоб: чаще всего для этого возводят специальные
флаги процессора, чтобы при NaN не возбуждалось исключение. Ну или у вас
JavaScript.
В следующей заметке я планирую рассмотреть, как процессор складывает два числа с плавающей запятой.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter