为什么double可以存储比unsigned long long更大的数字?

问题是,我不太清楚为什么double可以存储比unsigned long long更大的数字。 由于它们都是8字节长,所以64位。

在无符号长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位数,但它们中的每一位都是精确定义的。

编辑:
在回复下面的注释“这是如何工作”时 ,符号为1位,指数为11位,尾数为52位。 尾数在开头有一个隐含的“1”位,没有存储,所以有效地你有53个尾数位。 2 53是9.007E15,因此您有15个,几乎16个十进制数字可供使用。
指数有一个符号位,范围从-1022到+1023,用于缩放(二进制左移或右移)尾数(2 1023约为10 307 ,因此限制范围),所以非常小,这种格式同样可以使用非常大的数字。
但是,当然,您可以表示的所有数字都只具有适合matissa的精度。

总而言之,浮点数不是很直观,因为“简单”十进制数不一定表示为浮点数。 这是因为尾数是二进制的。 例如,可以(并且很容易)表示任何高达几十亿的正整数,或者数字如0.5或0.25或0.0125,具有完美的精度。
另一方面,也可以表示像10 250这样的数字,但只能大约表示。 事实上,你会发现10 250和10 250 +1是相同的数字(等待,什么???)。 这是因为尽管您可以轻松拥有250个数字,但您没有那么多有效数字(将“重要”视为“已知”或“已定义”)。
此外,尽管0.3甚至不是“大”数字,但代表看起来像0.3这样简单的东西也是可能的。 但是,你不能用二进制表示0.3,无论你附加什么二进制指数,你都不会发现任何二进制数正好导致0.3(但你可以非常接近)。

一些“特殊值”保留为“无穷大”(正面和负面)以及“非数字”,因此您的理论范围小于理论范围。

另一方面, unsigned long long不会以任何方式解释位模式。 您可以表示的所有数字都只是位模式表示的确切数字。 每个数字的每个数字都是精确定义的,不会发生缩放。

IEEE754浮点值可以存储更大范围的数字,因为它们牺牲了精度。

通过这种方式,我的意思是64位整数类型可以表示其范围内的每个单独值,但64位双精度表示不能。

例如,尝试将0.1存储为双精度实际上不会0.1 ,它会给你类似的东西:

 0.100000001490116119384765625 

(这实际上是最接近的精度值,但同样的效果将适用于双精度)。


但是,如果问题是“如何使用更少的可用位来获得更大的范围?”,则只需使用其中一些位来扩展该值。

经典示例,假设您有四位十进制数来存储值。 使用整数,您可以表示数字00009999含)。 该范围内的精度是完美的,您可以表示每个积分值。

但是,让我们去浮点并使用最后一位作为比例,以便数字1234实际上代表数字123 x 10 4

所以现在你的范围从0 (由00000009表示)到999,000,000,000 (由9999表示为999 x 10 9 )。

但是你不能代表该范围内的每个数字。 例如,无法表示123,456 ,您可以获得的数字1233123,000 。 事实上,在整数值精度为四位数的情况下,现在只有三位数。

基本上是IEEE754的工作方式,牺牲了范围的精度。

放弃

这是尝试提供有关浮点编码如何工作的易于理解的解释。 这是一种简化,它不包括真正的IEEE 754浮点标准的任何技术方面(归一化,有符号零,无穷大,NaN,舍入等)。 但是,这里提出的想法是正确的。


理解浮点数如何工作受到严重阻碍,因为计算机使用基数为2数字,而人类不能轻易处理它们。 我将尝试解释浮点数如何使用基数10

让我们使用符号和基数10位数来构造浮点数表示(即我们每天​​使用的09的常用数字)。

假设我们有10方格单元格,每个单元格可以包含符号( +- )或十进制数字( 09 )。

我们可以使用10位数来存储有符号整数。 符号的一位数和值的9位数:

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

这是值1500表示为整数的方式。

我们也可以用它们来存储浮点数。 例如,尾数为7位,指数为3位:

  +------ sign digits --------+ vv +---+---+---+---+---+---+---+---+---+---+ | + | 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)+(10^9-1) -999,999,999 )之间的有符号值。 此外,它可以表示这些限制之间的每个整数值。 更重要的是,每个值都有一个表示,而且确切。

浮点表示可以存储-999,999+999,999之间的尾数( M )以及-99+99之间的指数( E )的有符号值。

它可以存储-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,654E将是+3V的最后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 (整数+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

浮点数提取了光速的重要特征:其幅度为数十万km / s( E=5 ),其值为3 (数十万km / s)。

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

我们的浮点表示可以近似为: 299,792 km/sM=299,792E=0 )。

发生了什么样的魔法?

同样的魔力可以让你代表101位数字

 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 

1.0 * 10 100

它只是代替基数10你在基地2中做到了:

0.57149369564113749110789177415267 * 2 333

此表示法允许您以紧凑的方式表示非常大(或非常小)的值。 您可以存储有效数字(也就是尾数或分数)和指数 ,而不是存储每个数字。 这样,数百个十进制数字长的数字可以用只占64位的格式表示。

它是指数,允许浮点数表示如此大范围的值。 指数值1024仅需要10位来存储,但是2 1024是308位数。

权衡是不是每个值都可以准确表示。 对于64位整数, 02 64 -1 (或-2 632 63 -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

Damon和Paxdiablo解释的一个小例子:

 #include  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或更小。