可存储在double类型中的最大整数

什么是最大的“无浮动”;可以存储在IEEE 754双类型而不损失精度的整数?

换句话说,at将下面的代码片段返回:

UInt64 i = 0;
Double d = 0;


while (i == d)
{
i += 1;
d += 1;
}
Console.WriteLine("Largest Integer: {0}", i-1);
256171 次浏览

维基百科在相同的上下文中给出了IEEE 754的链接:

在典型的计算机系统中,“双精度”(64位)二进制浮点数的系数为53位(其中一个是隐含的),指数为11位,以及一个符号位。

2^53略大于9 * 10^15。

可以存储在double类型中而不损失精度的最大/最大整数与double类型的最大可能值相同。即DBL_MAX或大约1.8 × 10308(如果你的双精度是IEEE 754 64位双精度)。它是一个整数。它被准确地表示出来了。你还想要什么?

继续,问我最大的整数是多少,这样和所有较小的整数可以存储在IEEE 64位双精度中而不损失精度。IEEE 64位双精度数有52位尾数,所以我认为它是253:

  • 253 + 1不能被存储,因为开头的1和结尾的1之间有太多的0。
  • 任何小于253的值都可以存储,尾数中显式存储52位,然后指数实际上会给你另一个。
  • 253显然可以被存储,因为它是2的小次幂。

或者另一种看待它的方式:一旦偏离指数,忽略与问题无关的符号位,双精度数存储的值是2的幂,加上一个52位整数乘以2exponent −52。因此,使用指数52可以存储从252到253 − 1的所有值。那么对于指数53,你可以存储在253之后的下一个数字是253 + 1 × 253−52。所以精度损失首先发生在253 + 1。

你得看看尾音的大小。IEEE 754 64位浮点数(包含52位,加1)可以精确地表示绝对值小于或等于2^53的整数。

9007199254740992(即9,007,199,254,740,992或2^53),没有保证:)

程序

#include <math.h>
#include <stdio.h>


int main(void) {
double dbl = 0; /* I started with 9007199254000000, a little less than 2^53 */
while (dbl + 1 != dbl) dbl++;
printf("%.0f\n", dbl - 1);
printf("%.0f\n", dbl);
printf("%.0f\n", dbl + 1);
return 0;
}

结果

9007199254740991
9007199254740992
9007199254740992

在IEEE 754 double(64位)中可以表示的最大整数与该类型可以表示的最大值相同,因为该值本身就是一个整数。

这被表示为0x7FEFFFFFFFFFFFFF,它由:

  • 符号位0(正)而不是1(负)
  • 最大指数0x7FE(2046表示减去偏置后的1023)而不是0x7FF(2047表示NaN或无穷大)。
  • 最大尾数0xFFFFFFFFFFFFF,它是52位全1。

在二进制中,值是隐式的1,后面是尾数中的另外52个1,然后是指数中的971个0(1023 - 52 = 971)。

精确的十进制值为:

179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368

这大约是1.8 x 10308

的确,对于64位的IEEE754双精度,所有到9007199254740992 == 2^53的整数都可以精确表示。

然而,值得一提的是,所有超出4503599627370496 == 2^52的可表示数字都是整数。 超过2^52,测试它们是否是整数就没有意义了,因为它们都隐式舍入到附近的可表示值

在2^51到2^52的范围内,唯一的非整数值是以“。5”结尾的中点,这意味着计算后的任何整数测试都必须产生至少50%的错误答案。

在2^51以下,我们还有".25"和“。75”,所以比较一个数字和它的四舍五入的对应数字,以确定它是否可能是整数开始是有意义的。

TLDR:如果您想测试计算结果是否可能是整数,请避免大于2251799813685248 == 2^51的数字

正如其他人所指出的,我将假设OP要求最大的浮点值,以便所有小于其本身的整数都可以精确表示。

你可以使用在float.h中定义的FLT_MANT_DIGDBL_MANT_DIG来不依赖于显式值(例如,53):

#include <stdio.h>
#include <float.h>


int main(void)
{
printf("%d, %.1f\n", FLT_MANT_DIG, (float)(1L << FLT_MANT_DIG));
printf("%d, %.1lf\n", DBL_MANT_DIG, (double)(1L << DBL_MANT_DIG));
}

