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


82

Довідкова сторінка isnormal () повідомляє:

Визначає, чи задане число з плаваючою комою arg є нормальним, тобто не є ні нульовим, ні нормальним, нескінченним, ні NaN.

Число, яке дорівнює нулю, нескінченному чи NaN, зрозуміло, що воно означає. Але це також говорить про ненормальне. Коли число ненормальне?


2
Перший результат Google показує, що це просто синонім денормального: en.wikipedia.org/wiki/Denormal_number
tenfour

10
І все-таки, зараз другим зверненням Google (пошук за „ненормальною плаваючою комою“) є саме це питання.
Сліпп Д. Томпсон,

Дивіться це питання для поглибленого обговорення денормальних явищ та боротьби з ними: stackoverflow.com/questions/9314534/…
рис

Відповіді:


79

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

Практично мантиса зберігається без першого 1, оскільки завжди є ведучий 1, за винятком субнормальних чисел (і нуля). Отже, інтерпретація полягає в тому, що якщо показник ступеня не мінімальний, існує неявний провідний 1, а якщо показник мінімальний, його немає, і число є субнормальним.

*) Більш загально, 1 ≤  M  <  B   для будь-якого наукового позначення base- B .


Ви хочете сказати isnomal, що trueякщо всі 8 бітів дорівнюють нулю, falseінакше?
Пейс’єр

"зберігається" або інтерпретується?
Пейс'єр

@Pacerier: "збережений": Він зберігається без першого 1, наприклад, як 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 нулі з a 1на кінці.

Ми не можемо взяти fraction = 0, інакше це число було б 0.0.

Але тоді інженери, які також мали гострий естетичний сенс, подумали: чи це не потворно? Що ми переходимо від прямого 0.0до чогось, що навіть не має належної сили 2? Чи не могли б ми якось представити ще менші цифри? (Добре, це було трохи більше, ніж "потворно": насправді люди отримували погані результати для своїх обчислень, див. "Як піднормальні покращують обчислення" нижче).

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

Інженери деякий час чухали голови і повертались, як завжди, з черговою доброю ідеєю. Що робити, якщо ми створимо нове правило:

Якщо показник степеня дорівнює 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.

Це зокрема стосується мого ноутбука Ubuntu 18.04 amd64 Lenovo P51.

З цим припущенням усі твердження передаються за такою програмою:

субнормальний. 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 безпосередньо на апаратному забезпеченні, на що перекладається код С.

У деяких реалізаціях субнормальні показники є менш швидкими, ніж нормальні: Чому зміна 0,1f на 0 сповільнює продуктивність в 10 разів? Про це йдеться у посібнику ARM, див. Розділ "Подробиці ARMv8" цієї відповіді.

Подробиці ARMv8

Довідковий посібник з ARM-архітектури ARMv8 DDI 0487C. посібник A1.5.4 "Змив до нуля" описує настроюваний режим, коли субнормалі округляються до нуля для поліпшення продуктивності:

Ефективність обробки з плаваючою точкою може бути знижена при виконанні обчислень із денормалізованими числами та винятками Underflow. У багатьох алгоритмах цю продуктивність можна відновити, не суттєво впливаючи на точність кінцевого результату, замінюючи денормалізовані операнди та проміжні результати нулями. Щоб дозволити цю оптимізацію, реалізації з плаваючою крапкою ARM дозволяють використовувати режим змиву до нуля для різних форматів з плаваючою точкою, як показано нижче:

  • Для AArch64:

    • Якщо FPCR.FZ==1, тоді режим «Змив-нуль» використовується для всіх входів та виходів з однією точністю та подвійною точністю всіх інструкцій.

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

А1.5.2 "Стандарти з плаваючою крапкою та термінологія" Таблиця А1-3 "Термінологія з плаваючою крапкою" підтверджує, що субнормалі та денормали є синонімами:

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

C5.2.7 "FPCR, реєстр керування плаваючою точкою" описує, як ARMv8 може додатково створювати винятки або встановлювати біти прапора, коли вхід операції з плаваючою точкою є ненормальним:

FPCR.IDE, біт [15] Увімкнути панораму виключення з плаваючою комою в режимі Denormal. Можливі значення:

  • 0b0 Вибрано обробку винятків без відключення. Якщо виникає виняток з плаваючою комою, тоді біт FPSR.IDC встановлюється на 1.

  • 0b1 Обрано обробку винятків у пастці. Якщо виникає виняток із плаваючою комою, PE не оновлює біт FPSR.IDC. Програмне забезпечення для обробки пастки може вирішити, чи встановлювати біт FPSR.IDC на 1.

D12.2.88 "MVFR1_EL1, AArch32 Media та VFP Feature Register 1" показує, що підтримка ненормальних даних насправді є абсолютно необов’язковою, і пропонує трохи виявити, чи є підтримка:

FPFtZ, біти [3: 0]

Змийте в нульовий режим. Вказує, чи реалізація з плаваючою точкою надає підтримку лише для режиму роботи Flush-to-Zero. Визначені значення:

  • 0b0000 Не реалізовано, або апаратне забезпечення підтримує лише режим роботи Flush-to-Zero.

  • 0b0001 Апаратне забезпечення підтримує повну денормалізовану арифметику чисел.

Усі інші значення зарезервовані.

В ARMv8-A дозволеними значеннями є 0b0000 та 0b0001.

Це свідчить про те, що коли субнормалі не реалізовані, реалізації просто повертаються до рівня нуля.

Нескінченність і NaN

Цікаво? Я писав деякі речі за адресою:

Як субнормальні покращують обчислення

TODO: далі зрозуміти більш точно, як цей стрибок погіршує результати обчислення / як субнормальні покращують результати обчислення.

Фактична історія

Інтерв'ю з Старшого з плаваючою точкою по Чарльз Severance . (1998) короткий реальний світ історичний огляд у вигляді інтерв'ю з Кехен було запропоновано Джона Колемана в коментарях.


1
Посилання на "Під час проектування IEEE 754 .."? Або краще розпочати речення з "Імовірно"
Пейс'єр,

@Pacerier Я не думаю, що цей факт може бути помилковим :-) Яке ще обгрунтування може бути для цього? Ймовірно, це було відомо раніше, але це гаразд, я думаю.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Чудова відповідь. Я готуюся вести заняття з цифрового аналізу навесні і направлю своїх студентів на це (наш текст коротко обговорює, але залишає деталі). Що стосується обгрунтування деяких рішень, я знайшов таке просвітницьке: Інтерв'ю зі старим з Плаваючої точки .
Джон Коулман

@JohnColeman дякую за це посилання! Додано до відповіді з посиланням на вас. Було б чудово, якби хтось міг додати, можливо, в іншій відповіді, найкоротший можливий значущий приклад, коли субнормальні покращують результат обчислень (і, можливо, один штучний приклад, коли результати погіршуються)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

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 . Це називається субнормальним числом. Мета наявності субнормальних чисел - згладити розрив між найменшим нормальним числом і нулем.

Дуже важливо усвідомити, що субнормальні числа представлені з меншою точністю, ніж звичайні числа. Насправді вони торгують зниженою точністю за менший розмір. Отже, обчислення, що використовують субнормальні числа, не будуть мати таку саму точність, як розрахунки для нормальних чисел. Отже, додаток, який робить значні обчислення субнормальних чисел, напевно, варто дослідити, щоб побачити, чи масштабування (тобто множення чисел на якийсь коефіцієнт масштабування) дасть менше субнормальних показників та більш точні результати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.