§ Описание

Вот уже достаточно давно я пытался написать и осознать то, как работает float point, и вот попробую рассказать хотя бы так, чтобы было хоть как-то понятно. С другой стороны, сложные темы невозможно сделать простыми, если нет понимания базовых вещей.
В компьютере числа записываются в весьма ограниченном представлении и в особом виде. Существуют разные типы чисел, такие как целочисленные знаковые или беззнаковые, а также есть класс чисел с фиксированной запятой и плавающей. Фиксированная запятая, как можно догадаться, никуда не двигается, в отличии от плавающей и поэтому с ней довольно просто работать, но и точность и диапазон такого числа намного ниже, чем у плавающих.
Что из себя представляет плавающая и фиксированная запятая? Приведу пример фиксированной запятой в десятичном представлении:
 100.00
 092.30
 001.12
 000.42
-230.99
Здесь ясно видно, что на целую часть выделено 3 знака, а на дробную два, и потому точность ограничена только 2 знаками после запятой. Конечно же, процессоры не работают с десятичными цифрами, поэтому я показал просто для наглядности все это. Плавающая запятая имеет другой смысл. Опять же, покажу на примере десятичных цифр.
123 = 1.23 * 10^2
562.3 = 5.623 * 10^2
0.415 = 4.15 * 10^{-1}
7.41 = 7.41 * 10^0
На примере отлично видно, что у чисел с плавающей запятой всегда будет один знак в целой части, а также степень, в которую надо возвести подобное число, чтобы получить исходное. В процессорах, как я уже говорил ранее, нет такого понятия как десятичное число, там используются числа двоичные, то есть, будет либо 0, либо 1.

§ Двоичное представление дробных чисел

Если с двоичным представлением целых чисел можно понять, то как представить дробные? Я не буду объяснять, как представляются двоичные целые, тут и так все просто, например 1 = 1, 10 = 2, 11 = 3, 100 = 4 и так далее. А вот дробные? Лучше показать примеры.
0.1  = 0.5
0.01 = 0.25
0.11 = 0.75
Так представляются дробные. Но почему так? Есть две операции, которые называются сдвиг. Например, если взять двоичное число 1110 (это десятичное 14), и сдвинуть его на 1 бит вправо, то мы получим число 111 (7). Сдвиг направо делит число на 2, то есть, было 14, стало 7.
Теперь представим, что мы хотим сдвинуть направо целое число 1, то что получится? Получится так: 0.1, он окажется по ту сторону запятой. Если мы поделим 1 на 2, то это будет число 0.5 в десятичной системе, а в двоичной это будет 0.1. Вот по сути и всё.
Число 16-битное (половинной точности) с плавающей запятой представляется так:
15   | 14 ... 10  | 9 ... 0
Знак | Экспонента | Мантисса
В математическом плане число можно представить в виде экспоненциальной записи:
s 2^p k
Здесь k = 1.mmmm (нормализованное представление) или 0.mmmm (денормализованное). В данном случае s - это знак, либо может быть -1, либо +1. В двоичном виде за +1 принимается значение s=0, а за -1 значение s=1.
Есть еще одна интересная особенность. Экспонента во float задана немного не так. Например, если она равна 15, то считается, что это 0-я степень. Если p=1, то это степень 2^{-14} .
Пример 1.
Возьмем простой пример 0 01111 0000000000 = 1.0
Почему получится 1? Поскольку тут p=15, то сначала вычитается 15, получается 0, это степень числа. Как мы видим, в мантиссе одни нули, но не все так просто. Поскольку число является нормализованным, то в целой части этого числа всегда будет находится 1 и ничего больше. Поскольку это так, то единицу называют "скрытой". Если число денормализованное, то эту единицу заменяют на 0. Поэтому в данном случае будет:
1 * 2^0 * 1.0000000000 = 1.0
Пример 2.
Рассмотрим 0 10001 0110000000 = 5.5
Разберемся, как так вышло. Для начала, проверим экспоненту, которая равна p=17, а это значит, что степень будет 2^2, или умножаем мантиссу на 4. В целом, мантисса же представляет из себя следующее число 1.0110000000 (скрытая единица в начале). Делаем два сдвига влево:
1.0110000000 => 1.375
101.10000000 => 5.5
Вот так работает плавающая точка. Двигать точку можно как влево, если порядок больше чем 15, так и вправо, если порядок меньше 15.

§ Денормализованное число

Такие числа предназначены для расширения диапазона возможных значений. От нормализованных чисел их отличает только то, что в мантиссе в целой ее части находится на скрытая 1, а скрытый 0. Денормализованное число возникает тогда, когда p=0, например: 0 00000 0101011110. Как видно, здесь показатель равен 0.
Несмотря на то, что p=0, это не значит, что степень итоговая будет равна 2^{-15} , нет, для денормализованных чисел степень так и остается 2^{-14} , но, как и говорил, убирается 1, так что вот такое число 0 00000 0101011110 будет выглядеть так:
2-14 0.0101011110 = 2^{-14} 0.342 = 0.0000209
К денормализованным числам также относится и 0. Причем, любопытно, что 0 может быть положительным и отрицательным, из-за того, что у float есть знак.
Вот например, как происходит переход между нормализованными и денормализованными:
0 00001 0000000000 = 2^(-14) 1.0000000000
0 00000 1000000000 = 2^(-14) 0.1000000000
0 00000 0000000001 = 2^(-14) 0.0000000001
То есть, даже учитывая, что степень не поменялась, число уменьшилось вдвое за счет того, что скрытая единица была убрана. В третьем случае получается самое наименьшее из возможных чисел, а именно 2^{-24} что приблизительно равно 10^{-8} 5.96 = 0.0000000596 .

§ Знак бесконечности и NaN

Для символа бесконечности и так называемого не-числа (Not a Number == NaN) есть отдельный случай.
  • Если p=31 (все единицы в экспоненте) и в мантиссе все нули, то это либо +inf, либо -inf (зависит от числа)
  • Если также, p=31, но в мантиссе не нуль, то тогда это число рассматривается как NaN
Тут может быть, на самом деле, большой класс чисел, поскольку аж 10 бит могут представлять из себя указатель на то, какой специфическое число используется, но математические сопроцессоры таким не занимаются, так что там NaN. Считается ошибкой вычислений. К примеру, не-число появляется при делении на 0, умножении 0 на бесконечность, и тому подобные случаи.