Почему double может хранить большие числа, чем unsigned long long?

вопрос в том, что я не совсем понимаю, почему double может хранить большие числа, чем unsigned long long. Поскольку оба они имеют длину 8 байт, поэтому 64 бита.

где в unsigned long long все 64 бита используются для хранения значения, с другой стороны, double имеет 1 для знака, 11 для экспоненты и 52 для мантиссы. Даже если 52 бита, которые используются для мантиссы, будут использоваться для хранения десятичных чисел без плавающей точки, у него все еще есть 63 бита ...

но LLONG_MAX значительно меньше, чем DBL_MAX ...

Почему?

6 ответов


причина в том, что unsigned long long хранит точно целые числа, а double хранит мантиссу (с ограниченной 52-битной точностью) и показатель.

в данном double для хранения очень больших чисел (около 10308), но не точно. У вас есть около 15 (почти 16) допустимых десятичных цифр в double, а остальные 308 возможных десятичных дробей-нули (на самом деле не определены, но вы можете принять "ноль" для лучшего понимания).
Ан unsigned long long только имеет 19 цифр, но каждая из них точно определена.

EDIT:
В ответ на комментарий ниже "как именно это работает", у вас есть 1 бит для знака, 11 бит для экспоненты и 52 бита на мантиссу. Мантисса имеет подразумеваемый бит" 1 " в начале, который не сохраняется, поэтому эффективно у вас есть 53 мантиссы. 253 9.007E15, поэтому у вас есть 15, почти 16 десятичных цифр для работы с.
Показатель имеет знаковый бит и может варьироваться от -1022 до +1023, который используется для масштабирования (двоичный сдвиг влево или вправо) мантиссы (21023 около 10307, следовательно, пределы диапазона), поэтому очень маленькие и очень большие числа одинаково возможны с этим форматом.
Но, конечно, все числа, которые вы можете представить, имеют столько точности, сколько поместится в Матиссу.

в целом, числа с плавающей запятой не очень интуитивно понятный, так как" простые " десятичные числа не обязательно представляются как числа с плавающей запятой. Это связано с тем, что мантисса двоичный. Например, можно (и легко) представить любое положительное целое число до нескольких миллиардов или такие числа, как 0.5, 0.25 или 0.0125, с идеальной точностью.
С другой стороны, также можно представить число, подобное 10250, но только приблизительно. На самом деле, вы найдете, что 10250 и 10250+1-это одно и то же число (подождите, что???). Это потому, что, хотя вы можете легко иметь 250 цифр, у вас не так много значительное цифры (читать " значительный "как" известный "или"определенный").
Кроме того, представляя что-то, казалось бы, простое, как 0.3 и возможно только приблизительно, хотя 0.3 даже не является "большим" числом. Однако вы не можете представить 0.3 в двоичном формате, и независимо от того, какой двоичный показатель вы прикрепляете к нему, вы не найти любое двоичное число, которое приводит к точно 0.3 (но вы можете получить очень близко).

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

unsigned long long с другой стороны, никак не интерпретирует битовый шаблон. Все числа, которые можно представить просто точное число, представленное двоичным кодом. Каждая цифра каждое число точно определено, масштабирование не происходит.


значения с плавающей запятой IEEE754 могут хранить больший ряд цифр просто потому, что они жертвуют точности.

под этим я подразумеваю, что 64-разрядный интегральный тип может представлять каждое значение в своем диапазоне, но 64-разрядный двойной не может.

например, попытка сохранить 0.1 в двуспальную не дать вы 0.1, это даст вам что-то вроде:

0.100000001490116119384765625

(это на самом деле ближайший один значение точности, но тот же эффект будет применяться для двойной точности).


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

классический пример, Предположим, у вас есть четыре десятичные цифры для хранения значения. С целым числом вы можете представить числа 0000 через 9999 включительно. Точность в пределах этого диапазона идеально, вы можете представить каждое интегральное значение.

однако, пойдем с плавающей запятой и используем последнюю цифру в качестве шкалы, чтобы цифры 1234 фактически представляют число 123 x 104.

