Типы данных с плавающей запятой в Стандарте IEEE-754

Рубрика:  Арифметика с плавающей запятойIEEE-754Основы

Показаны типы данных, которые поддерживает Стандарт. Подробно рассмотрен тип binary32.

Формат IEEE-754 предоставляет возможность уместить число с плавающей запятой с определённой точностью в полях размером от 16 до 128 бит. Опишем эти типы данных:

  • binary16 (половинная точность, half precision),
  • binary32 (одинарная точность, single precision),
  • binary64 (двойная точность, double precision) и
  • binary128 (учетверённая точность, quadruple precision).

Есть также упоминание о формате вроде binary256 (увосмерённая точность, octuple precision) и краткие рекомендации по форматам с расширенной точностью (extended precision formats). Например, формат с расширенной точностью размером 80 бит используется в FPU (x87), но в настоящее время задействуется крайне редко. Ряд компиляторов (например, Visual C++ последних версий) даже не поддерживает подобный тип данных, который обычно называется «long double». Его использование крайне не рекомендуется. Ниже представлена таблица значений $p$, $E$, $e_{min}$ и $e_{max}$ для указанных форматов.

Цифры в названии формата показывают число бит, используемое для двоичного кодирования чисел, в нём представленных.

Binary32

Давайте начнём изучение IEEE-754 с чисел одинарной точности binary32. Этот тип данных поддерживается многими языками, в том числе Си и Си++ (тип данных «float»), так что мы сможем сразу проверить наши знания на практике. В формате binary32 под экспоненту выделено 8 бит (E=8), а под дробную часть мантиссы — 23 (p=24), ещё один бит остаётся для знака.

Итак, пронумеруем биты числа от 0 до 31, а всё пространство из 32 битов разделим на три секции так, как указано на картинке:

Первое поле, синего цвета, занимающее один бит с номером 31, отвечает за знак числа. Второе, зелёное, поле, занимающее биты с 23-го по 30-й, относится к экспоненте. Как мы помним из логики Стандарта IEEE-754, в этом поле сохраняется смещённая экспонента $e_b=e+\mathrm{bias}$, а смещение $\mathrm{bias}$ равно $127$. Последнее поле, красное, занимающее 23 бита от 0-го до 22-го отвечает за мантиссу, точнее, за её дробную часть, потому как старший значащий бит (бит целой части) мы не храним явно.

Рассмотрим пример. Число 3,14 запишется в виде $1{,}100\,100\,011\,110\,101\,110\,000\,11_{(2)}×2^1$ (мы взяли только 23 цифры после запятой, правильно округлив значение, больше цифр нам не нужно). Наши 23 цифры дробной части — это 10010001111010111000011. Значение смещённой экспоненты будет равно $e_b=1+127=128=10000000_{(2)}$. Таким образом, в двоичном коде с плавающей запятой число 3,14 примет вид:

0 10000000 10010001111010111000011

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

$$\displaylines{ \biggl(1 + \frac1{2^1} + \frac1{2^4} + \frac1{2^8}+ \frac1{2^9}+ \frac1{2^{10}}+ \frac1{2^{11}}+ \frac1{2^{13}}+ \frac1{2^{15}}+ {}\cr {} + \frac1{2^{16}}+ \frac1{2^{17}}+ \frac1{2^{22}}+ \frac1{2^{23}}\biggr)\cdot2^1 = 3{,}140\,000\,104\,904\,174\,804\,687\,5.}$$

Если вы не вполне понимаете, откуда в знаменателях эти степени двойки, то поглядите на те позиции мантиссы, на которых стоят единицы (слева направо, начиная отсчёт от единицы). Единицы стоят в первой позиции, затем в четвёртой, затем в восьмой и так далее до позиций 22 и 23. Неявная единица — это как будто 1/20.

Итак, наше число 3,14 не может быть точно представлено с использованием 23 битов после десятичной запятой, поэтому оно округляется до ближайшего числа, представимого в таком формате. Эта особенность плавающей арифметики должна совершенно жёстко закрепиться в вашей памяти. В прикладных расчётах вообще редко встречаются числа, точно представимые в двоичной арифметике с плавающей точкой, всегда есть некоторая погрешность. В нашем примере точность составляет не меньше 10—6 (разница между исходным и приближённым числами не превышает одной миллионной).