输出:

24, 16777216.0
53, 9007199254740992.0

更新1:

刚刚意识到5 ^ 1074<强> < / >强不,这是你可以从IEEE 754双精度浮点中免费获得的真正上限,因为我只计算了非规整指数,忘记了尾数本身可以适合另外22次5的事实,所以据我所知,一个人可以从双精度格式中免费获得的5的最大次幂是:

5的最大次方:

  • 5 ^ 1096

最大奇数:

  • < p > 5 ^ 1074 x 9007199254740991

  • < p > 5 ^ 1074 x (2 ^ 53 - 1)

mawk 'BEGIN { OFS = "\f\r\t";


CONVFMT = "IEEE754 :: 4-byte word :: %.16lX";
   

print "",
sprintf("%.*g", __=(_+=_+=_^=_<_)^++_+_*(_+_),
___=_=((_+_)/_)^-__),   (_ ""),
\
sprintf("%.*g",__,_=_*((_+=(_^=!_)+(_+=_))*_\
)^(_+=_++)), (_ ""),
\
sprintf("%.*g",__,_=___*=  \
(_+=_+=_^=_<_)^--_^_/--_-+--_), (_ "") }'
  • < p > 4.940656458412465441765687928682213723650598026143247644255856825006755072702087518652998363616359923797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036887186360569987307230500063874091535649843873124733972731696151400317153853980741262385655911710266585566867681870395603106249319452715914924553293054565444011274801297099995419319894090804165633245247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431936092382893458368060106011506169809753078342277318329247904982524730776375927247874656084778203734469699533647017972677717585125660551199131504891101451037862738167250955837389733598993664809941164205702637090279242767544565229087538682506419718265533447265625e-324

      — IEEE754 :: 4-byte word :: 0000000000000001
    
    
    494065645841246544176568792......682506419718265533447265625 } 751 dgts :
    5^1,074
    
  • < p > 1.1779442926436580280698985883431944188238616052015418158187524855152976686244219586021896275559329804892458073984282439492384355315111632261247033977765604928166883306272301781841416768261169960586755720044541328685833215865788678015827760393916926318959465387821953663477851727634395732669139543975751084522891987808004020022041120326339133484493650064495265010111570347355174765803347028811562651566216206901711944564705815590623254860079132843479610128658074120767908637153514231969910697784644086106916351461663273587631725676246505444808791274797874748064938487833137213363849587926231550453981511635715193075144590522172925785791614297511667878003519179715722536405560955202126362715257889359212587458533154881546706053453699158950485070818103849887847900390625e-308

      — IEEE754 :: 4-byte word :: 000878678326EAC9
    
    
    117794429264365802806989858......070818103849887847900390625 } 767 dgts :
    5^1,096
    
  • < p > 4.4501477170144022721148195934182639518696390927032912960468522194496444440421538910330590478162701758282983178260792422137401728773891892910553144148156412434867599762821265346585071045737627442980259622449029037796981144446145705102663115100318287949527959668236039986479250965780342141637013812613333119898765515451440315261253813266652951306000184917766328660755595837392240989947807556594098101021612198814605258742579179000071675999344145086087205681577915435923018910334964869420614052182892431445797605163650903606514140377217442262561590244668525767372446430075513332450079650686719491377688478005309963967709758965844137894433796621993967316936280457084866613206797017728916080020698679408551343728867675409720757232455434770912461317493580281734466552734375e-308

      — IEEE754 :: 4-byte word :: 001FFFFFFFFFFFFF
    
    
    445014771701440227211481959......317493580281734466552734375 } 767 dgts :
    5^1,074
    6361
    69431
    20394401
    

下面是一个快速的awk代码片段,它可以打印出2到1023的每一个正幂,5到1096的每一个正幂,以及它们的共同幂为零,对有和没有bigint库都进行了优化:

{m,g,n}awk' BEGIN {


CONVFMT = "%." ((_+=_+=_^=_<_)*_+--_*_++)(!++_) "g"
OFMT = "%." (_*_) "g"