так теперь ваш диапазон от 0 (в лице 0000 через 0009) через 999,000,000,000 (в лице 9999 будучи 999 x 109).

но вы не можете представлять любое число внутри этот диапазон. Например, 123,456 невозможно представить, шкаф, который вы можете получить, с цифрами 1233, которые дают вам 123,000. И, на самом деле, где целочисленные значения имели точность четырех цифр, теперь у вас есть только три.

это в основном, как IEEE754 работает, жертвуя точностью для диапазона.


отказ от ответственности

это попытка дать простое для понимания объяснение о том, как работает кодировка с плавающей запятой. Это упрощение, и оно не охватывает никаких технических аспектов реального стандарта IEEE 754 с плавающей запятой (нормализация, нулевой знак, бесконечности, NaNs, округление и т. д.). Однако представленная здесь идея верна.


понимание того, как работают числа с плавающей запятой, серьезно затрудняется дело в том, что компьютеры работают с числами в базе 2 в то время как люди не легко справляются с ними. Я попытаюсь объяснить, как работают числа с плавающей запятой, используя base 10.

давайте построим представление числа с плавающей запятой, используя знаки и базу 10 цифры (т. е. обычные цифры из 0 to 9 мы используем на ежедневной основе).

Допустим, у нас есть 10 квадратных ячеек, и каждая ячейка может содержать либо знак (+ или -) или a десятичная цифра (0, 1, 2, 3, 4, 5, 6, 7, 8 или 9).

мы можем использовать 10 цифр для хранения целых чисел со знаком. Одна цифра для знака и 9 цифр для значения:

sign -+   +-------- 9 decimal digits -----+
      v   v                               v
    +---+---+---+---+---+---+---+---+---+---+
    | + | 0 | 0 | 0 | 0 | 0 | 1 | 5 | 0 | 0 |
    +---+---+---+---+---+---+---+---+---+---+

вот как значение 1500 представляется в виде целого числа.

мы также можем использовать их для хранения чисел с плавающей точкой. Например, 7 цифр для мантиссы и 3 цифры для показатель:

  +------ sign digits --------+
  v                           v
+---+---+---+---+---+---+---+---+---+---+
| + | 0 | 0 | 0 | 1 | 5 | 0 | + | 0 | 1 |
+---+---+---+---+---+---+---+---+---+---+
|<-------- Mantissa ------->|<-- Exp -->|       

это одно из возможных представлений 1500 как значение с плавающей запятой (используя наше представление 10 десятичных цифр).

значение мантиссы (M) is +150 значение показателя (E) составляет +1. Значение, представленное выше:

V = M * 10^E = 150 * 10^1 = 1500

диапазоны

целочисленное представление может хранить подписанные значения между -(10^9-1) (-999,999,999) и +(10^9-1) (+999,999,999). Больше, оно может представляют каждое целое значение между этими пределами. Более того, для каждого значения существует одно представление, и оно является точным.

представление с плавающей запятой может хранить подписанные значения для мантиссы (M) между -999,999 и +999,999 и для экспоненты (E) между -99 и +99.

он может хранить значения между -999,999*10^99 и +999,999*10^99. Эти цифры 105 цифры, гораздо больше, чем 9 цифры из крупнейших числа, представленные в виде целых чисел выше.

потеря точности

отметим, что для целых значений, M сохраняет знак и первые 6 цифр значения (или меньше) и E - это количество цифр, которые не вписываются в M.

V = M * 10^E

давайте попробуем представить V = +987,654,321 используя нашу кодировку с плавающей запятой.

, потому что M ограничен +999,999 он может хранить только +987,654 и E будет +3 (последние 3 цифры V не может поместиться в M).

положить их вместе:

+987,654 * 10^(+3) = +987,654,000

это не наше первоначальное значение V но лучшее приближение мы можем получить, используя это представление.

заметим, что все числа между (и включая) +987,654,000 и +987,654,999 аппроксимируются с использованием того же значения (M=+987,654, E=+3). Также нет возможности хранить десятичные цифры для чисел больше, чем +999,999.

