Что такое субнормальное число с плавающей запятой?

82

Справочная страница isnormal () сообщает:

Определяет, является ли данное число с плавающей запятой arg нормальным, т. Е. Не равно нулю, субнормальному, бесконечному или NaN.

Число, равное нулю, бесконечности или NaN, ясно, что это означает. Но это также говорит о субнормальном. Когда число ненормальное?

БЈовић
источник
2
Первый результат в Google показывает, что это просто синоним денормального значения: en.wikipedia.org/wiki/Denormal_number
tenfour 01
10
И все же, теперь второе попадание в Google (поиск «субнормальных чисел с плавающей запятой») - это сам вопрос.
Слипп Д. Томпсон
См. Этот вопрос для подробного обсуждения денормальных величин и работы с ними: stackoverflow.com/questions/9314534/…
рис.

Ответы:

79

В стандарте IEEE754 числа с плавающей запятой представлены в двоичной научной нотации x  =  M  × 2 e . Здесь M - мантисса, а e - показатель степени . Математически вы всегда можете выбрать показатель степени так, чтобы 1 ≤  M  <2. * Однако, поскольку в компьютерном представлении показатель степени может иметь только конечный диапазон, есть некоторые числа, которые больше нуля, но меньше 1,0 × 2 e мин . Эти числа являются субнормальными или ненормальными .

Практически мантисса хранится без ведущей единицы, поскольку всегда есть ведущая 1, за исключением субнормальных чисел (и нуля). Таким образом, интерпретация такова, что если показатель степени не минимален, существует неявная ведущая единица, а если показатель минимальный, нет и число субнормально.

*) В более общем случае 1 ≤  M  <  B   для любой научной записи с основанием B.

Керрек С.Б.
источник
Вы говорите isnomal, trueесли все 8 бит равны нулю, или falseиначе?
Pacerier
«хранится» или интерпретируется?
Pacerier
@Pacerier: "хранится": хранится без первой единицы, например как 001010, и интерпретируется как 1.001010.
Керрек С.Б.
Ясно ли, что такое emin в: `` e <sub> min </sub>? `` (Надеюсь, моя попытка форматирования сработает) ..
Razzle
85

Основы IEEE 754

Сначала давайте рассмотрим основы организации номеров IEEE 754.

Мы сосредоточимся на одинарной точности (32 бита), но все можно сразу же обобщить на другие точности.

Формат:

  • 1 бит: знак
  • 8 бит: показатель степени
  • 23 бита: дробь

Или, если вам нравятся картинки:

введите описание изображения здесь

Источник .

Знак простой: 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
  • тогда число представляет плюс или минус 0.0

так что байты 00 00 00 00также представляют 0.0, что выглядит хорошо.

Если бы мы учитывали только эти правила, то наименьшее ненулевое число, которое может быть представлено, было бы:

  • показатель степени: 0
  • фракция: 1

что выглядит примерно так в шестнадцатеричной дроби из-за соглашения о начальных битах:

1.000002 * 2 ^ (-127)

где .00000222 нуля с буквой 1на конце.

Мы не можем взять fraction = 0, иначе было бы это число 0.0.

Но потом инженеры, имевшие к тому же острое эстетическое чутье, подумали: разве это не уродливо? Что мы прыгаем прямо0.0 к чему-то, что даже не является истинной степенью двойки? Не могли бы мы как-нибудь представить еще меньшие числа? (Хорошо, это было немного больше, чем «уродливое»: на самом деле люди получали плохие результаты своих вычислений, см. «Как субнормальные факторы улучшают вычисления» ниже).

Субнормальные числа

Инженеры немного почесали затылки и, как обычно, вернулись с другой хорошей идеей. Что, если мы создадим новое правило:

Если показатель степени равен 0, то:

  • ведущий бит становится 0
  • экспонента фиксируется на -126 (не -127, как если бы у нас не было этого исключения)

Такие числа называются субнормальными числами (или денормальными числами, что является синонимом).

Из этого правила сразу следует, что число такое, что:

  • показатель степени: 0
  • фракция: 0

по-прежнему 0.0, что довольно элегантно, поскольку означает, что на одно правило меньше.

Так 0.0на самом деле субнормальный номер в соответствии с нашим определением!

Таким образом, с помощью этого нового правила наименьшее ненормальное число будет:

  • показатель степени: 1 (0 будет субнормальным)
  • фракция: 0

что представляет собой:

1.0 * 2 ^ (-126)

Тогда наибольшее субнормальное число:

  • показатель степени: 0
  • дробь: 0x7FFFFF (23 бита 1)

что равно:

0.FFFFFE * 2 ^ (-126)

где .FFFFFEснова 23 бита, единица справа от точки.

Это довольно близко к наименьшему ненормальному числу, что звучит нормально.

И наименьшее ненулевое субнормальное число:

  • показатель степени: 0
  • фракция: 1

что равно:

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

Из этого мы видим, что:

  • для каждой экспоненты нет перекрытия между представленными числами
  • для каждого показателя у нас есть одно и то же число 2 ^ 32 чисел (здесь представлено 4 *)
  • внутри каждой экспоненты точки расположены на одинаковом расстоянии
  • более крупные показатели охватывают более крупные диапазоны, но с более разбросанными точками

