Почему 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 или меньше.