问题是,我不太明白为什么 double 可以存储比 unsigned long long 更大的数字。 因为它们都是8个字节长,即64位。
在 unsigned long long 中,所有64位都被用来存储值,而另一方面,double 带有1个符号位、11个指数位和52个尾数位。即使使用了52位用于存储没有浮点的小数,它仍然拥有63位……
但 LLONG_MAX 比 DBL_MAX 显着更小……
为什么呢?
问题是,我不太明白为什么 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
可以存储非常大的数字(约为10的308次方),但不是精确的。在double
中,您有大约15(几乎16)个有效十进制数字,其余308个可能的小数位都是零(实际上是未定义的,但您可以假设为“零”以更好地理解)。unsigned long long
只有19位数字,但每一位数字都是精确定义的。
编辑:unsigned long long
不对比特模式进行任何解释。所有你可以表示的数字都只是表示该比特模式所代表的确切数字。每个数字的每一位都是精确定义的,没有进行任何缩放。double
的 15-16 个有效十进制数字和 unsigned long long
的 19 个精确数字的陈述的快速演示:http://ideone.com/8igfpt - axiacIEEE754浮点数之所以能够存储更大的数字范围,是因为它们牺牲了精度。
也就是说,一个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、舍入等)的技术方面。然而,此处提出的想法是正确的。
由于计算机使用二进制,在理解浮点数的工作原理时受到了严重阻碍,而人类很难处理它们。我将尝试使用十进制来解释浮点数的工作原理。
让我们构建一个使用符号和基于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
)之间的有符号值。此外,它可以表示这些限制之间的每个整数值。更重要的是,每个值都有一个单一的表示,并且是精确的。
浮点表示法可以存储幂指数(E
)介于-99
和+99
之间,尾数(M
)介于-999,999
和+999,999
之间的有符号值。
它可以存储介于-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
(V
的最后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
和+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千米/秒
(M=299,792
,E=0
)。10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
as
1.0 * 10100
这只是在进行二进制计算时,使用的是基数2而非10:
0.57149369564113749110789177415267 * 2333
.
此表示法能够以紧凑的方式表示非常大(或非常小)的值。您只需存储有效数字(也称为尾数或分数)和指数,而不是每个数字。因此,长度为数百个十进制数字的数字可以用仅占用64位的格式表示。
正是指数使浮点数能够表示如此大范围的值。指数值1024
只需要10位即可存储,但21024
是一个308位数字。
不过,这种表示法的缺点是并不能精确地表示每个值。使用64位整数,介于0
和264-1
之间的每个值(或者介于-263
和263-1
之间的每个值)都有精确的表示法。然而对于浮点数来说,并非如此,原因有几个。首先,您只有那么多位数,因此只能表示有限的小数位数。例如,如果您只有三个有效数字,则无法表示介于0.123和0.124、1.23和1.24、123和124、1230000和1240000之间的值。随着您接近您的范围边缘,可表示值之间的差距变得更大。
其次,就像有些值不能在有限的十进制数字中表示一样(3/10
给出非终止序列0.33333...10
),有些值也不能在有限的位数中表示(1/10
给出非终止序列1.100110011001...2
)。
unsigned long long
和其他整数类型的约定,还有用于double
的尾数+指数约定,但我们也可以定义自己的(荒谬的)约定,例如所有比特都是零意味着任何你想指定的巨大数字。在实践中,我们通常使用允许我们使用我们运行程序的硬件有效地组合(相加、相乘等)数字的约定。2^exponent * mantissa
的最大数字,其中exponent
和mantissa
是E位和M位二进制数(在尾数开头隐含1)。这是2^(2^E-1) * (2^M - 1)
,通常确实远大于2^N - 1
。以下是 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位或更少的情况下,这两个变量将以相同的方式递增。