Теперь давайте полностью опустим это до степени 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" описывает настраиваемый режим, в котором субнормальные значения округляются до нуля для повышения производительности:

Производительность обработки с плавающей запятой может быть снижена при выполнении вычислений с использованием денормализованных чисел и исключений потери значимости. Во многих алгоритмах эту производительность можно восстановить без значительного влияния на точность конечного результата, заменив денормализованные операнды и промежуточные результаты нулями. Чтобы разрешить эту оптимизацию, реализации с плавающей запятой ARM позволяют использовать режим Flush-to-zero для различных форматов с плавающей запятой следующим образом:

  • Для AArch64:

    • Если FPCR.FZ==1, то режим Flush-to-Zero используется для всех входов и выходов одинарной и двойной точности всех инструкций.

    • Если FPCR.FZ16==1, то режим Flush-to-Zero используется для всех входов и выходов половинной точности инструкций с плавающей запятой, кроме: - Преобразования между числами половинной точности и одинарной точности. - Преобразования между половинной точностью и двойной точностью. числа.

A1.5.2 «Стандарты с плавающей запятой и терминология» Таблица A1-3 «Терминология с плавающей запятой» подтверждает, что субнормальные и денормальные числа являются синонимами:

This manual                 IEEE 754-2008
-------------------------   -------------
[...]
Denormal, or denormalized   Subnormal

C5.2.7 «FPCR, регистр управления с плавающей запятой» описывает, как ARMv8 может опционально вызывать исключения или устанавливать биты флага всякий раз, когда ввод операции с плавающей запятой является субнормальным:

FPCR.IDE, бит [15] Входное разрешение прерывания исключительной ситуации с ненормальной плавающей точкой. Возможные значения:

  • 0b0 Выбрана неотработанная обработка исключений. Если возникает исключительная ситуация с плавающей запятой, бит FPSR.IDC устанавливается в 1.

  • 0b1 Выбрана обработка захваченного исключения. Если возникает исключительная ситуация с плавающей запятой, PE не обновляет бит FPSR.IDC. Программа обработки прерываний может решить, устанавливать ли бит FPSR.IDC в ​​1.

D12.2.88 «MVFR1_EL1, AArch32 Media and VFP Feature Register 1» показывает, что денормальная поддержка фактически не является обязательной, и предлагает немного определить, есть ли поддержка:

FPFtZ, биты [3: 0]

Сброс до нулевого режима. Указывает, поддерживает ли реализация с плавающей запятой только режим работы Flush-to-Zero. Определенные значения:

  • 0b0000 Не реализовано, или оборудование поддерживает только режим работы Flush-to-Zero.

  • 0b0001 Аппаратное обеспечение поддерживает полную арифметику денормализованных чисел.

Все остальные значения зарезервированы.

В ARMv8-A допустимые значения - 0b0000 и 0b0001.

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

Бесконечность и NaN

Любопытно? Я писал кое-что по адресу:

Как субнормальные факторы улучшают вычисления

TODO: дополнительно понять, как этот скачок ухудшает результаты вычислений / как субнормальные значения улучшают результаты вычислений.

Актуальная история

Чарльз Северанс « Интервью со стариком с плавающей точкой » (1998) представляет собой краткий исторический обзор реального мира в форме интервью с Уильямом Каханом, предложенный Джоном Коулманом в комментариях.

Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
1
Цитата за «При разработке IEEE 754 ..»? Или лучше начать предложение с «Предположительно»
Пэйсьер
@Pacerier Я не думаю, что этот факт может быть неправильным :-) Какое еще может быть объяснение этому? Скорее всего, это было известно раньше, но я думаю, что это нормально.
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
Отличный ответ. Я готовлюсь провести весной урок по числовому анализу и направлю своих учеников на это (в нашем тексте есть краткое обсуждение, но не все детали). Что касается обоснования некоторых решений, я нашел следующее поучительным: Интервью со Стариком Плавающей Точки .
Джон Коулман,
@JohnColeman спасибо за ссылку! Добавлен к ответу со ссылкой на вас. Было бы замечательно, если бы кто-то мог добавить, возможно, в другом ответе, кратчайший возможный значимый пример, в котором субнормальные значения улучшают результаты вычислений (и, возможно, один искусственный пример, когда результаты ухудшаются)
Чиро Сантилли 郝海东 冠状 病 六四 事件
29

Из http://blogs.oracle.com/d/entry/subnormal_numbers :

Существует несколько способов представления одного и того же числа, используя, например, десятичную дробь, число 0,1 может быть представлено как 1 * 10 -1 или 0,1 * 10 0 или даже 0,01 * 10. Стандарт требует, чтобы числа всегда сохранялись с первый бит как один. В десятичном формате это соответствует примеру 1 * 10-1.

Теперь предположим, что наименьший показатель, который может быть представлен, равен -100. Таким образом, наименьшее число, которое может быть представлено в нормальной форме, - 1 * 10 -100 . Однако, если мы ослабим ограничение на то, что ведущий бит равен единице, тогда мы сможем фактически представлять меньшие числа в том же пространстве. В десятичном примере мы можем представить 0,1 * 10 -100 . Это называется субнормальным числом. Цель наличия субнормальных чисел - сгладить разрыв между наименьшим нормальным числом и нулем.

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

allwyn.menezes
источник