为什么 double 可以存储比 unsigned long long 更大的数字?
Why double can store bigger numbers than unsigned long long?
问题是,我不太明白为什么 double 可以存储比 unsigned long long 更大的数字。由于它们都是8字节长,所以都是64位。
在 unsigned long long 中,所有 64 位都用于存储值,另一方面,double 的符号为 1,指数为 11,尾数为 52。即使用于尾数的 52 位将用于存储没有浮点数的十进制数,它仍然有 63 位 ...
但是 LLONG_MAX 明显小于 DBL_MAX ...
为什么?
原因是 unsigned long long
将存储 精确的 整数,而 double
存储一个尾数(具有有限的 52 位精度)和一个指数。
这允许 double
存储非常大的数字(大约 10308)但不完全。 double
中有大约 15(将近 16)个有效的十进制数字,其余 308 个可能的小数是零(实际上未定义,但您可以假设 "zero" 以便更好地理解)。
一个unsigned long long
只有19位数字,但每一位都是精确定义的。
编辑:
在回复下面的评论 "how does this exactly work" 时,您有 1 位符号,11 位指数和 52 位尾数。尾数在开头有一个隐含的“1”位,未存储,因此 有效地 你有 53 个尾数位。 253 是 9.007E15,所以你有 15,几乎 16 个十进制数字可以使用。
指数有一个符号位,范围从-1022到+1023,用于缩放(二进制左移或右移)尾数(21023大约是10307,因此存在范围限制),因此非常小和非常大的数字同样可以使用这种格式。
但是,当然,您可以表示的所有数字都只能具有适合 matissa 的精度。
总而言之,浮点数不是很直观,因为 "easy" 十进制数根本不一定表示为浮点数。这是因为尾数是二进制的。例如,可以(并且很容易)以完美的精度表示高达几十亿的任何正整数,或者像 0.5 或 0.25 或 0.0125 这样的数字。
另一方面,也可以表示像 10250 这样的数字,但只是近似值。事实上,你会发现 10250 和 10250+1 是同一个数字(等等,什么???)。这是因为虽然您可以轻松拥有 250 位数字,但您没有那么多 重要的 位数字(将 "significant" 读作 "known" 或 "defined")。
此外,表示像 0.3 这样看似简单的东西 也 只是近似的,即使 0.3 甚至不是 "big" 数字。但是,你不能用二进制表示 0.3,而且无论你附加什么二进制指数,你都不会找到任何结果正好是 0.3 的二进制数(但你可以非常接近)。
一些 "special values" 保留给 "infinity"(正面和负面)以及 "not a number",因此您 非常少 比总的理论范围。
另一方面,unsigned long long
不以任何方式解释位模式。您可以表示的所有数字都只是位模式所表示的确切数字。每个数字的每个数字都是精确定义的,没有缩放。
IEEE754 浮点值可以存储更大的范围 数字,仅仅是因为它们牺牲了精度。
我的意思是 64 位整数类型可以表示其范围内的每个值,但 64 位双精度类型不能。
例如,尝试将 0.1
存储到双精度数实际上不会 给 你 0.1
,它会给你类似的东西:
0.100000001490116119384765625
(这实际上是最接近的 单精度 精度值,但同样的效果将适用于双精度)。
但是,如果问题是 "how do you get a larger range with fewer bits available to you?",那只是其中一些位用于缩放值。
经典示例,假设您有四位十进制数字来存储一个值。使用整数,您可以表示 0000
到 9999
之间的数字。该范围内的精度是完美的,您可以表示每个整数值。
但是,让我们使用浮点数并使用最后一位数字作为比例,以便数字 1234
实际上代表数字 123 x 10<sup>4</sup>
.
所以现在你的范围是从0
(代表0000
到0009
)到999,000,000,000
(代表9999
是 999 x 10<sup>9</sup>
).
但是您无法表示该范围内的每个数字。比如123,456
是不能表示的,你能得到的衣橱里的数字是1233
,它给你的是123,000
。而且,事实上,整数值的精度为四位,现在只有三位。
这就是 IEEE754 的基本工作原理,牺牲了范围的精度。
Damon 和 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 或更少。
也许您觉得 "storing a number in N bits" 是最基本的东西,其实有多种方法可以做到。事实上,更准确的说法是我们 表示 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
。
免责声明
这是为了提供一个关于浮点编码如何工作的简单易懂的解释。这是一种简化,它不涵盖真正的 IEEE 754 浮点标准的任何技术方面(归一化、带符号的零、无穷大、NaN、舍入等)。不过,这里提出的思路是正确的。
理解浮点数的工作原理受到严重阻碍,因为计算机使用基数 2
中的数字,而人类不容易处理它们。我将尝试使用基数 10
.
来解释浮点数是如何工作的
让我们使用符号和基数 10
数字(即我们每天使用的从 0
到 9
的常用数字)构造一个浮点数表示法。
假设我们有 10
个正方形单元格,每个单元格可以包含一个符号(+
或 -
)或一个十进制数字(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
)的值为+150
,指数(E
)的值为+1
。上面表示的值为:
V = M * 10^E = 150 * 10^1 = 1500
范围
整数表示可以存储-(10^9-1)
(-999,999,999
)和+(10^9-1)
(+999,999,999
)之间的有符号值。此外,它可以表示这些限制之间的每个整数值。更重要的是,每个值都有一个表示,而且是准确的。
浮点表示可以存储 -999,999
和 +999,999
之间的尾数 (M
) 和 -99
之间的指数 (E
) 的有符号值和 +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
([=63的后3位=] 无法放入 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
和 [=73] 之间的所有值生成相同的表示=](整数或实数值,没关系)。
结论
对于大值(大于M
的最大值),浮点表示法在它能表示的数字之间有间隙。随着 E
值的增加,这些差距变得越来越大。
“浮点数”的整个思想就是存储十几个最有代表性的数字(数字的开头)和数字的大小。
我们以光速为例。它的价值约为300,000 km/s
。如此庞大,出于大多数实际目的,您不关心它是 300,000.001 km/s
还是 300,000.326 km/s
.
事实上,它甚至没有那么大,更好的近似是 299,792.458 km/s
.
浮点数提取了光速的重要特征:其大小为数十万km/s(E=5
),其值为3
(百数以千计 km/s).
speed of light = 3*10^5 km/s
我们的浮点表示可以近似为:299,792 km/s
(M=299,792
, E=0
).
What kind of magic is happening ???
让你代表101位数字的同一种魔法
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
为
1.0 * 10<sup>100</sup>
它只是以 2 为基数,而不是以 10 为基数:
0.57149369564113749110789177415267 * 2<sup>333</sup>
。
此表示法允许您以紧凑的方式表示非常大(或非常小)的值。不是存储每个数字,而是存储 significand(a.k.a。尾数或分数)和 exponent。这样,可以用仅占用 64 位的格式表示数百位十进制数字。
它是允许浮点数表示如此大的范围值的指数。指数值1024
只需要10位来存储,而2<sup>1024</sup>
是一个308位的数。
权衡是并非每个值都可以准确表示。对于 64 位整数,0
和 2<sup>64</sup>-1
(或 -2<sup>63</sup>
到 2<sup>63</sup>-1
) 有一个精确的表示。由于多种原因,浮点数并非如此。首先,你只有这么多位,只能给你这么多位数的精度。例如,如果您只有 3 位有效数字,那么您不能表示 0.123 和 0.124、或 1.23 和 1.24、或 123 和 124、或 1230000 和 1240000 之间的值。当您接近范围的边缘时,可表示值之间的差距变大。
其次,就像有些值不能用有限的数字表示一样(3/10
给出了非终止序列0.33333...<sub>10</sub>
),有些值无法用有限的位数表示(1/10
给出了非终止序列1.100110011001...<sub> 2</sub>
)。
问题是,我不太明白为什么 double 可以存储比 unsigned long long 更大的数字。由于它们都是8字节长,所以都是64位。
在 unsigned long long 中,所有 64 位都用于存储值,另一方面,double 的符号为 1,指数为 11,尾数为 52。即使用于尾数的 52 位将用于存储没有浮点数的十进制数,它仍然有 63 位 ...
但是 LLONG_MAX 明显小于 DBL_MAX ...
为什么?
原因是 unsigned long long
将存储 精确的 整数,而 double
存储一个尾数(具有有限的 52 位精度)和一个指数。
这允许 double
存储非常大的数字(大约 10308)但不完全。 double
中有大约 15(将近 16)个有效的十进制数字,其余 308 个可能的小数是零(实际上未定义,但您可以假设 "zero" 以便更好地理解)。
一个unsigned long long
只有19位数字,但每一位都是精确定义的。
编辑:
在回复下面的评论 "how does this exactly work" 时,您有 1 位符号,11 位指数和 52 位尾数。尾数在开头有一个隐含的“1”位,未存储,因此 有效地 你有 53 个尾数位。 253 是 9.007E15,所以你有 15,几乎 16 个十进制数字可以使用。
指数有一个符号位,范围从-1022到+1023,用于缩放(二进制左移或右移)尾数(21023大约是10307,因此存在范围限制),因此非常小和非常大的数字同样可以使用这种格式。
但是,当然,您可以表示的所有数字都只能具有适合 matissa 的精度。
总而言之,浮点数不是很直观,因为 "easy" 十进制数根本不一定表示为浮点数。这是因为尾数是二进制的。例如,可以(并且很容易)以完美的精度表示高达几十亿的任何正整数,或者像 0.5 或 0.25 或 0.0125 这样的数字。
另一方面,也可以表示像 10250 这样的数字,但只是近似值。事实上,你会发现 10250 和 10250+1 是同一个数字(等等,什么???)。这是因为虽然您可以轻松拥有 250 位数字,但您没有那么多 重要的 位数字(将 "significant" 读作 "known" 或 "defined")。
此外,表示像 0.3 这样看似简单的东西 也 只是近似的,即使 0.3 甚至不是 "big" 数字。但是,你不能用二进制表示 0.3,而且无论你附加什么二进制指数,你都不会找到任何结果正好是 0.3 的二进制数(但你可以非常接近)。
一些 "special values" 保留给 "infinity"(正面和负面)以及 "not a number",因此您 非常少 比总的理论范围。
另一方面,unsigned long long
不以任何方式解释位模式。您可以表示的所有数字都只是位模式所表示的确切数字。每个数字的每个数字都是精确定义的,没有缩放。
IEEE754 浮点值可以存储更大的范围 数字,仅仅是因为它们牺牲了精度。
我的意思是 64 位整数类型可以表示其范围内的每个值,但 64 位双精度类型不能。
例如,尝试将 0.1
存储到双精度数实际上不会 给 你 0.1
,它会给你类似的东西:
0.100000001490116119384765625
(这实际上是最接近的 单精度 精度值,但同样的效果将适用于双精度)。
但是,如果问题是 "how do you get a larger range with fewer bits available to you?",那只是其中一些位用于缩放值。
经典示例,假设您有四位十进制数字来存储一个值。使用整数,您可以表示 0000
到 9999
之间的数字。该范围内的精度是完美的,您可以表示每个整数值。
但是,让我们使用浮点数并使用最后一位数字作为比例,以便数字 1234
实际上代表数字 123 x 10<sup>4</sup>
.
所以现在你的范围是从0
(代表0000
到0009
)到999,000,000,000
(代表9999
是 999 x 10<sup>9</sup>
).
但是您无法表示该范围内的每个数字。比如123,456
是不能表示的,你能得到的衣橱里的数字是1233
,它给你的是123,000
。而且,事实上,整数值的精度为四位,现在只有三位。
这就是 IEEE754 的基本工作原理,牺牲了范围的精度。
Damon 和 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 或更少。
也许您觉得 "storing a number in N bits" 是最基本的东西,其实有多种方法可以做到。事实上,更准确的说法是我们 表示 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
。
免责声明
这是为了提供一个关于浮点编码如何工作的简单易懂的解释。这是一种简化,它不涵盖真正的 IEEE 754 浮点标准的任何技术方面(归一化、带符号的零、无穷大、NaN、舍入等)。不过,这里提出的思路是正确的。
理解浮点数的工作原理受到严重阻碍,因为计算机使用基数 2
中的数字,而人类不容易处理它们。我将尝试使用基数 10
.
让我们使用符号和基数 10
数字(即我们每天使用的从 0
到 9
的常用数字)构造一个浮点数表示法。
假设我们有 10
个正方形单元格,每个单元格可以包含一个符号(+
或 -
)或一个十进制数字(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
)的值为+150
,指数(E
)的值为+1
。上面表示的值为:
V = M * 10^E = 150 * 10^1 = 1500
范围
整数表示可以存储-(10^9-1)
(-999,999,999
)和+(10^9-1)
(+999,999,999
)之间的有符号值。此外,它可以表示这些限制之间的每个整数值。更重要的是,每个值都有一个表示,而且是准确的。
浮点表示可以存储 -999,999
和 +999,999
之间的尾数 (M
) 和 -99
之间的指数 (E
) 的有符号值和 +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
([=63的后3位=] 无法放入 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
和 [=73] 之间的所有值生成相同的表示=](整数或实数值,没关系)。
结论
对于大值(大于M
的最大值),浮点表示法在它能表示的数字之间有间隙。随着 E
值的增加,这些差距变得越来越大。
“浮点数”的整个思想就是存储十几个最有代表性的数字(数字的开头)和数字的大小。
我们以光速为例。它的价值约为300,000 km/s
。如此庞大,出于大多数实际目的,您不关心它是 300,000.001 km/s
还是 300,000.326 km/s
.
事实上,它甚至没有那么大,更好的近似是
299,792.458 km/s
.
浮点数提取了光速的重要特征:其大小为数十万km/s(E=5
),其值为3
(百数以千计 km/s).
speed of light = 3*10^5 km/s
我们的浮点表示可以近似为:299,792 km/s
(M=299,792
, E=0
).
What kind of magic is happening ???
让你代表101位数字的同一种魔法
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
为
1.0 * 10<sup>100</sup>
它只是以 2 为基数,而不是以 10 为基数:
0.57149369564113749110789177415267 * 2<sup>333</sup>
。
此表示法允许您以紧凑的方式表示非常大(或非常小)的值。不是存储每个数字,而是存储 significand(a.k.a。尾数或分数)和 exponent。这样,可以用仅占用 64 位的格式表示数百位十进制数字。
它是允许浮点数表示如此大的范围值的指数。指数值1024
只需要10位来存储,而2<sup>1024</sup>
是一个308位的数。
权衡是并非每个值都可以准确表示。对于 64 位整数,0
和 2<sup>64</sup>-1
(或 -2<sup>63</sup>
到 2<sup>63</sup>-1
) 有一个精确的表示。由于多种原因,浮点数并非如此。首先,你只有这么多位,只能给你这么多位数的精度。例如,如果您只有 3 位有效数字,那么您不能表示 0.123 和 0.124、或 1.23 和 1.24、或 123 和 124、或 1230000 和 1240000 之间的值。当您接近范围的边缘时,可表示值之间的差距变大。
其次,就像有些值不能用有限的数字表示一样(3/10
给出了非终止序列0.33333...<sub>10</sub>
),有些值无法用有限的位数表示(1/10
给出了非终止序列1.100110011001...<sub> 2</sub>
)。