if (((_+=_+_)^_%(_+_))==(_)) {
print __=_=\
int((___=_+=_+=_*=++_)^!_)
OFS = ORS
while (--___) {
print int(__+=__), int(_+=_+(_+=_))
}
__=((_+=_+=_^=!(__=_))^--_+_*_) substr("",_=__)
do {
print _+=_+(_+=_) } while (--__)
exit
} else { _=_<_ }


__=((___=_+=_+=++_)^++_+_*(_+_--))
_=_^(-(_^_--))*--_^(_++^_^--_-__)
_____=-log(_<_)
__^=_<_
___=-___+--___^___


while (--___) {
print ____(_*(__+=__+(__+=__))) }
do {
print ____(_) } while ((_+=_)<_____)
}


function ____(__,_) {
return (_^=_<_)<=+__ \
?              sprintf( "%.f", __) \
: substr("", _=sprintf("%.*g", (_+=++_)^_*(_+_),__),
gsub("^[+-]*[0][.][0]*|[.]|[Ee][+-]?[[:digit:]]+$","",_))_
}'

=============================

这取决于你对“代表”的定义有多灵活。和“;representable"-

不管一般文献怎么说,实际上“最大”的整数;在IEEE 754 double precision没有中的任何bigint库或外部函数调用,具有完整的可计算,可存储和可打印的完整的尾数,实际上是:

9,007,199,254,740,991 * 5 ^ 1074 (~2546.750773909... bits)

  4450147717014402272114819593418263951869639092703291
2960468522194496444440421538910330590478162701758282
9831782607924221374017287738918929105531441481564124
3486759976282126534658507104573762744298025962244902
9037796981144446145705102663115100318287949527959668
2360399864792509657803421416370138126133331198987655
1545144031526125381326665295130600018491776632866075
5595837392240989947807556594098101021612198814605258
7425791790000716759993441450860872056815779154359230
1891033496486942061405218289243144579760516365090360
6514140377217442262561590244668525767372446430075513
3324500796506867194913776884780053099639677097589658
4413789443379662199396731693628045708486661320679701
7728916080020698679408551343728867675409720757232455
434770912461317493580281734466552734375

我使用xxhash将其与gnu-bc进行比较,并确认它确实是相同的,并且没有丢失精度。没有什么是“非正规化”的;关于这个数字,尽管指数范围被这样标记。

如果你不相信,在你自己的系统上试试。(我通过现成的mawk得到了这个打印出来)-你也可以很容易地得到它:

  1. 1(1)幂/次幂(^ aka **) op,
  2. 1(1)乘法运算(*) op,
  3. 一个(1)sprintf()调用,和
  4. 任一(1)的 - substr()或regex- __abc1 执行必要的清理

就像我们经常提到的1.79…E309数字,

  • 都是尾数有限公司
  • 两者都是指数受限的
  • 两者都有大得离谱的ULPs (unit in last place)
  • 两者都离“压倒性”只有一步之遥。浮点单元通过溢出或下溢来返回一个可用的答案

对工作流的二进制指数求反,你就可以完全在这个空间中完成操作,然后在工作流的尾部再次反转它,回到我们通常认为“较大”的一侧,

but keep in mind that in the inverted
exponent realm, there's no "gradual overflow"

- 4Chan出纳员

双份,“简单”;解释

最大的“双”;Number(双精度浮点数)通常是64位或8字节的数字,表示为:

1.79E308
or
1.79 x 10 (to the power of) 308

正如你可以猜到的,10的308次方是一个巨大的数字,就像170000000000000000000000000000000000000000000000000000000甚至更大!

在天平的另一端,双精度浮点64位数字支持微小的小数小数,使用“。”;最小的符号:

4.94E-324
or
4.94 x 10 (to the power of) -324

任何数乘以10的负次方都是很小很小的小数,比如0.00000000000000000000000000000000000000494,甚至更小。

但让人们困惑的是,他们会听到计算机书呆子和数学专家说,“但这个数字的范围只有15个数字值”。事实证明,上面描述的值是计算机可以存储并从内存中显示的全部最大值和最小值。但在它们变得这么大之前,它们就失去了准确性和创造数字的能力。因此,大多数程序员都避免使用最大的双位数,尽量保持在一个已知的、小得多的范围内。

