双精度浮点数可以存储的最大整数

303

在IEEE 754双精度类型中,最大的“no-floating”整数是多少,而不会失去精度?

换句话说,下面这段代码将返回什么:(点击此处查看)

UInt64 i = 0;
Double d = 0;

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

1
"no-floating" → fixed point → “无浮点” → 定点数 - phuclv
关于“最大的“非浮点数”是什么”,所有数字都使用浮点表示,除了+0、-0、非常非常小的次规范数(使用定点表示)、无穷大和NaN。显然这不是你想要问的。你似乎在询问的是,有哪个最大的整数,它和比它小的每个整数都可以被双精度浮点数准确表示。 - ikegami
11个回答

691
可以在不丢失精度的情况下存储在双精度浮点数中的最大整数与双精度浮点数的最大可能值相同。也就是说,如果您的双精度浮点数是IEEE 754 64位双精度浮点数,则为DBL_MAX或约1.8×10308。它是一个整数。它被准确地表示。你还想要什么?
继续问我,最大的整数是多少,以便和所有较小的整数都可以在IEEE 64位双精度浮点数中存储而不会丢失精度。IEEE 64位双精度浮点数具有52位的尾数,因此是253(负数方面是-253):
  • 253 + 1无法存储,因为起始处的1和末尾处的1之间有太多的零。
  • 小于253的任何数都可以存储,尾数中明确存储了52位,然后指数实际上再增加了一位。
  • 显然可以存储253,因为它是一个较小的2的幂。
或者从另一个角度来看:一旦偏差被移除,忽略符号位与问题无关,双精度浮点数存储的值是2的幂次方,加上52位整数乘以2的指数减去52。因此,使用指数为52,您可以存储从2的52次方到2的53次方减1的所有值。然后,使用指数为53,在2的53次方之后,您可以存储下一个数字,即2的53次方加1乘以2的53减去52。因此,精度损失首次发生在2的53次方加1。

184
好的,我会尽力为您翻译。以下是需要翻译的内容:+1 发现问题并非提问者预期的意思,并给出了两种答案(“技术上正确”和“可能预期的”),做得很好。 - Pascal Cuoq
84
或者,正如我倾向于称呼它们的那样,“胡闹”和“试图帮助” :-) - Steve Jessop
9
我向小马托尼鞠躬,不向其他任何人。 - Steve Jessop
13
你的意思不是“所有更小的整数”,而是所有绝对值小于或等于给定数的整数,因为在双精度浮点数中有很多负整数小于2^53,无法准确表示。 - Southern Hospitality
16
我确实是指更小,当我说“更小”时,这就是我的意思 :-) -1,000,000比1小,但它并不“更小”。 - Steve Jessop
显示剩余24条评论

104

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

8
假设它将会是“接近”但小于2的N次方,那么更快的测试方法是 double dbl = 1; while (dbl + 1 != dbl) dbl *= 2; while (dbl == --dbl);,该方法得到与原来相同的结果。 - Seph
4
@Seph 什么情况?不行吗?while (dbl == --dbl)会一直循环,或者根本不会执行。(在这种情况下,根本不会执行,因为它是2的N次幂)。你需要从下面的角度来考虑它。实际上,这也会导致比预期结果少一个(因为while循环中的一个检查会将dbl减少)。而且它取决于执行顺序,如果在评估左侧之前或之后执行减量操作(据我所知,这是未定义的)。如果是前者,它将永远为真并一直循环。 - falstro
17
或许可以在某处指出2^53=9,007,199,254,740,992。 - Xonatron
2
很难反驳这个观点!不错的实验。 - MattM
使用 while (dbl + 1 != dbl) dbl++; 的弱点在于 dbl + 1 != dbl 可能会使用 long double 数学运算 - 考虑 FLT_EVAL_METHOD == 2。这可能会导致无限循环。 - chux - Reinstate Monica
显示剩余4条评论

31

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

这个值用0x7FEFFFFFFFFFFFFF表示,由以下部分组成:

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

在二进制中,该值是隐含的1后跟来自尾数的另外52个1,然后是来自指数的971个零(1023-52 = 971)。

精确的十进制值为:

179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368

这约为1.8 x 10308


2
它能表示的最大值是多少?所有在它和零之间的值都可以连续表示吗? - Aaron Franke
@AaronFranke 这个问题并没有询问连续表示,但是对于那个不同的问题的答案已经包含在大多数其他答案中了,甚至错误地被给出作为实际答案。它是2的53次方(2⁵³)。 - Simon Biber
@AaronFranke:在整个宇宙中,无论有多少尾数也无法“表示出所有值”,从零开始到那个“x”的值之间,除非您已经想出了如何有限地表达超越数。 - RARE Kpop Manifesto
@RAREKpopManifesto 这个问题特指整数,所以在这个语境中,“values” 指的是整数。 - Aaron Franke

