October 12, 2022

Почему у программирования поплыла запятая?

Числа с плавающей запятой в англоязычной литературе называются числа с плавающей точкой (от англ. floating point). Такое различие связано с тем, что в русскоязычной литературе принято отделять дробную часть числа запятой, а в европейской и американской - точкой.

Хранение чисел с плавающей запятой работает по стандарту IEEE 754 (1985 г). Для работы с числами с плавающей запятой на аппаратурном уровне к обычному процессору добавляют математический сопроцессор (FPU, floating point unit).

Типы с плавающей запятой

Рисунок демонстрирует, как распределяются биты в числах с плавающей запятой разных разрядностей, где

  • S - Sign (знак);
  • E - Exponent (8(11) разрядов поля порядка, экспонента);
  • M - Mantissa (23(52) бита мантиссы, дробная часть числа).

Также на рисунке показана так называемая, мнимая единица, она всегда есть в самом старшем разряде мантиссы, поэтому её всегда подразумевают, но в явном виде не хранят, экономя один бит информации.

Если попытаться уложить весь стандарт в два предложения, то получится примерно следующее: получить число в соответствующих разрядностях возможно по формулам:

Формулы расчёта значения числа с плавающей запятой

Например: +0,5 = 2 в -1й степени поэтому, число будет записано как

  0_01111110_00000000000000000000000,

то есть знак равен 0, мантисса равна 0, порядок равен 127 - 1 = 126, чтобы получить следующие результаты вычислений:

  • -1 в нулевой степени - положительный знак;
  • умножить на порядок 2 в степени 126-127, то есть в -1й = 0,5;
  • и умножить на мантиссу 1 + 0.

То есть, 1 * 0,5 * (1 + 0) = 0,5. Отсюда становится очевидно, что чем сложнее мантисса и чем меньше порядок, тем более точные и интересные числа мы можем получить.

А запятая-то куда поплыла?

Возьмём для примера число -0,15625, чтобы понять как его записывать, откинем знак, это будет единица в разряде, отвечающем за знак, и посчитаем мантиссу с порядком. Представим число как положительное и будем от него последовательно отнимать числа, являющиеся отрицательными степенями двойки, чтобы получить максимально близкое к нулю значение.

Значения степеней двойки

Очевидно, что -1 и -2 степени отнять не получится, поскольку мы явно уходим за границу нуля, а вот -3 прекрасно отнимается, значит порядок будет 127 - 3 = 124, осталось понять, что получится в мантиссе. Видим, что оставшееся после первого вычитания (0,15625 - 0,125) число - это 2 в -5й степени. Значит в мантиссе пишем 01 и остальные нули, то есть слева направо указываем, какие степени после -3 будут нужны, а именно, -4 не нужна, ставим 0, а -5 нужна, ставим 1. Довольно очевидно, что при подсчёте других чисел нам так не повезёт, поэтому точность представления может страдать, мы будем отнимать степени двойки пока не получим 0 или пока не закончится мантисса.

Отсюда и "уплывание" точности, отсюда же и разделение на float/double, чем больше битов есть для хранения значения, тем более высокую точность можно записать.

Для полноты картины, приведу пару математических выкладок, по только что описанному алгоритму

Алгоритм вычисления значений с плавающей запятой

и по формуле, представленной в самом начале

Формула вычисления значения с плавающей запятой

чтобы можно было самостоятельно посчитать на калькуляторе и убедиться в тождественности выражений. Так, число с плавающей запятой возможно посчитать двумя способами: по приведённой формуле, или последовательно складывая разряды мантиссы умноженные на двойку в степени порядка, уменьшая порядок на каждом шагу.

Может, есть какие-то особенности?

Конечно, куда же без них. Посмотрим на диапазон возможных значений чисел с плавающей запятой и отметим некоторые особенности:

Значения чисел с плавающей запятой
  • возможен как положительный, так и отрицательный ноль (в целых числах ноль всегда положительный);
  • есть огромная зона, отмеченная на рисунке, которая являет собой непредставимые числа, слишком большие для хранения внутри такой переменной или настолько маленькие, что мнимая единица в мантиссе отсутствует;
  • в таком числе можно хранить значения положительной и отрицательной бесконечности;
  • при работе с такими числами появляется понятие не-числа, при этом важно помнить, что NaN не равен NaN.