Давайте потренируемся. Возьмём число 25. В двоичном коде оно будет иметь вид 11001. В нормализованной научной нотации: 1,1001×24. Таким образом, смещённая экспонента равна $e_b=4+127=131=10000011_{(2)}$, мантисса (когда мы уберём старший бит и дополним последовательность нулями до 23-х бит) равна 10010000000000000000000. Таким образом, число 25 в формате binary32 примет вид:

0 10000011 10010000000000000000000

Другой пример, число −0,3515625. В нормализованной научной нотации примет вид −1,01101×2−2. Смещённая экспонента $e_b=-2+127=125=01111101_{(2)}$. Мантисса равна 01101000000000000000000, а поскольку число отрицательное, знаковый бит будет единичным. В итоге, получим запись:

1 01111101 01101000000000000000000

Интересен также пример конвертирования числа 1. Его экспонента равна 0, поэтому смещённая экспонента примет вид $e_b=127=01111111_{(2)}$, а мантисса будет состоять из одних нулей:

0 01111111 00000000000000000000000

Очень полезно владеть навыком чтения шестнадцатеричных записей чисел с плавающей запятой. Поэтому прошу читателя самостоятельно перевести три предыдущих примера в шестнадцатеричный код:

  • 0 10000011 10010000000000000000000 = 0x41C80000 = 25,0
  • 1 01111101 01101000000000000000000 = 0xBEB40000 = −0,3515625
  • 0 01111111 00000000000000000000000 = 0x3F800000 = 1.0

В будущем мы весьма часто будем иметь дело именно с такой записью, как с наиболее экономной из удобных для человека.

Попробуем выполнить обратную операцию. Пусть в формате binary32 нам дано число 0x449A4000, требуется перевести его в привычную нам десятичную запись. Разбиваем его на три битовых поля:

0 10001001 00110100100000000000000 = 0x449A4000

Смещённая экспонента равна $e_b=137$, то есть порядок числа e=10. Таким образом, всё число имеет значение $$ \bigl( 1+2^{-3}+2^{-4}+2^{-6}+2^{-9} \bigr) \times 2^{10} = 1\,234. $$

Все три приведённых выше примера конвертирования из десятичной записи в формат binary32 обладают одной особенностью, благодаря которой мы так легко справились с конвертированием. Все эти числа точно представимы в формате binary32, а потому у нас и не возникло проблем с округлением. Когда число не может быть точно представлено в заданном формате, оно должно округляться.

Как уже было сказано, стандарт IEEE-754 предоставляет 4 способа округления: к нулю (towards zero), к минус бесконечности (towards minus infinity), к плюс бесконечности (towards plus infinity) и к ближайшему целому, либо к чётному (half to even).

Рассмотрим число 1+2−23. В научной нотации оно будет записано как $1{,}000\,000\,000\,000\,000\,000\,000\,01 \times 2^0$. Это число точно представимо в указанном формате, поэтому не требует округлений. Размера мантиссы хватает как раз на то, чтобы уместить в себя 23-й единичный бит дробной части:

0 01111111 00000000000000000000001

Теперь возьмём 1+2−23+2−24. $$\require{color}1{,}000\,000\,000\,000\,000\,000\,000\,01\colorbox{gray}{1} \times 2^0.$$

Нам не хватает одного бита в поле мантиссы, чтобы уместить последний 24-й бит дробной части. Следовательно, требуется округление. Но в какую сторону? Правило half to even требует в случае неоднозначности округлять в сторону чётной мантиссы. То есть получаем 1+2−23+2−24≈1+2−22:

0 01111111 00000000000000000000010

Теперь возьмём 1+2−23+2−25: $$\require{color}1{,}000\,000\,000\,000\,000\,000\,000\,01\colorbox{gray}{0}1 \times 2^0.$$ Здесь мы не видим спорного случая, поэтому однозначно округляется книзу 1+2−23+2−25≈1+2−23.

0 01111111 00000000000000000000001

Самое маленькое число, которое можно представить в нормализованной научной нотации, это число $1{,}000\,000\,000\,000\,000\,000\,000\,00 \times 2^{-126} = 2^{-126}\approx1{,}18\times 10^{-38}$. У него минимально возможная экспонента −126 и в двоичном коде оно записывается как

0 00000001 00000000000000000000000 = 0x00800000

Максимальное число имеет максимально возможную экспоненту 127 и имеет вид $1{,}111\,111\,111\,111\,111\,111\,111\,11 \times 2^{127} = (2-2^{-23})\times 2^{127}\approx3{,}4\times 10^{38}$.