29
在与IEEE 754链接的同一上下文中,维基百科这样说道:

在典型的计算机系统上,“双精度”(64位)二进制浮点数具有53位系数(其中一位被隐含),11位指数和一位符号位。

2的53次方略大于9 x 10^15。


@Steve Jessop 大体上来说,这确实是我想表达的。我也遇到过一些没有 FPU 的硬件系统,但仍需要符合 IEEE 标准,所以如果我在8个月后回到这里并需要相同的信息来处理基于68K的微控制器(假设它没有 FPU...我记不清了),那么“典型系统”这种说法对我并没有太大帮助。 - San Jacinto
16
@San Jacinto的回复“这没用”有些过于苛刻。这个答案是相当有用的,只是如果它包括了典型计算机系统确实使用IEEE 754表示的评论,它会更加有用。 - Stephen C. Steel
@Stephen C. Steel,实际上你是正确的。在我的情况下,稍后回来查找IEEE max时,“典型系统”是什么是不可能明确的,但除了这个抱怨之外,答案仍然有价值。 - San Jacinto

9

您需要关注尾数的大小。一个IEEE 754 64 位浮点数(其中有52位,加上1个隐含位)可以准确表示绝对值小于或等于2^53的整数。


8
它也可以精确表示2的53次方 :-) - Steve Jessop

4

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

然而,值得一提的是,所有可表示的数字超过4503599627370496 == 2^52都是整数。在2^52之后,测试它们是否为整数变得毫无意义,因为它们都被隐式地舍入为附近的可表示值。

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

在2^51以下,我们还有".25"和".75",因此比较一个数字与其舍入后的对应数字,以确定它是否为整数开始变得有些合理。

简而言之:如果您想测试计算结果是否可能为整数,请避免使用大于2251799813685248 == 2^51的数字。


2

1.7976931348623157 × 10^308

http://zh.wikipedia.org/wiki/IEEE_754


这是一个代表最大值的数字,它使用双精度浮点格式存储。如果想了解更多关于双精度浮点格式的信息,请访问上述链接。

2
这个回答如果附带引用将更好。 - San Jacinto
2
@Carl 如果整数左侧有零,则它会被精确地存储。 - Wilhelm
4
各位给我负反馈的人:1.7976931348623157 × 10^308 一个精确的整数。难道你们都需要参加补习班吗??请注意,这里不是解释,只是对原文进行翻译。 - Dan Moulding
7
在这个毫无希望的答案讨论中,我们已经陷入了语义学的境地。确实,那个数字可以被精确表示,从而满足问题的要求。但我们都知道,在接近但不等于这个数字的范围内,准确性就是一片汪洋大海中的一个微小岛屿,大多数人正确地推测出这个问题的意思是“超过这个数字之后,准确性就变得很糟糕了”。啊,计算机科学是一门精确的科学,难道不是很美妙吗? :) - Carl Smotricz
3
1.7976931348623157 × 10^308 是一个精确的整数,但我相信这个特定的整数不能在一个双精度浮点数中被精确地存储。 - Pascal Cuoq
显示剩余8条评论

0

浮点数的“简单”解释

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

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

正如你所猜测的,10的308次方是一个巨大的数字,像170000000000000000000000000000000000000000000甚至更大!

另一方面,双精度浮点64位数字支持使用“点”符号表示的微小小数,最小值为:

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

任何数乘以10的负指数次幂都是非常小的小数,例如0.0000000000000000000000000000000000494,甚至更小。

但是令人困惑的是,计算机专业人士和数学人士会说,“但该数字的范围只有15个不同的值。” 实际上,上面所述的值是计算机可以存储并从内存中显示的所有最大值和最小值。 但是,它们在变得如此巨大之前就失去了精度和创建数字的能力。 因此,大多数程序员避免使用可能的最大双倍数,并尝试保持在已知的、更小的范围内。

但是为什么?哪一个是最好的最大双倍数?我找不到答案,即使在数学网站上阅读了数十个糟糕的解释。因此,下面的这个简单的解释可能会帮助您。对我很有帮助!

双倍数事实和缺陷

JavaScript(还使用计算机中数字的64位双精度存储系统)使用双精度浮点数来存储所有已知数字值。 因此,它使用与上面显示的相同的MAX和MIN范围。 但是,大多数语言使用具有范围的类型数字系统以避免精度问题。 然而,双倍和浮点数存储系统似乎都共享失去数字精度的相同缺陷,因为它们变得越来越大或越来越小。 我将解释其中的原理,因为它影响了“最大”值的概念...

