Справочная страница isnormal () сообщает:
Определяет, является ли данное число с плавающей запятой arg нормальным, т. Е. Не равно нулю, субнормальному, бесконечному или NaN.
Число, равное нулю, бесконечности или NaN, ясно, что это означает. Но это также говорит о субнормальном. Когда число ненормальное?
c++
c++11
floating-point
ieee-754
БЈовић
источник
источник
Ответы:
В стандарте IEEE754 числа с плавающей запятой представлены в двоичной научной нотации x = M × 2 e . Здесь M - мантисса, а e - показатель степени . Математически вы всегда можете выбрать показатель степени так, чтобы 1 ≤ M <2. * Однако, поскольку в компьютерном представлении показатель степени может иметь только конечный диапазон, есть некоторые числа, которые больше нуля, но меньше 1,0 × 2 e мин . Эти числа являются субнормальными или ненормальными .
Практически мантисса хранится без ведущей единицы, поскольку всегда есть ведущая 1, за исключением субнормальных чисел (и нуля). Таким образом, интерпретация такова, что если показатель степени не минимален, существует неявная ведущая единица, а если показатель минимальный, нет и число субнормально.
*) В более общем случае 1 ≤ M < B для любой научной записи с основанием B.
источник
isnomal
,true
если все 8 бит равны нулю, илиfalse
иначе?001010
, и интерпретируется как1.001010
.Основы IEEE 754
Сначала давайте рассмотрим основы организации номеров IEEE 754.
Мы сосредоточимся на одинарной точности (32 бита), но все можно сразу же обобщить на другие точности.
Формат:
Или, если вам нравятся картинки:
Источник .
Знак простой: 0 - положительный, а 1 - отрицательный, конец истории.
Показатель экспоненты составляет 8 бит, поэтому он колеблется от 0 до 255.
Показатель степени называется смещенным, потому что он имеет смещение
-127
, например:0 == special case: zero or subnormal, explained below 1 == 2 ^ -126 ... 125 == 2 ^ -2 126 == 2 ^ -1 127 == 2 ^ 0 128 == 2 ^ 1 129 == 2 ^ 2 ... 254 == 2 ^ 127 255 == special case: infinity and NaN
Соглашение о ведущих битах
(Далее следует вымышленный гипотетический рассказ, не основанный на каких-либо реальных исторических исследованиях.)
При разработке стандарта IEEE 754 инженеры заметили, что все числа, за исключением
0.0
, имеют1
двоичную единицу в качестве первой цифры. Например:25.0 == (binary) 11001 == 1.1001 * 2^4 0.625 == (binary) 0.101 == 1.01 * 2^-1
оба начинаются с этой надоедливой
1.
части.Поэтому было бы расточительным позволять этой цифре занимать один бит точности почти для каждого отдельного числа.
По этой причине они создали «соглашение о начальных битах»:
Но как тогда бороться
0.0
? Что ж, решили создать исключение:0.0
так что байты
00 00 00 00
также представляют0.0
, что выглядит хорошо.Если бы мы учитывали только эти правила, то наименьшее ненулевое число, которое может быть представлено, было бы:
что выглядит примерно так в шестнадцатеричной дроби из-за соглашения о начальных битах:
1.000002 * 2 ^ (-127)
где
.000002
22 нуля с буквой1
на конце.Мы не можем взять
fraction = 0
, иначе было бы это число0.0
.Но потом инженеры, имевшие к тому же острое эстетическое чутье, подумали: разве это не уродливо? Что мы прыгаем прямо
0.0
к чему-то, что даже не является истинной степенью двойки? Не могли бы мы как-нибудь представить еще меньшие числа? (Хорошо, это было немного больше, чем «уродливое»: на самом деле люди получали плохие результаты своих вычислений, см. «Как субнормальные факторы улучшают вычисления» ниже).Субнормальные числа
Инженеры немного почесали затылки и, как обычно, вернулись с другой хорошей идеей. Что, если мы создадим новое правило:
Из этого правила сразу следует, что число такое, что:
по-прежнему
0.0
, что довольно элегантно, поскольку означает, что на одно правило меньше.Так
0.0
на самом деле субнормальный номер в соответствии с нашим определением!Таким образом, с помощью этого нового правила наименьшее ненормальное число будет:
что представляет собой:
1.0 * 2 ^ (-126)
Тогда наибольшее субнормальное число:
что равно:
0.FFFFFE * 2 ^ (-126)
где
.FFFFFE
снова 23 бита, единица справа от точки.Это довольно близко к наименьшему ненормальному числу, что звучит нормально.
И наименьшее ненулевое субнормальное число:
что равно:
0.000002 * 2 ^ (-126)
что тоже выглядит довольно близко к
0.0
!Не сумев найти какой-либо разумный способ представить числа меньшие, чем это, инженеры были счастливы и вернулись к просмотру картинок кошек в Интернете или что-то еще, что они делали в 70-х.
Как видите, субнормальные числа являются компромиссом между точностью и длиной представления.
В качестве самого крайнего примера, наименьшее ненулевое субнормальное:
0.000002 * 2 ^ (-126)
по существу имеет точность одного бита вместо 32 бита. Например, если мы разделим его на два:
0.000002 * 2 ^ (-126) / 2
мы реально достигаем
0.0
ровно!Визуализация
Всегда полезно иметь геометрическую интуицию относительно того, что мы изучаем, так что начнем.
Если мы нанесем числа с плавающей запятой IEEE 754 на линию для каждого заданного показателя степени, это будет выглядеть примерно так:
+---+-------+---------------+-------------------------------+ exponent |126| 127 | 128 | 129 | +---+-------+---------------+-------------------------------+ | | | | | v v v v v ------------------------------------------------------------- floats ***** * * * * * * * * * * * * ------------------------------------------------------------- ^ ^ ^ ^ ^ | | | | | 0.5 1.0 2.0 4.0 8.0
Из этого мы видим, что:
*
)Теперь давайте полностью опустим это до степени 0.
Без субнормальных явлений это гипотетически выглядело бы так:
+---+---+-------+---------------+-------------------------------+ exponent | ? | 0 | 1 | 2 | 3 | +---+---+-------+---------------+-------------------------------+ | | | | | | v v v v v v ----------------------------------------------------------------- floats * **** * * * * * * * * * * * * ----------------------------------------------------------------- ^ ^ ^ ^ ^ ^ | | | | | | 0 | 2^-126 2^-125 2^-124 2^-123 | 2^-127
С субнормальными это выглядит так:
+-------+-------+---------------+-------------------------------+ exponent | 0 | 1 | 2 | 3 | +-------+-------+---------------+-------------------------------+ | | | | | v v v v v ----------------------------------------------------------------- floats * * * * * * * * * * * * * * * * * ----------------------------------------------------------------- ^ ^ ^ ^ ^ ^ | | | | | | 0 | 2^-126 2^-125 2^-124 2^-123 | 2^-127
Сравнивая два графика, мы видим, что:
субнормальные удваивают длину диапазона экспоненты
0
, от[2^-127, 2^-126)
до[0, 2^-126)
Расстояние между числами с плавающей запятой в субнормальном диапазоне такое же, как и для
[0, 2^-126)
.диапазон
[2^-127, 2^-126)
имеет половину количества точек, которое он имел бы без субнормальных значений.Половина этих баллов идет на заполнение другой половины диапазона.
в диапазоне
[0, 2^-127)
есть некоторые точки с субнормальными значениями, но без них нет.Это отсутствие очков
[0, 2^-127)
не очень элегантно и является основной причиной существования субнормальных существ!поскольку точки расположены на одинаковом расстоянии:
[2^-128, 2^-127)
имеет половину баллов, чем[2^-127, 2^-126)
-[2^-129, 2^-128)
имеет половину баллов, чем[2^-128, 2^-127)
Это то, что мы имеем в виду, когда говорим, что субнормальные значения - это компромисс между размером и точностью.
Пример исполняемого C
Теперь давайте поиграемся с реальным кодом, чтобы проверить нашу теорию.
Практически на всех современных и настольных компьютерах C
float
представляет числа с плавающей запятой одинарной точности IEEE 754.В частности, это относится к моему ноутбуку Lenovo P51 с Ubuntu 18.04 amd64.
При таком предположении все утверждения передаются следующей программе:
subnormal.c
#if __STDC_VERSION__ < 201112L #error C11 required #endif #ifndef __STDC_IEC_559__ #error IEEE 754 not implemented #endif #include <assert.h> #include <float.h> /* FLT_HAS_SUBNORM */ #include <inttypes.h> #include <math.h> /* isnormal */ #include <stdlib.h> #include <stdio.h> #if FLT_HAS_SUBNORM != 1 #error float does not have subnormal numbers #endif typedef struct { uint32_t sign, exponent, fraction; } Float32; Float32 float32_from_float(float f) { uint32_t bytes; Float32 float32; bytes = *(uint32_t*)&f; float32.fraction = bytes & 0x007FFFFF; bytes >>= 23; float32.exponent = bytes & 0x000000FF; bytes >>= 8; float32.sign = bytes & 0x000000001; bytes >>= 1; return float32; } float float_from_bytes( uint32_t sign, uint32_t exponent, uint32_t fraction ) { uint32_t bytes; bytes = 0; bytes |= sign; bytes <<= 8; bytes |= exponent; bytes <<= 23; bytes |= fraction; return *(float*)&bytes; } int float32_equal( float f, uint32_t sign, uint32_t exponent, uint32_t fraction ) { Float32 float32; float32 = float32_from_float(f); return (float32.sign == sign) && (float32.exponent == exponent) && (float32.fraction == fraction) ; } void float32_print(float f) { Float32 float32 = float32_from_float(f); printf( "%" PRIu32 " %" PRIu32 " %" PRIu32 "\n", float32.sign, float32.exponent, float32.fraction ); } int main(void) { /* Basic examples. */ assert(float32_equal(0.5f, 0, 126, 0)); assert(float32_equal(1.0f, 0, 127, 0)); assert(float32_equal(2.0f, 0, 128, 0)); assert(isnormal(0.5f)); assert(isnormal(1.0f)); assert(isnormal(2.0f)); /* Quick review of C hex floating point literals. */ assert(0.5f == 0x1.0p-1f); assert(1.0f == 0x1.0p0f); assert(2.0f == 0x1.0p1f); /* Sign bit. */ assert(float32_equal(-0.5f, 1, 126, 0)); assert(float32_equal(-1.0f, 1, 127, 0)); assert(float32_equal(-2.0f, 1, 128, 0)); assert(isnormal(-0.5f)); assert(isnormal(-1.0f)); assert(isnormal(-2.0f)); /* The special case of 0.0 and -0.0. */ assert(float32_equal( 0.0f, 0, 0, 0)); assert(float32_equal(-0.0f, 1, 0, 0)); assert(!isnormal( 0.0f)); assert(!isnormal(-0.0f)); assert(0.0f == -0.0f); /* ANSI C defines FLT_MIN as the smallest non-subnormal number. */ assert(FLT_MIN == 0x1.0p-126f); assert(float32_equal(FLT_MIN, 0, 1, 0)); assert(isnormal(FLT_MIN)); /* The largest subnormal number. */ float largest_subnormal = float_from_bytes(0, 0, 0x7FFFFF); assert(largest_subnormal == 0x0.FFFFFEp-126f); assert(largest_subnormal < FLT_MIN); assert(!isnormal(largest_subnormal)); /* The smallest non-zero subnormal number. */ float smallest_subnormal = float_from_bytes(0, 0, 1); assert(smallest_subnormal == 0x0.000002p-126f); assert(0.0f < smallest_subnormal); assert(!isnormal(smallest_subnormal)); return EXIT_SUCCESS; }
GitHub вверх по течению .
Скомпилируйте и запустите с:
gcc -ggdb3 -O0 -std=c11 -Wall -Wextra -Wpedantic -Werror -o subnormal.out subnormal.c ./subnormal.out
C ++
В дополнение к раскрытию всех API C, C ++ также предоставляет некоторые дополнительные субнормальные функции, которые не так легко доступны в C
<limits>
, например:denorm_min
: Возвращает минимальное положительное субнормальное значение типа TВ C ++ весь API шаблонизирован для каждого типа с плавающей запятой, и это намного лучше.
Реализации
x86_64 и ARMv8 реализуют IEEE 754 непосредственно на оборудовании, в которое преобразуется код C.
В некоторых реализациях субнормалы кажутся менее быстрыми, чем нормальные: почему изменение 0,1f на 0 снижает производительность в 10 раз? Это упоминается в руководстве по ARM, см. Раздел «Подробности ARMv8» в этом ответе.
ARMv8 подробности
Справочное руководство по архитектуре ARM ARMv8 DDI 0487C.a manual A1.5.4 "Flush-to-zero" описывает настраиваемый режим, в котором субнормальные значения округляются до нуля для повышения производительности:
A1.5.2 «Стандарты с плавающей запятой и терминология» Таблица A1-3 «Терминология с плавающей запятой» подтверждает, что субнормальные и денормальные числа являются синонимами:
C5.2.7 «FPCR, регистр управления с плавающей запятой» описывает, как ARMv8 может опционально вызывать исключения или устанавливать биты флага всякий раз, когда ввод операции с плавающей запятой является субнормальным:
D12.2.88 «MVFR1_EL1, AArch32 Media and VFP Feature Register 1» показывает, что денормальная поддержка фактически не является обязательной, и предлагает немного определить, есть ли поддержка:
Это говорит о том, что когда субнормальные функции не реализованы, реализации просто возвращаются к нулевому значению.
Бесконечность и NaN
Любопытно? Я писал кое-что по адресу:
Как субнормальные факторы улучшают вычисления
TODO: дополнительно понять, как этот скачок ухудшает результаты вычислений / как субнормальные значения улучшают результаты вычислений.
Актуальная история
Чарльз Северанс « Интервью со стариком с плавающей точкой » (1998) представляет собой краткий исторический обзор реального мира в форме интервью с Уильямом Каханом, предложенный Джоном Коулманом в комментариях.
источник
Из http://blogs.oracle.com/d/entry/subnormal_numbers :
источник