В двоичном коде это будет выглядеть как

0 11111110 11111111111111111111111 = 0x7F7FFFFF

Если значение смещённой экспоненты равно нулю, то мы имеем дело с денормализованными числами. Значение экспоненты полагается равным e=-126. То есть денормализованные числа имеют вид 0,xxx…×2−126. Получается, что самое маленькое число, представимое в формате binary32 имеет вид $$0{,}000\,000\,000\,000\,000\,000\,000\,01 \times 2^{-126} = 2^{-126-23}=2^{-149}\approx1{,}4\times10^{-45}.$$

В двоичном коде:

0 00000000 00000000000000000000001 = 0x00000001

Если $e_b = 255$, то есть поле экспоненты заполняют только единичные биты, мы имеем дело с бесконечностями или NaN'ами Поле знака, соответственно, отвечает за знак бесконечности и не имеет смысла для NaN.

  • 0 11111111 00000000000000000000000 = 0x7F800000 = +oo
  • 1 11111111 00000000000000000000000 = 0xFF800000 = -oo

«Не число» NaN кодируется всеми единичками в поле экспоненты и ненулевой мантиссой:

  • 0 11111111 10000000000000000000000 = 0x7FС00000 = NaN
  • 1 11111111 00000000000000000000010 = 0xFF800002 = NaN

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

#include <cstdio>

using namespace std;

typedef unsigned int u32;
typedef float fp32;

void output (fp32 &a) {
  printf ("%08X (%.10g)\n", *(u32*)&a, a);
}

int main() {
  fp32 a=1.0f, b=-2.0f, c=0.1f, d=0.333333333f, 
  p_inf, n_inf, nan1, nan2, max, min, dmin, p_zero, n_zero;
  
  max = 3.402823466e38f;    // Max value (approximately).
  min = 1.175494351e-38f;   // Min normal value (approximately).
  dmin = 1.401298464e-45f;  // Min denormal value (approximately).

  p_inf = max*2;         // Plus infinity (max float * 2).
  n_inf = -p_inf;        // Minus infinity.  
  nan1 = p_inf + n_inf;  // NaN as +oo + (-oo).
  nan2 = p_inf / p_inf;  // NaN as +oo / +oo.
  p_zero = 1.0f/p_inf;    // +0 = 1/+oo.
  n_zero = 1.0f/n_inf;    // -0 = 1/-oo.
  
  output (a);
  output (b);
  output (c);
  output (d);
  output (max);
  output (min);
  output (dmin);
  output (p_inf);
  output (n_inf);
  output (p_zero);
  output (n_zero);
  output (nan1);
  output (nan2);
  
  return 0;
}

Например, на VC++ 2015 вывод этой программы будет таким:

3F800000 (1)
C0000000 (-2)
3DCCCCCD (0.1000000015)
3EAAAAAB (0.3333333433)
7F7FFFFF (3.402823466e+38)
00800000 (1.175494351e-38)
00000001 (1.401298464e-45)
7F800000 (inf)
FF800000 (-inf)
00000000 (0)
80000000 (-0)
FFC00000 (-nan(ind))
FFC00000 (-nan(ind))

Обратите, пожалуйста, внимание, что некоторые значение отражены не точно. Скажем, число 0,1(10) не может быть точно представлено в двоичной системе счисления. В двоичной научной нотации оно будет выглядеть как 1,(1001)×2−4. Таким образом, ближайшее число, точно представимое в формате binary32, будет значиться как (округление произошло вверх) $1{,}100\,110\,011\,001\,100\,110\,011\,01 \times 2^{-4} = 0{,}100\,000\,001\,490\,116\,119\,384\,765\,625$

То есть число 0,1 удалось в этом формате закодировать с точностью не меньше 10−8. То же самое можно сказать о числе 1/3. В двоичной научной нотации оно будет записано как 1,(01)×2−2, а значит ближайшее точно представимое значение будет (после округления вверх) $1{,}010\,101\,010\,101\,010\,101\,010\,11 \times 2^{-2} = 0{,}333\,333\,343\,267\,440\,795\,898\,437\,5$.

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

Взглянув на таблицу, можно сделать вывод о том, что формат binary32 «держит» около 7 значащих цифр. Однако для денормализованных чисел эта точность может быть ниже. С каждой арифметической операцией точность может падать, причём иногда очень существенно.