为了解决这个问题,JavaScript拥有一个称为Number.MAX_SAFE_INTEGER的值,它是9007199254740991。 这是它可以表示整数的最准确数字,但不是可以存储的最大数字。 它是“准确”的,因为它保证任何等于或小于该值的数字都可以被查看、计算、存储等。 超出该范围,则存在“缺失”的数字。 原因是在9007199254740991之后的双倍精度数字使用额外的数字将它们乘以更大的值,包括真正的最大数字1.79E308。 那个新数字就叫做指数。

邪恶的指数

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

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

11111111111111111111111111111111111111111111111111111

这是计算机使用64位数字存储系统实际上可以存储双精度浮点数整数部分的最大位数。
为了获得更大的最大值(1.79E308),JavaScript必须使用一种额外的技巧,称为“指数”,将其乘以越来越大的值。因此,在计算机内存中,53位尾数值旁边有一个11位指数值,允许数字变得更大或更小,从而创建双倍期望表示的最终数字范围。 (也有一个单独的位用于正数和负数。)
在计算机达到最大整数值(约9千万亿)并用53位填满尾数部分的情况下,JavaScript使用一个新的11位存储区域来存储指数,从而使更大的整数增长(高达10的308次方!)并且更小的小数变得更小(10的-324次方!)。因此,该指数允许创建完整范围的大型和小型小数,其中浮点基数或小数点可以向上和向下移动数字,从而创建您希望看到的复杂分数或小数值。同样,该指数是存储在11位中的另一个大数,并且其最大值为2048。
您会注意到,9007199254740991是最大整数,但不解释存储中可能的更大MAX值或最小的小数值,甚至不解释如何创建和存储小数分数。这个计算机位值如何创造所有这些?
答案再次是通过指数!
事实证明,指数11位值本身被分成正值和负值,以便可以创建大整数,但也可以创建小小数。
为此,它具有自己的正负范围,通过从其2048最大值中减去1024来获得新的值范围从+1023到-1023(减去0的保留值)来创建正/负指数范围。然后,为了获得最终的DOUBLE NUMBER,将尾数(9007199254740991)乘以指数(加上单个位符号)以获得最终值!这使指数可以将尾数值乘以更大的整数范围,超过9千万亿,但也可以朝相反方向走,即小数变成非常小的分数。
然而,存储在指数中的+-1023数字并不是乘以尾数得到双精度浮点数,而是用来将数字2提高到指数幂。指数是一个小数,但不是应用于类似于10的功率或1023的十进制指数。它再次应用于Base2系统,并创建一个值为2的幂(指数)
然后将生成的值乘以尾数,以获得JavaScript允许存储的最大和最小数字,以及范围内的所有更大和更小的值。出于精度目的,它使用“2”而不是10,因此随着指数值的每次增加,它仅将尾数值加倍。这减少了数字的损失。但是,这个指数乘数也意味着它会在增长时失去越来越多的双倍数字范围,直到达到存储的最大指数和尾数,非常大的数字范围从最终计算的数字中消失,因此某些数字现在在数学计算中不可能!
这就是为什么大多数人使用SAFE最大整数范围(9007199254740991或更少),因为大多数人知道JavaScript中的非常大和小的数字是高度不准确的!还要注意,2的-1023次幂得到最小数字或与典型“浮点数”相关的小十进制分数。因此,指数用于将尾数整数转换为最大和最小范围内可以存储的非常大和小的数字。
请注意,2的1023次幂使用10的308次幂将其转换为十进制指数,以便您可以以人类值或二进制计算的Base10数字格式查看该数字。通常,数学专家不会解释所有这些值都是相同的数字,只是以不同的基数或格式表示。
双精度浮点数的真正最大值是无穷大。
最后,当整数达到可能的最大数字或最小的小数分数时会发生什么?
事实证明,双精度浮点数为64位指数和尾数值保留了一组比特值,以存储四个其他可能的数字:
1. +无穷大 2. -无穷大 3. +0 4. -0
例如,64位内存中存储的双精度数字中的+0是计算机内存中一大行空位。当您超出最小可能的十进制数(4.94E-324)时,以下是发生的情况。它在用双精度浮点数表示后变成了+0!计算机将返回+0,但在内存中存储0位。以下是计算机内存中双精度浮点数的完整64位存储设计。第一位控制正数(0)或负数(1)的加号或减号,接下来是11位指数(所有零为0,因此变成2的0次方=1),以及53位用于表示0的尾数或有效数字的大块。因此,+0由所有零表示!
0 00000000000 0000000000000000000000000000000000000000000000000000

如果 double 达到其正最大值或最小值,或者其负最大值或最小值,许多语言会以某种形式返回其中一个值。然而,一些语言会返回 NaN、溢出异常等。如何处理这些情况是另一个讨论话题。但通常,这四个值是 double 的真正最小值和最大值。通过返回无理数值,您至少可以在 double 中有一个表示最大和最小的形式,并解释不能被存储或理性解释的 double 类型的最后形式。
总结:
因此,正负 Double 的最大和最小范围如下:
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)

