Диапазоны типа данных с плавающей точкой в ​​C?

Я читаю книгу на языке C, говоря о диапазонах с плавающей запятой, автор дал таблицу:

Type     Smallest Positive Value  Largest value      Precision
====     =======================  =============      =========
float    1.17549 x 10^-38         3.40282 x 10^38    6 digits
double   2.22507 x 10^-308        1.79769 x 10^308   15 digits

Я не знаю, откуда взялись числа в столбцах "Наименьшее положительное" и "Наибольшее значение".

6 ответов

Решение

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

32-битное число с плавающей запятой имеет 23 + 1 бит мантиссы и 8-битную экспоненту (хотя используется от -126 до 127), поэтому самое большое число, которое вы можете представить:

(1 + 1 / 2 + ... 1 / (2 ^ 23)) * (2 ^ 127) = 
(2 ^ 23 + 2 ^ 23 + .... 1) * (2 ^ (127 - 23)) = 
(2 ^ 24 - 1) * (2 ^ 104) ~= 3.4e38

Значения для типа данных с плавающей запятой получены из общего числа 32 бита для представления числа, которое распределено следующим образом:

1 бит: знаковый бит

8 бит: показатель степени p

23 бита: мантисса

Экспонента хранится как p + BIAS где BIAS равен 127, мантисса имеет 23 бита и 24-й скрытый бит, который предполагается равным 1. Этот скрытый бит является старшим значащим битом (MSB) мантиссы, и показатель степени должен быть выбран так, чтобы он равнялся 1.

Это означает, что наименьшее число, которое вы можете представить, 01000000000000000000000000000000 который 1x2^-126 = 1.17549435E-38,

Наибольшее значение 011111111111111111111111111111111мантисса равна 2 * (1 - 1/65536), а показатель степени равен 127, что дает (1 - 1 / 65536) * 2 ^ 128 = 3.40277175E38,

Те же самые принципы применяются к двойной точности, кроме битов:

1 бит: знаковый бит

11 битов: экспонентные биты

52 бита: биты мантиссы

BIAS: 1023

Таким образом, технически ограничения взяты из стандарта IEEE-754 для представления чисел с плавающей запятой, и как это происходит

Бесконечность, NaN и субнормалы

Это важные предостережения, о которых ни один другой ответ не упомянул.

Сначала прочтите это введение в IEEE 754 и субнормальные числа: Что такое субнормальное число с плавающей запятой?

Тогда для одинарной точности (32-разрядных):

  • IEEE 754 говорит, что если показатель степени равен единице (0xFF == 255), то это представляет или NaN или Бесконечность.

    Вот почему наибольшее бесконечное число имеет показатель степени 0xFE == 254 и не 0xFF,

    Затем с уклоном становится:

    254 - 127 == 127
    
  • FLT_MIN наименьшее нормальное число. Но есть меньшие субнормальные! Те берут -127 слот экспоненты.

Все утверждения следующей программы проходят на Ubuntu 18.04 amd64:

#include <assert.h>
#include <float.h>
#include <inttypes.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>

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 main(void) {
    /* All 1 exponent and non-0 fraction means NaN.
     * There are of course many possible representations,
     * and some have special semantics such as signalling vs not.
     */
    assert(isnan(float_from_bytes(0, 0xFF, 1)));
    assert(isnan(NAN));
    printf("nan                  = %e\n", NAN);

    /* All 1 exponent and 0 fraction means infinity. */
    assert(INFINITY == float_from_bytes(0, 0xFF, 0));
    assert(isinf(INFINITY));
    printf("infinity             = %e\n", INFINITY);

    /* ANSI C defines FLT_MAX as the largest non-infinite number. */
    assert(FLT_MAX == 0x1.FFFFFEp127f);
    /* Not 0xFF because that is infinite. */
    assert(FLT_MAX == float_from_bytes(0, 0xFE, 0x7FFFFF));
    assert(!isinf(FLT_MAX));
    assert(FLT_MAX < INFINITY);
    printf("largest non infinite = %e\n", FLT_MAX);

    /* ANSI C defines FLT_MIN as the smallest non-subnormal number. */
    assert(FLT_MIN == 0x1.0p-126f);
    assert(FLT_MIN == float_from_bytes(0, 1, 0));
    assert(isnormal(FLT_MIN));
    printf("smallest normal      = %e\n", FLT_MIN);

    /* 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));
    printf("smallest subnormal   = %e\n", smallest_subnormal);

    return EXIT_SUCCESS;
}

GitHub вверх по течению.

Скомпилируйте и запустите с:

gcc -ggdb3 -O0 -std=c11 -Wall -Wextra -Wpedantic -Werror -o subnormal.out subnormal.c
./subnormal.out

Выход:

nan                  = nan
infinity             = inf
largest non infinite = 3.402823e+38
smallest normal      = 1.175494e-38
smallest subnormal   = 1.401298e-45

Как dasblinkenlight уже ответил, числа получены из того, как числа с плавающей запятой представлены в IEEE-754, и у Андреаса есть хорошая разбивка математики.

Однако следует помнить, что точность чисел с плавающей запятой точно не равна 6 или 15 значащим десятичным цифрам, как показано в таблице, поскольку точность чисел IEEE-754 зависит от количества значащих двоичных цифр.

  • float имеет 24 значащих двоичных разряда - что в зависимости от представленного числа преобразуется в 6–8 десятичных знаков точности.

  • double имеет 53 значащих двоичных цифр, что составляет примерно 15 десятичных цифр.

Другой мой ответ имеет дальнейшее объяснение, если вам интересно.

Это является следствием размера показательной части типа, как, например, в IEEE 754. Вы можете проверить размеры с FLT_MAX, FLT_MIN, DBL_MAX, DBL_MIN в float.h.

Другие вопросы по тегам