但是为什么呢?最好的的最大双位数是多少?我在网上数学网站上阅读了几十个糟糕的解释,却找不到答案。所以下面这个简单的解释可能会对你有所帮助。它帮助了我!!

加倍事实和缺陷

JavaScript(它也使用64位双精度存储系统来存储计算机中的数字)使用双精度浮点数来存储所有已知的数值。因此,它使用如上所示的相同的MAX和MIN范围。但是大多数语言使用带范围的类型化数字系统来避免精度问题。然而,双位数和浮点数存储系统似乎都有相同的缺陷,即随着数值变大或变小而丢失精度。我将解释为什么它会影响“最大”的概念;值……

为了解决这个问题,JavaScript有一个所谓的号码。MAX_SAFE_INTEGER值,即9007199254740991。这是它可以代表整数的最准确的数字,但不是可以存储的最大数字。它是准确的,因为它保证任何等于或小于该值的数字都可以被查看、计算、存储等。在这个范围之外,还有“missing"数字。原因是双精度数AFTER 9007199254740991使用一个额外的数字将它们乘成越来越大的值,包括1.79E308的真实最大值。这个新数字被称为指数

邪恶指数

事实上,这个9007199254740991的最大值也是你可以存储在64位存储系统中使用的53位计算机内存中的最大值。这个存储在内存中53位的9007199254740991数是可以直接存储在JavaScript使用的典型双精度浮点数的内存尾数部分中的最大值。

顺便说一下,9007199254740991是一种我们称之为Base10或十进制(人类使用的数字)的格式。但是它也像这个值一样以53位的形式存储在计算机内存中…

11111111111111111111111111111111111111111111111111111

这是计算机使用64位数字存储系统实际可以存储的双精度数字的整数部分的最大位数。

为了得到更大的最大值(1.79E308), JavaScript必须使用一个额外的技巧,称为指数,将其乘以越来越大的值。因此,在上面的计算机内存中,在53-bit mantissa值旁边有一个11-bit exponent数字,它允许该数字变得更大或更小,从而创建了期望双精度数表示的最终范围。(同样,正数和负数也只有一个位。)

当计算机达到这个最大整数值的限制(大约9千万亿)并用53位填充内存的尾数部分后,JavaScript指数使用一个新的11位存储区域,它允许更大的整数增长(到10的308次方!)和更小的小数变得更小(10的-324次方!)因此,这个指数数字允许使用浮动基数或小数点来创建整个范围的大小小数,以上下移动数字,创建您期望看到的复杂分数或十进制值。同样,这个指数是另一个以11位存储的大数字,它本身的最大值为2048

你会注意到9007199254740991是一个最大整数,但没有解释存储中可能的更大的max值或MINIMUM十进制数,甚至没有解释十进制分数是如何创建和存储的。这个计算机位值是如何创造这一切的?

答案同样是,通过指数!

事实证明,指数11位值本身被分为正数和负数,因此它可以创建大整数,也可以创建小小数。

为此,它有自己的正负范围,通过从它的2048 max值中减去1024来获得从+1023-1023的新值范围(减去0的保留值)来创建正/负指数范围。为了得到最终的双数字,尾数 (9007199254740991)乘以指数(加上单位符号)得到最终值!这允许指数尾数的值乘到超过9千万亿的更大的整数范围,但也可以相反地将小数乘到非常小的分数。

然而,存储在指数中的-+1023数字不会与尾数相乘以获得双精度,而是用于将数字2提高到指数的幂。该指数是一个十进制数,但不适用于十进制指数,如10的次方或1023。它再次应用于Base2系统,并创建值2 to the power of (the exponent number)

然后,生成的值与尾数相乘,以获得允许存储在JavaScript中的MAX和MIN数字,以及范围内所有较大和较小的值。它使用"2"而不是精确的10,所以指数值每增加一次,尾数值只增加一倍。这减少了数字的损失。但是这个指数乘法器也意味着它将失去一个增加的双倍范围的数字增长,当你达到最大存储指数和尾数可能的点,非常大的数字从最终计算的数字消失,所以某些数字现在是不可能的数学计算!

