Ieee floating point
Изучение компьютерной арифметики
Рубрика:  Арифметика с плавающей запятой → 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_
Цифры в названии формата показывают число бит, используемое для двоичного кодирования чисел, в нём представленных.
Binary32
Давайте начнём изучение IEEE-754 с чисел одинарной точности binary32. Этот тип данных поддерживается многими языками, в том числе Си и Си++ (тип данных «float»), так что мы сможем сразу проверить наши знания на практике. В формате binary32 под экспоненту выделено 8 бит (E=8), а под дробную часть мантиссы — 23 (p=24), ещё один бит остаётся для знака.
Итак, пронумеруем биты числа от 0 до 31, а всё пространство из 32 битов разделим на три секции так, как указано на картинке:
Первое поле, синего цвета, занимающее один бит с номером 31, отвечает за знак числа. Второе, зелёное, поле, занимающее биты с 23-го по 30-й, относится к экспоненте. Как мы помним из логики Стандарта IEEE-754, в этом поле сохраняется смещённая экспонента $e_b=e+mathrm
Рассмотрим пример. Число 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
И здесь начинается то, с чем в действительности столкнётся каждый, кто будет работать с плавающей арифметикой. Смотрите внимательно! Давайте попробуем перевести наше число обратно в десятичный формат:
Если вы не вполне понимаете, откуда в знаменателях эти степени двойки, то поглядите на те позиции мантиссы, на которых стоят единицы (слева направо, начиная отсчёт от единицы). Единицы стоят в первой позиции, затем в четвёртой, затем в восьмой и так далее до позиций 22 и 23. Неявная единица — это как будто 1/2 0 .
Итак, наше число 3,14 не может быть точно представлено с использованием 23 битов после десятичной запятой, поэтому оно округляется до ближайшего числа, представимого в таком формате. Эта особенность плавающей арифметики должна совершенно жёстко закрепиться в вашей памяти. В прикладных расчётах вообще редко встречаются числа, точно представимые в двоичной арифметике с плавающей точкой, всегда есть некоторая погрешность. В нашем примере точность составляет не меньше 10 —6 (разница между исходным и приближённым числами не превышает одной миллионной).
Давайте потренируемся. Возьмём число 25. В двоичном коде оно будет иметь вид 11001. В нормализованной научной нотации: 1,1001×2 4 . Таким образом, смещённая экспонента равна $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. Его экспонента равна , поэтому смещённая экспонента примет вид $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
Нам не хватает одного бита в поле мантиссы, чтобы уместить последний 24-й бит дробной части. Следовательно, требуется округление. Но в какую сторону? Правило half to even требует в случае неоднозначности округлять в сторону чётной мантиссы. То есть получаем 1+2 −23 +2 −24 ≈1+2 −22 :
0 01111111 00000000000000000000010
Теперь возьмём 1+2 −23 +2 −25 : $$require
0 01111111 00000000000000000000001
Самое маленькое число, которое можно представить в нормализованной научной нотации, это число $1<,>000,000,000,000,000,000,000,00 times 2^ <-126>= 2^<-126>approx1<,>18times 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<,>4times 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<,>4times10^<-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. Она выводит на экран некоторые значения и отображает их шестнадцатеричный код.
Например, на VC++ 2015 вывод этой программы будет таким:
Обратите, пожалуйста, внимание, что некоторые значение отражены не точно. Скажем, число 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 значащих цифр. Однако для денормализованных чисел эта точность может быть ниже. С каждой арифметической операцией точность может падать, причём иногда очень существенно.
Это была заключительная статья данной серии.
Предыдущая: «Компьютерное представление чисел с плавающей запятой»
Содержание Все права защищены © Артём Михайлович Караваев, 2016. При копировании материала прошу указывать ссылку на источник
Числа с плавающей точкой/запятой согласно стандарту IEEE754-2008
Так как перед нами не стоит задача досконального изучения стандартов, перейдём непосредственно к представлению действительных чисел в памяти компьютера на примере одного из типов данных, проверяя при этом отдельные положения небольшими программами.
Во-первых, определимся с терминологией.
Во-вторых, для приводимых примеров будет использоваться язык С. Рассмотрим имеющиеся в нём типы данных.
Если у вас на компьютере больше гигабайта оперативной памяти и вы считаете, что для современных ЭВМ вопрос выбора формата хранения данных при программировании неактуален, напомним, что также существует мир микроконтроллеров, по сути тех же ЭВМ, чьи объёмы памяти на 1-2 порядка меньше. Даже SIM-карту телефона (или банковскую карту с чипом) можно в какой-то мере рассматривать как специфическую ЭВМ, не говоря уже о том, что во многих автомобилях управление устройствами происходит по CAN-шине, работа с которой не обходится без ЭВМ.
Какой тип выбрать?
Перед началом использования типов попробуем понять, что для представления действительных чисел также существует несколько «сущностей» (по аналогии с рис. 2.1 сущность – это некоторая модель представления действительных чисел и соответствующий ей формат записи числа на бумаге или в памяти ЭВМ) (см. рис. 2.2).
По мнению авторов, направление «движения мыслей» наиболее естественно происходит по стрелкам, хотя ничто не мешает вам двигаться и в обратном направлении, даже «перескакивать» через отдельные сущности. Так как за каждой сущностью фактически стоит одна или несколько моделей представления действительных чисел, то высказать однозначно мнение, что какая-то сущность (и как следствие ‒ какой-то формат записи) лучше и что надо использовать только её, нельзя.
«Пробежимся» по графу, изображённому выше на рис. 2.2 от «числа» до его представления в памяти ЭВМ на примере показания термометра (см. табл. 2.6).
После рассмотрения примера выделим общие закономерности преобразований и конкретные механизмы переходов между ячейками таблицы.
Опираясь на основы моделирования (представление чисел – это модель) и глядя в таблицу легко догадаться, что:
- каждая модель имеет свои ограничения;
- достоверность (точность) представления чисел для каждой модели различна;
- в некоторых сущностях числа могут иметь несколько форм записи;
- преобразования чисел из одной сущности в другую могут быть неоднозначными.
- либо заранее преобразовывать все числа к единой форме,
- либо создать специальные алгоритмы распознавания формата числа,
- либо указывать каждый раз его форму записи.
Все три варианта в той или иной мере используются.
Практическая проверка правильности вычислений показала, что предыдущие преобразования верны. Если проводимые вычисления не показались вам сложными и вы считаете, что уже умеете преобразовывать любые числа по формату IEE754, то спешим вас огорчить, рано радуетесь. Если бы мы говорили о формате хранения целых беззнаковых чисел, то можно было бы «остановиться», так как для них используется всего лишь одна формула преобразований. В случае же с действительными числами забежим вперёд и скажем, что таких формул будет 10! В примере выше мы использовали лишь одну, ниже разберём остальные. В этом, наверное, и кроется основная проблема изучения формата хранения чисел, когда обучаемыми рассматриваются лишь некоторые случаи преобразований, а все остальные уходят из рассмотрения.
Понять сказанное проще, проведя рассуждения «от обратного», а именно «от памяти к числу». Так как для хранения данных одной переменной типа float у нас задействовано 32 двоичных разряда, то максимальное число вариантов будет 2 32 , начиная 32 «нулями» и заканчивая всеми «единицами». Записывать числа удобнее в более компактной шестнадцатеричной форме, где начало и конец будут 00 00 00 00 и FF FF FF FF. см. табл. 2.8a.
Рассмотрим подробнее рис. 2.3. Над воображаемой осью координат «x» записаны значения ячеек памяти. Под осью записаны значения чисел, а цветом указаны «форму- лы», по которым можно осуществить преобразования, причём с целью упрощения и исходя из симметрии их число (как и цветов на рис., оттенков при чёрно-белом изда- нии) сокращено до пяти. «Пробежимся» по положительной части оси и разберём их.
«Ноль – он и в Африке ноль». Для представления нуля в памяти используется комбинация нулей во всех 32 разрядах. Согласитесь, что это тривиально и легко запомнить.
Фрагмент памяти ЭВМ, содержащий значения 00 00 00 0016 (следует учитывать, что реальная последовательность хранения зависит от архитектуры, см. стр. 36), будучи интерпретирован как число формата binary32 IEEE754-2008, считается числом «+0».
Следует отметить, что если рассматривать «отрицательную область», то там тоже будет «свой» ноль.
Из рисунков видно, что при шестнадцатеричной записи в памяти отображение «–0» будет как 80 00 00 00 (следует учитывать, что реальная последовательность хранения зависит от архитектуры и может быть обратной, см. стр. 36).
При кодировании целых чисел одним байтом от значения «−0» отказались в пользу ещё одного отрицательного числа, вот почему при 256 вариантах мы пользуемся не симметричным диапазоном [−127, +127], а асимметричным [−128, +127], как и [−32768, +32767] и т. д. Смею предположить, что «+0» и «−0», с точки зрения математики, если не рассматривать пределы справа и слева, есть одно и то же – «0», а в чём различие «+» и «−» – философский вопрос. Однако из факта существования двух нулей технически можно сделать интересные выводы, например для операции сравнения. Если ранее, при использовании целых чисел, из равенства «представления чисел в памяти ЭВМ» следовало и равенство самих чисел, то для типов с плавающей запятой одним сравнением уже не обойтись. Например, при сравнении фрагментов памяти 80 00 00 00 и 00 00 00 00, хранящих числа с плавающей запятой, побитно видно, что они различаются, а значит, можно ошибочно предположить, что числа, представленные данными записями, разные, по факту же мы понимаем, что числа равны.
Замечание, открытый вопрос
Почему комбинация 80 00 00 0016 не была использована для представления какого-нибудь ещё числа, как это было сделано с целыми типами?
Смеем предположить, что с целыми типами, при использовании 8 бит, «выигрыш» оказался существен, плюс несложность добавления числа «на край» отрицательных чисел подсказала простое решение, как использовать это значение. Целые типы с большим числом разрядов просто унаследовали данный подход. Что же касается чисел с плавающей запятой, видимо, даже при разработке стандарта IEEE754 сочли вклад 1/216 для формата Binary16 несущественным, не говоря уже о меньшем вкладе 1/232 для формата binary32 и т. д. Следует отметить, что в «блоке NaN», о котором написано ниже, заложено больше неиспользуемых значений.
Для удобства изложения поменяем порядок изучения формул и рассмотрим сначала нормализованные числа.
Таблица 2.9. Параметры формата «несуществующий binary5»
В придуманном нами пятибитовом формате возможно представить 16 чисел со знаком «+» (см. табл. 2.10), размещение которых на числовой оси представлено на рис.2.6.
. подробнее см. в бумажном издании, либо по запросу на почту.
Tools & Thoughts
This page allows you to convert between the decimal representation of numbers (like «1.02») and the binary format used by all modern CPUs (IEEE 754 floating point).
Update
There has been an update in the way the number is displayed. Previous version would give you the represented value as a possibly rounded decimal number and the same number with the increased precision of a 64-bit double precision float. Now the original number is shown (either as the number that was entered, or as a possibly rounded decimal string) as well as the actual full precision decimal number that the float value is representing. Entering «0.1» is — as always — a nice example to see this behaviour. The difference between both values is shown as well, so you can easier tell the difference between what you entered and what you get in IEEE-754.
This webpage is a tool to understand IEEE-754 floating point numbers. This is the format in which almost all CPUs represent non-integer numbers. As this format is using base-2, there can be surprising differences in what numbers can be represented easily in decimal and which numbers can be represented in IEEE-754. As an example, try «0.1». The conversion is limited to 32-bit single precision numbers, while the IEEE-754-Standard contains formats with increased precision.
Usage:
You can either convert a number by choosing its binary representation in the button-bar, the other fields will be updated immediately. Or you can enter a binary number, a hexnumber or the decimal representation into the corresponding textfield and press return to update the other fields. To make it easier to spot eventual rounding errors, the selected float number is displayed after conversion to double precision.
Special Values:
You can enter the words «Infinity», «-Infinity» or «NaN» to get the corresponding special values for IEEE-754. Please note there are two kinds of zero: +0 and -0.
Conversion:
The value of a IEEE-754 number is computed as:
sign 2 exponent mantissa
The sign is stored in bit 32. The exponent can be computed from bits 24-31 by subtracting 127. The mantissa (also known as significand or fraction) is stored in bits 1-23. An invisible leading bit (i.e. it is not actually stored) with value 1.0 is placed in front, then bit 23 has a value of 1/2, bit 22 has value 1/4 etc. As a result, the mantissa has a value between 1.0 and 2. If the exponent reaches -127 (binary 00000000), the leading 1 is no longer used to enable gradual underflow.
Underflow:
If the exponent has minimum value (all zero), special rules for denormalized values are followed. The exponent value is set to 2 -126 and the «invisible» leading bit for the mantissa is no longer used.
The range of the mantissa is now [0:1).
Note: The converter used to show denormalized exponents as 2 -127 and a denormalized mantissa range [0:2). This is effectively identical to the values above, with a factor of two shifted between exponent and mantissa. However this confused people and was therefore changed (2015-09-26).
Rounding errors:
Not every decimal number can be expressed exactly as a floating point number. This can be seen when entering «0.1» and examining its binary representation which is either slightly smaller or larger, depending on the last bit.
Other representations:
The hex representation is just the integer value of the bitstring printed as hex. Don’t confuse this with true hexadecimal floating point values in the style of 0xab.12ef.
FAQ (Frequently Asked Questions):
Can you send me the source code? I need to convert format x to format y.:
This source code for this converter doesn’t contain any low level conversion routines. The conversion between a floating point number (i.e. a 32 bit area in memory) and the bit representation isn’t actually a conversion, but just a reinterpretation of the same data in memory. This can be easily done with typecasts in C/C++ or with some bitfiddling via java.lang.Float.floatToIntBits in Java. The conversion between a string containing the textual form of a floating point number (e.g. «3.14159», a string of 7 characters) and a 32 bit floating point number is also performed by library routines. If you need to write such a routine yourself, you should have a look at the sourecode of a standard C library (e.g. GNU libc, uclibc or the FreeBSD C library — please have a look at the licenses before copying the code) — be aware, these conversions can be complicated.
Can you add support for 64-bit float/16-bit float/non-IEEE 754 float?.:
This page relies on existing conversion routines, so formats not usually supported in standard libraries cannot be supported with reasonable effort. Double-precision (64-bit) floats would work, but this too is some work to support alongside single precision floats. As the primary purpose of this site is to support people learning about these formats, supporting other formats is not really a priority.
I’ve converted a number to floating point by hand/some other method, and I get a different result. Your converter is wrong!
Possible, but unlikely. The conversion routines are pretty accurate (see above). Until now, checking the results always proved the other conversion less accurate. First, consider what «correct» means in this context — unless the conversion has no rounding error, there are two reasonable results, one slightly smaller the entered value and one slightly bigger. The best result is usually the one closer to the value that was entered, so you should check for that. Please check the actual represented value (second text line) and compare the difference to the expected decimal value while toggling the last bits.
Note: If you find any problems, please report them here.
Числа с плавающей точкой/запятой согласно стандарту IEEE754-2008
Так как перед нами не стоит задача досконального изучения стандартов, перейдём непосредственно к представлению действительных чисел в памяти компьютера на примере одного из типов данных, проверяя при этом отдельные положения небольшими программами.
Во-первых, определимся с терминологией.
Во-вторых, для приводимых примеров будет использоваться язык С. Рассмотрим имеющиеся в нём типы данных.
Если у вас на компьютере больше гигабайта оперативной памяти и вы считаете, что для современных ЭВМ вопрос выбора формата хранения данных при программировании неактуален, напомним, что также существует мир микроконтроллеров, по сути тех же ЭВМ, чьи объёмы памяти на 1-2 порядка меньше. Даже SIM-карту телефона (или банковскую карту с чипом) можно в какой-то мере рассматривать как специфическую ЭВМ, не говоря уже о том, что во многих автомобилях управление устройствами происходит по CAN-шине, работа с которой не обходится без ЭВМ.
Какой тип выбрать?
Перед началом использования типов попробуем понять, что для представления действительных чисел также существует несколько «сущностей» (по аналогии с рис. 2.1 сущность – это некоторая модель представления действительных чисел и соответствующий ей формат записи числа на бумаге или в памяти ЭВМ) (см. рис. 2.2).
По мнению авторов, направление «движения мыслей» наиболее естественно происходит по стрелкам, хотя ничто не мешает вам двигаться и в обратном направлении, даже «перескакивать» через отдельные сущности. Так как за каждой сущностью фактически стоит одна или несколько моделей представления действительных чисел, то высказать однозначно мнение, что какая-то сущность (и как следствие ‒ какой-то формат записи) лучше и что надо использовать только её, нельзя.
«Пробежимся» по графу, изображённому выше на рис. 2.2 от «числа» до его представления в памяти ЭВМ на примере показания термометра (см. табл. 2.6).
После рассмотрения примера выделим общие закономерности преобразований и конкретные механизмы переходов между ячейками таблицы.
Опираясь на основы моделирования (представление чисел – это модель) и глядя в таблицу легко догадаться, что:
- каждая модель имеет свои ограничения;
- достоверность (точность) представления чисел для каждой модели различна;
- в некоторых сущностях числа могут иметь несколько форм записи;
- преобразования чисел из одной сущности в другую могут быть неоднозначными.
- либо заранее преобразовывать все числа к единой форме,
- либо создать специальные алгоритмы распознавания формата числа,
- либо указывать каждый раз его форму записи.
Все три варианта в той или иной мере используются.
Практическая проверка правильности вычислений показала, что предыдущие преобразования верны. Если проводимые вычисления не показались вам сложными и вы считаете, что уже умеете преобразовывать любые числа по формату IEE754, то спешим вас огорчить, рано радуетесь. Если бы мы говорили о формате хранения целых беззнаковых чисел, то можно было бы «остановиться», так как для них используется всего лишь одна формула преобразований. В случае же с действительными числами забежим вперёд и скажем, что таких формул будет 10! В примере выше мы использовали лишь одну, ниже разберём остальные. В этом, наверное, и кроется основная проблема изучения формата хранения чисел, когда обучаемыми рассматриваются лишь некоторые случаи преобразований, а все остальные уходят из рассмотрения.
Понять сказанное проще, проведя рассуждения «от обратного», а именно «от памяти к числу». Так как для хранения данных одной переменной типа float у нас задействовано 32 двоичных разряда, то максимальное число вариантов будет 2 32 , начиная 32 «нулями» и заканчивая всеми «единицами». Записывать числа удобнее в более компактной шестнадцатеричной форме, где начало и конец будут 00 00 00 00 и FF FF FF FF. см. табл. 2.8a.
Рассмотрим подробнее рис. 2.3. Над воображаемой осью координат «x» записаны значения ячеек памяти. Под осью записаны значения чисел, а цветом указаны «форму- лы», по которым можно осуществить преобразования, причём с целью упрощения и исходя из симметрии их число (как и цветов на рис., оттенков при чёрно-белом изда- нии) сокращено до пяти. «Пробежимся» по положительной части оси и разберём их.
«Ноль – он и в Африке ноль». Для представления нуля в памяти используется комбинация нулей во всех 32 разрядах. Согласитесь, что это тривиально и легко запомнить.
Фрагмент памяти ЭВМ, содержащий значения 00 00 00 0016 (следует учитывать, что реальная последовательность хранения зависит от архитектуры, см. стр. 36), будучи интерпретирован как число формата binary32 IEEE754-2008, считается числом «+0».
Следует отметить, что если рассматривать «отрицательную область», то там тоже будет «свой» ноль.
Из рисунков видно, что при шестнадцатеричной записи в памяти отображение «–0» будет как 80 00 00 00 (следует учитывать, что реальная последовательность хранения зависит от архитектуры и может быть обратной, см. стр. 36).
При кодировании целых чисел одним байтом от значения «−0» отказались в пользу ещё одного отрицательного числа, вот почему при 256 вариантах мы пользуемся не симметричным диапазоном [−127, +127], а асимметричным [−128, +127], как и [−32768, +32767] и т. д. Смею предположить, что «+0» и «−0», с точки зрения математики, если не рассматривать пределы справа и слева, есть одно и то же – «0», а в чём различие «+» и «−» – философский вопрос. Однако из факта существования двух нулей технически можно сделать интересные выводы, например для операции сравнения. Если ранее, при использовании целых чисел, из равенства «представления чисел в памяти ЭВМ» следовало и равенство самих чисел, то для типов с плавающей запятой одним сравнением уже не обойтись. Например, при сравнении фрагментов памяти 80 00 00 00 и 00 00 00 00, хранящих числа с плавающей запятой, побитно видно, что они различаются, а значит, можно ошибочно предположить, что числа, представленные данными записями, разные, по факту же мы понимаем, что числа равны.
Замечание, открытый вопрос
Почему комбинация 80 00 00 0016 не была использована для представления какого-нибудь ещё числа, как это было сделано с целыми типами?
Смеем предположить, что с целыми типами, при использовании 8 бит, «выигрыш» оказался существен, плюс несложность добавления числа «на край» отрицательных чисел подсказала простое решение, как использовать это значение. Целые типы с большим числом разрядов просто унаследовали данный подход. Что же касается чисел с плавающей запятой, видимо, даже при разработке стандарта IEEE754 сочли вклад 1/216 для формата Binary16 несущественным, не говоря уже о меньшем вкладе 1/232 для формата binary32 и т. д. Следует отметить, что в «блоке NaN», о котором написано ниже, заложено больше неиспользуемых значений.
Для удобства изложения поменяем порядок изучения формул и рассмотрим сначала нормализованные числа.
Таблица 2.9. Параметры формата «несуществующий binary5»
В придуманном нами пятибитовом формате возможно представить 16 чисел со знаком «+» (см. табл. 2.10), размещение которых на числовой оси представлено на рис.2.6.
. подробнее см. в бумажном издании, либо по запросу на почту.
IEEE floating-point arithmeticВ¶
This chapter describes functions for examining the representation of floating point numbers and controlling the floating point environment of your program. The functions described in this chapter are declared in the header file gsl_ieee_utils.h .
Representation of floating point numbersВ¶
The IEEE Standard for Binary Floating-Point Arithmetic defines binary formats for single and double precision numbers. Each number is composed of three parts: a sign bit (), an exponent (
) and a fraction (
). The numerical value of the combination
is given by the following formula,
The sign bit is either zero or one. The exponent ranges from a minimum value to a maximum value
depending on the precision. The exponent is converted to an unsigned number
, known as the biased exponent, for storage by adding a bias parameter,
The sequence represents the digits of the binary fraction
. The binary digits are stored in normalized form, by adjusting the exponent to give a leading digit of
. Since the leading digit is always 1 for normalized numbers it is assumed implicitly and does not have to be stored. Numbers smaller than
are be stored in denormalized form with a leading zero,
This allows gradual underflow down to for
bits of precision. A zero is encoded with the special exponent of
and infinities with the exponent of
.
The format for single precision numbers uses 32 bits divided in the following way:
The format for double precision numbers uses 64 bits divided in the following way:
It is often useful to be able to investigate the behavior of a calculation at the bit-level and the library provides functions for printing the IEEE representations in a human-readable form.
void gsl_ieee_fprintf_float ( FILE *В stream, const float *В x ) В¶ void gsl_ieee_fprintf_double ( FILE *В stream, const double *В x ) В¶
These functions output a formatted version of the IEEE floating-point number pointed to by x to the stream stream . A pointer is used to pass the number indirectly, to avoid any undesired promotion from float to double . The output takes one of the following forms,
the Not-a-Number symbol
positive or negative infinity
1.fffff. *2^E, -1.fffff. *2^E
a normalized floating point number
0.fffff. *2^E, -0.fffff. *2^E
a denormalized floating point number
positive or negative zero
The output can be used directly in GNU Emacs Calc mode by preceding it with 2# to indicate binary.
void gsl_ieee_printf_float ( const float *В x ) В¶ void gsl_ieee_printf_double ( const double *В x ) В¶
These functions output a formatted version of the IEEE floating-point number pointed to by x to the stream stdout .
The following program demonstrates the use of the functions by printing the single and double precision representations of the fraction . For comparison the representation of the value promoted from single to double precision is also printed.
The binary representation of is
. The output below shows that the IEEE format normalizes this fraction to give a leading digit of 1:
The output also shows that a single-precision number is promoted to double-precision by adding zeros in the binary representation.
Setting up your IEEE environmentВ¶
The IEEE standard defines several modes for controlling the behavior of floating point operations. These modes specify the important properties of computer arithmetic: the direction used for rounding (e.g. whether numbers should be rounded up, down or to the nearest number), the rounding precision and how the program should handle arithmetic exceptions, such as division by zero.
Many of these features can now be controlled via standard functions such as fpsetround() , which should be used whenever they are available. Unfortunately in the past there has been no universal API for controlling their behavior—each system has had its own low-level way of accessing them. To help you write portable programs GSL allows you to specify modes in a platform-independent way using the environment variable GSL_IEEE_MODE . The library then takes care of all the necessary machine-specific initializations for you when you call the function gsl_ieee_env_setup() .
Environment variable which specifies IEEE mode.
This function reads the environment variable GSL_IEEE_MODE and attempts to set up the corresponding specified IEEE modes. The environment variable should be a list of keywords, separated by commas, like this:
where keyword is one of the following mode-names:
If GSL_IEEE_MODE is empty or undefined then the function returns immediately and no attempt is made to change the system’s IEEE mode. When the modes from GSL_IEEE_MODE are turned on the function prints a short message showing the new settings to remind you that the results of the program will be affected.
If the requested modes are not supported by the platform being used then the function calls the error handler and returns an error code of GSL_EUNSUP .
When options are specified using this method, the resulting mode is based on a default setting of the highest available precision (double precision or extended precision, depending on the platform) in round-to-nearest mode, with all exceptions enabled apart from the INEXACT exception. The INEXACT exception is generated whenever rounding occurs, so it must generally be disabled in typical scientific calculations. All other floating-point exceptions are enabled by default, including underflows and the use of denormalized numbers, for safety. They can be disabled with the individual mask- settings or together using mask-all .
The following adjusted combination of modes is convenient for many purposes:
This choice ignores any errors relating to small numbers (either denormalized, or underflowing to zero) but traps overflows, division by zero and invalid operations.
Note that on the x86 series of processors this function sets both the original x87 mode and the newer MXCSR mode, which controls SSE floating-point operations. The SSE floating-point units do not have a precision-control bit, and always work in double-precision. The single-precision and extended-precision keywords have no effect in this case.
To demonstrate the effects of different rounding modes consider the following program which computes , the base of natural logarithms, by summing a rapidly-decreasing series,
Here are the results of running the program in round-to-nearest mode. This is the IEEE default so it isn’t really necessary to specify it here:
After nineteen terms the sum converges to within of the correct value. If we now change the rounding mode to round-down the final result is less accurate:
The result is about below the correct value, an order of magnitude worse than the result obtained in the round-to-nearest mode.
If we change to rounding mode to round-up then the final result is higher than the correct value (when we add each term to the sum the final result is always rounded up, which increases the sum by at least one tick until the added term underflows to zero). To avoid this problem we would need to use a safer converge criterion, such as while (fabs(sum — oldsum) > epsilon) , with a suitably chosen value of epsilon.
Finally we can see the effect of computing the sum using single-precision rounding, in the default round-to-nearest mode. In this case the program thinks it is still using double precision numbers but the CPU rounds the result of each floating point operation to single-precision accuracy. This simulates the effect of writing the program using single-precision float variables instead of double variables. The iteration stops after about half the number of iterations and the final result is much less accurate:
with an error of , which corresponds to single precision accuracy (about 1 part in
). Continuing the iterations further does not decrease the error because all the subsequent results are rounded to the same value.
References and Further ReadingВ¶
The reference for the IEEE standard is,
ANSI/IEEE Std 754-1985, IEEE Standard for Binary Floating-Point Arithmetic.
A more pedagogical introduction to the standard can be found in the following paper,
David Goldberg: What Every Computer Scientist Should Know About Floating-Point Arithmetic. ACM Computing Surveys, Vol.: 23, No.: 1 (March 1991), pages 5–48.
Corrigendum: ACM Computing Surveys, Vol.: 23, No.: 3 (September 1991), page 413. and see also the sections by B. A. Wichmann and Charles B. Dunham in Surveyor’s Forum: “What Every Computer Scientist Should Know About Floating-Point Arithmetic”. ACM Computing Surveys, Vol.: 24, No.: 3 (September 1992), page 319.
A detailed textbook on IEEE arithmetic and its practical use is available from SIAM Press,
Michael L. Overton, Numerical Computing with IEEE Floating Point Arithmetic, SIAM Press, ISBN 0898715717.