как правило, для чисел больше максимального значения M (+999.999), этот метод создает одинаковое представление для всех значений между +999,999*10^E и +999,999*10^(E+1)-1 (целых или вещественных значения, это не имеет значения).

вывод

для больших значений (больше максимального значения M), представление с плавающей запятой имеет пробелы между числами, которые оно может представлять. Эти пробелы становятся все больше и больше по мере того, как ценность E увеличивается.

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

давайте возьмем скорость света в качестве примера. Его значение составляет около 300,000 km/s. Будучи таким массивным, для большинства практических целей вам все равно, если это 300,000.001 km/s или 300,000.326 km/s.

на самом деле, это даже не так много, лучшее приближение 299,792.458 km/s.

числа с плавающей запятой извлекают важные характеристики скорости света: его величина составляет сотни тысяч км / с (E=5) и его стоимость составляет 3 (сто тысяч км/с).

speed of light = 3*10^5 km/s

наше представление с плавающей запятой может приблизить его:299,792 km/s (M=299,792, E=0).


какая магия происходит ???

тот же вид магии, который позволяет представлять 101-значным номером

10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 

as

1.0 * 10100

это просто вместо базы 10 вы делаете это в базе 2:

0.57149369564113749110789177415267 * 2333.

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

это показатель, который позволяет числам с плавающей запятой представлять такой большой ряд ценностей. Значение показателя 1024 требуется только 10 бит для хранения, но 21024 - это 308-значное число.

компромисс заключается в том, что не каждое значение может быть представлено ровно. С 64-разрядным целым числом каждое значение между 0 и 264-1 (или -263 to 263-1) имеет точное представление. Это не относится к числам с плавающей запятой по нескольким причинам. Прежде всего, у вас есть только так много битов, давая вам только так много цифр точности. Например, если у вас есть только 3 значащие цифры, то вы не можете представлять значения между 0.123 и 0.124, или 1.23 и 1.24, или 123 и 124, или 1230000 и 1240000. По мере приближения к краю диапазона разрыв между представимыми значениями увеличивается.

во-вторых, так же, как есть значения, которые не могут быть представлены в конечном количестве цифр (3/10 дает непрерывную последовательность 0.33333...10), есть значения, которые не могут быть представлены в конечном количестве битов (1/10 дает непрерывную последовательность 1.100110011001...2).


возможно, вы чувствуете, что" хранение числа в N битах " является чем-то фундаментальным, тогда как существуют различные способы сделать это. На самом деле, точнее сказать мы представляют число в N битах, поскольку значение зависит от того, какую конвенцию мы принимаем. Мы можем, в принципе, принять любое соглашение, которое нам нравится, для которого числа представляют разные N-разрядные шаблоны. Существует двоичное соглашение, используемое для unsigned long long и другие целочисленные типы, а также соглашение мантиссы + экспоненты, как используется для double, но мы могли бы также определить (абсурдное) наше собственное соглашение, в котором, например, все биты нуля означают любое огромное число, которое вы хотите указать. На практике мы обычно используем соглашения, которые позволяют нам объединять (добавлять, умножать и т. д.) номера эффективно используя оборудование, на котором мы запускаем наши программы.

тем не менее, на ваш вопрос нужно ответить, сравнив наибольшее двоичное N-разрядное число с наибольшим числом формы 2^exponent * mantissa, где exponent mantissa несколько E-и M-разрядные двоичные числа (с неявным 1 в начале мантиссы). Это 2^(2^E-1) * (2^M - 1), который, как правило, действительно намного больше, чем 2^N - 1.


маленький пример Дэймон и Paxdiablo объяснения:

#include <stdio.h>

int main(void) {
    double d = 2LL<<52;
    long long ll = 2LL<<52;
    printf("d:%.0f  ll:%lld\n", d, ll);
    d++; ll++;
    printf("d:%.0f  ll:%lld\n", d, ll);
}

выход:

d:72057594037927936  ll:72057594037927936
d:72057594037927936  ll:72057594037927937

обе переменные были бы увеличены одинаково со сдвигом 51 или меньше.