Мы много говорили о числах с плавающей запятой. Есть и другой формат – дробные числа с фиксированной запятой. Давайте кратко их рассмотрим.

Об этих числах я упоминал в заметке о первой Playstation. Это эмуляция дробных чисел для архитектур без математического сопроцессора. Главное их преимущество в том, что все операции с ними выполняются как целочисленные.

У фиксированных чисел, как ясно из определения, дробный разделитель не плавает туда-сюда. Например, мы решили, что после запятой хранится два разряда, и так будет всегда. Не нужно нормализовать числа и приводить их к общим экспонентам.

Число с фиксированной запятой хранится как целое, умноженное на некий коэффициент. Для простоты рассмотрим десятичные числа. Будем хранить цены в рублях с двумя десятичными знаками (копейками):

Кофе     80.99
Булочка 115.50

Коэффициент равен 10^-2 (или 0.01), а их целые представления равны:

Кофе     8099
Булочка 11550

Складываются и вычитаются такие числа в одну машинную команду. Найдем сумму покупки:

  1
 8099
11550
-----
19649

Приведем к дробному виду, чтобы были видны копейки:

19649 * 10^-2 = 196.49

Это было сложение, а теперь рассмотрим умножение. Умножать булочку на кофе немного странно, но давайте не думать об этом.

Числа 80.99 и 115.50 можно записать таким образом:

 80.99 =  8099 * 10^-2
115.50 = 11550 * 10^-2

Их произведение:

8099 * 10^-2 * 11550 * 10^-2

Две степени десяти схлопываются в одну: -2 + -2 = -4

8099 * 11550 * 10^-4

Умножение на степень десяти равносильно тому, чтобы сдвинуть разряд влево или вправо. Произведение 8099 * 11550 дает 93543450, и нужно сдвинуть разделитель влево на 4 позиции. Результат:

9354.3450

Однако мы храним только две цифры после запятой. Поэтому хвост 3450 округляется до двух знаков. Цифра 5 округляется вверх, и на конце оказывается 35:

9354.35

Целое (внутреннее) представление становится таким образом 935435.

Вот как устроены числа с фиксированной запятой.

Выше мы рассматрели их в десятичном виде, потому что так удобней. В двоичном виде происходит то же самое. Договариваются, что первые 16 бит хранят целую часть, а остальные 16 – дробную. Сокращенно записывают так: Q16.16. Могут быть и другие варианты: больше бит на целую часть (Q24.8) или наоборот – на дробную (Q8.24). Вместо десятичного сдвига происходит битовый силами процессора.

С такими числами работали игровые консоли вроде Playstation, Sega Saturn и GameBoy. Очевидный их минус в том, что, во-первых, диапазон значений гораздо ниже, чем у float. Во-вторых, нужно следить за переполнением целой части. Чтобы не допустить переполнения, числа приводят к аналогам, где у целой части больше бит. Например, конвертируют Q16.16 в Q24.8, выполняют расчеты, а затем возвращаются к Q16.16.

Раньше числа с фиксированной запятой использовались часто. Например, в Doom для расчета углов; в LaTeX для растеризации шрифтов; в ранних играх Blizzard (Starcraft).

В следующей заметке я хочу рассказать о Старкрафте: какую роль в нем играли числа с фиксированной запятой и почему разработчики не использовали целые.