因此,你可以看到,加上+-Infinity和+-0后,Double类型在超出最大值和最小值时具有额外的最大和最小范围。

如上所述,当您从最大正值转换为最小的十进制正值或分数时,位数将变为零,您将得到0。在4.94E-324之后,Double类型无法存储任何更小的小数分数值,因此会折叠为+0。对于微小的负小数,也会发生同样的事件,它们会折叠为-0。如您所知,-0 = +0,因此虽然内存中存储的不是相同的值,但在应用程序中它们通常被强制转换为0。但请注意,许多应用程序确实提供了带符号的零!

相反的情况发生在大值上...超过1.79E308,它们会变成+Infinity和其负版本的-Infinity。这就是在JavaScript等语言中创建所有奇怪数字范围的原因。Double精度数字有奇怪的返回值!

请注意,小数/分数的最小安全范围未显示在上面,因为它取决于所需精度的精度。当您将整数与小数部分组合时,小数点精度会迅速降低,因为它变得越来越小。有许多关于此的讨论和争论。以下列表可能有所帮助。如果您想保证精度,您可能需要将上述范围更改为更小的值。正如您所看到的,如果您想在浮点数中支持高达9位小数精度,则需要将尾数中的最大值限制为这些值。精度表示您需要多少位小数,具有准确性。不安全表示超过这些值,数字将失去精度并且存在缺失数字:

            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

我花了一些时间才理解双精度浮点数和计算机的真正极限。在网上阅读数学专家们创造数字的过程中,我遇到了很多混乱,但他们却很糟糕地解释东西!我希望我的简单解释可以帮助您在编码旅途中更好地理解 - 和平 :)


1
很抱歉你遇到了这么多关于浮点数的不好解释,但我恐怕你的理解仍然不完美,因为这个答案仍然反映了许多相当严重的误解。我今天没有时间来解释它们,但我鼓励你阅读维基百科上的指数符号文章,这是浮点数的基础。特别是,指数字段并不是“邪恶”的;它对整个方案来说是绝对基本的!而且它一直适用,不仅仅是在我们超过9007199254740991之后。 - Steve Summit

0

正如其他人所指出的那样,我将假设 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

请考虑您的编译器,它可能不遵循当前的IEEE 754双精度类型规范。这里是一个修改后的片段可以在VB6或Excel VBA中尝试。它在999,999,999,999,999处退出循环,这只是预期值的1/9。这不能测试所有数字,因此可能会有一个更低的数字,在增加1时不会增加总和。您还可以在调试窗口中尝试以下行:Print Format(1E15# + 1#,"#,###")

    Microsoft VB6, Microsoft Excel 2013 VBA (Both obsolete) 
    Sub TestDbl()
    Dim dSum    As Double      'Double Precision Sum
    Dim vSum    As Variant     'Decimal Precision Sum
    Dim vSumL   As Variant     'Last valid comparison
   
    Dim dStep   As Double
    Dim vStep   As Variant
   
    dStep = 2# ^ 49#           'Starting step
    vStep = CDec(dStep)
   
    dSum = dStep               'Starting Sums
    vSum = vStep
    vSumL = vSum
   
   
    Debug.Print Format(dSum, "###,###,###,###,###,###,###"); " "; _
                Format(vSum, "###,###,###,###,###,###,###"); " "; _
                vStep; " "; Now()
    Do
       dSum = dSum + dStep     'Increment Sums
       vSum = CDec(vSum + vStep)
                              
       If dSum <> vSum Then
                              'Print bad steps
          Debug.Print Format(dSum, "###,###,###,###,###,###,###"); " "; _
                      Format(vSum, "###,###,###,###,###,###,###"); " "; _ 
                      vStep; " "; Now()
                              'Go back 2 steps
          vSum = CDec(vSumL - vStep)
          dSum = CDbl(vSum)
                              'Exit if Step is 1
          If dStep < 2 Then Exit Do
                              'Adjust Step, if <1 make 1
          vStep = CDec(Int(vStep / 4))
          If vStep < 2 Then vStep = CDec(1)
          dStep = CDbl(vStep)
       End If                  'End check for matching sums
       vSumL = vSum            'Last Valid reading
       DoEvents
    Loop                       'Take another step
                               'Last Valid step
    Debug.Print Format(dSum, "###,###,###,###,###,###,###"); " "; _
                Format(vSum, "###,###,###,###,###,###,###"); " ";  _
                vStep; " "; Now()
   
    End Sub

不,它不取决于编译器。所问的问题是关于“IEEE 754双精度类型”的,这是一个标准、规范良好、与语言和编译器无关的规范。 - Steve Summit
你说得对。我更新了答案并将代码改为更高效的版本。 - Oscar
原帖询问IEEE754双精度类型,但是有一个代码片段表明了一个循环,并询问循环何时退出。 - Oscar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接