这就是为什么大多数人使用安全最大整数范围(9007199254740991或更小),因为大多数人都知道JavaScript中非常大和很小的数字是非常不准确的!还要注意,2的-1023次方得到最小值或与典型的“浮点数”相关联的小小数。因此,指数用于将尾数整数转换为非常大和非常小的数字,直到它可以存储的最大值和最小值范围。

注意,2 to power of 1023转换为十进制指数,使用10 to the power of 308作为最大值。这使您可以看到数值在Human值,或Base10数值格式的二进制计算。数学专家通常不会解释所有这些值都是相同的数字,只是不同的进制或格式。

double的真正最大值是无穷大

最后,当整数达到可能的最大数或可能的最小小数部分时会发生什么?

事实证明,双精度浮点数为64位指数和尾数保留了一组位值,以存储其他四种可能的数字:

  1. +∞
  2. + 0
  3. -0

例如,存储在64位内存中的双位数中的+0是计算机内存中的一大行空位。下面是在使用双精度浮点数时超出可能的最小小数(4.94E-324)后发生的情况。它在内存耗尽后变成+0 !计算机将返回+0,但在内存中存储0位。下面是全64位的存储设计在位双计算机内存。第一个位控制积极的数字的+(0)或-(1),接下来是11位的指数(所有的零都是0,所以变成了2 to the power of 0 = 1),而53位的大块用于+00或+01,它表示0。所以+0用全0表示!

0 00000000000 0000000000000000000000000000000000000000000000000000

如果双精度数达到正的最大值或最小值,或者达到负的最大值或最小值,许多语言总是以某种形式返回这些值之一。但是,有些会返回NaN、溢出、异常等。如何处理是另一回事。但通常这四个值是double的TRUE最小值和最大值。通过返回不合理的值,您至少可以用双精度值表示最大值和最小值,这解释了不能合理存储或解释的双精度类型的最后一种形式。

总结

所以正double和负double的MAXIMUM和MINIMUM范围如下:

MAXIMUM TO MINIMUM POSITIVE VALUE RANGE
1.79E308 to 4.94E-324 (+Infinity to +0 for out of range)


MAXIMUM TO MINIMUM NEGATIVE VALUE RANGE
-4.94E-324 to -1.79E308 (-0 to -Infinity for out of range)


But the SAFE and ACCURATE MAX and MIN range is really:
9007199254740991 (max) to -9007199254740991 (min)


所以你可以看到+-∞和+-0添加,双精度有额外的最大和最小范围,以帮助你当你超过最大和分钟。

如上所述,当你从最大的正数值到最小的十进制正数值或分数时,位归零,你得到0 Past 4.94E-324这个双精度数不能存储任何更小的十进制分数值,所以它在位注册表中崩溃为+0。同样的事件也发生在微小的负小数上,它们的值超过了-0。正如你所知道的-0 = +0,所以虽然内存中存储的值不相同,但在应用程序中它们经常被强制为0。但是请注意,许多应用程序确实交付了带符号的0 !

大数值则相反……在1.79E308之前,它们会变成+∞和-∞。这就是在JavaScript等语言中创建所有奇怪数字范围的原因。双精度数字有奇怪的返回!

请注意,小数/分数的最小安全范围没有显示在上面,因为它根据分数所需的精度而变化。当您将整数与小数部分组合在一起时,小数点的精度会随着它变小而迅速下降。网上对此有很多讨论和辩论。没有人知道答案。下面的列表可能会有所帮助。如果希望保证精度,可能需要将列出的这些范围更改为更小的值。如您所见,如果您想在浮点数中支持高达小数点后9位的精度,则需要将尾数中的MAX值限制为这些值。精确是指你需要精确的小数点后多少位。不安全的意思是超过这些值,数字将失去精度,并有缺失的数字:

            Precision   Unsafe
1           5,629,499,534,21,312
2           703,687,441,770,664
3           87,960,930,220,208
4           5,497,558,130,888
5           68,719,476,736
6           8,589,934,592
7           536,870,912
8           67,108,864
9           8,388,608

我花了一段时间来理解双精度浮点数和计算机的真正限制。在网上阅读了许多数学专家的大量困惑之后,我创建了上面这个简单的解释,他们擅长创造数字,但不擅长解释任何事情!我希望我对你的编程之旅有所帮助-和平:)