在IEEE 754双精度类型中,最大的“no-floating”整数是多少,而不会失去精度?
换句话说,下面这段代码将返回什么:(点击此处查看)
UInt64 i = 0;
Double d = 0;
while (i == d)
{
i += 1;
d += 1;
}
Console.WriteLine("Largest Integer: {0}", i-1);
在IEEE 754双精度类型中,最大的“no-floating”整数是多少,而不会失去精度?
换句话说,下面这段代码将返回什么:(点击此处查看)
UInt64 i = 0;
Double d = 0;
while (i == d)
{
i += 1;
d += 1;
}
Console.WriteLine("Largest Integer: {0}", i-1);
DBL_MAX
或约1.8×10308。它是一个整数。它被准确地表示。你还想要什么?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
double dbl = 1; while (dbl + 1 != dbl) dbl *= 2; while (dbl == --dbl);
,该方法得到与原来相同的结果。 - Sephwhile (dbl == --dbl)
会一直循环,或者根本不会执行。(在这种情况下,根本不会执行,因为它是2的N次幂)。你需要从下面的角度来考虑它。实际上,这也会导致比预期结果少一个(因为while循环中的一个检查会将dbl减少)。而且它取决于执行顺序,如果在评估左侧之前或之后执行减量操作(据我所知,这是未定义的)。如果是前者,它将永远为真并一直循环。 - falstrowhile (dbl + 1 != dbl) dbl++;
的弱点在于 dbl + 1 != dbl
可能会使用 long double
数学运算 - 考虑 FLT_EVAL_METHOD == 2
。这可能会导致无限循环。 - chux - Reinstate MonicaIEEE 754双精度(64位)可以表示的最大整数与该类型可表示的最大值相同,因为该值本身就是一个整数。
这个值用0x7FEFFFFFFFFFFFFF
表示,由以下部分组成:
0x7FE
(2046,在减去偏差后表示为1023),而不是0x7FF
(2047,表示为NaN
或无穷大)0xFFFFFFFFFFFFF
,即52个1。在二进制中,该值是隐含的1后跟来自尾数的另外52个1,然后是来自指数的971个零(1023-52 = 971)。
精确的十进制值为:
179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368
这约为1.8 x 10308。
在典型的计算机系统上,“双精度”(64位)二进制浮点数具有53位系数(其中一位被隐含),11位指数和一位符号位。
2的53次方略大于9 x 10^15。
您需要关注尾数的大小。一个IEEE 754 64 位浮点数(其中有52位,加上1个隐含位)可以准确表示绝对值小于或等于2^53的整数。
对于64位IEEE754双精度浮点数,所有整数直到9007199254740992 == 2^53都可以被准确表示。
然而,值得一提的是,所有可表示的数字超过4503599627370496 == 2^52都是整数。在2^52之后,测试它们是否为整数变得毫无意义,因为它们都被隐式地舍入为附近的可表示值。
在2^51到2^52的范围内,唯一的非整数值是以".5"结尾的中间值,这意味着计算后的任何整数测试都必须预期至少有50%的错误答案。
在2^51以下,我们还有".25"和".75",因此比较一个数字与其舍入后的对应数字,以确定它是否为整数开始变得有些合理。
简而言之:如果您想测试计算结果是否可能为整数,请避免使用大于2251799813685248 == 2^51的数字。
1.7976931348623157 × 10^308
http://zh.wikipedia.org/wiki/IEEE_754
最大的“双精度浮点数”通常是一个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
2
提高到指数幂。指数是一个小数,但不是应用于类似于10的功率或1023的十进制指数。它再次应用于Base2系统,并创建一个值为2的幂(指数)
。9007199254740991
或更少),因为大多数人知道JavaScript中的非常大和小的数字是高度不准确的!还要注意,2的-1023次幂得到最小数字或与典型“浮点数”相关的小十进制分数。因此,指数用于将尾数整数转换为最大和最小范围内可以存储的非常大和小的数字。2的1023次幂
使用10的308次幂
将其转换为十进制指数,以便您可以以人类值或二进制计算的Base10数字格式查看该数字。通常,数学专家不会解释所有这些值都是相同的数字,只是以不同的基数或格式表示。0 00000000000 0000000000000000000000000000000000000000000000000000
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
我花了一些时间才理解双精度浮点数和计算机的真正极限。在网上阅读数学专家们创造数字的过程中,我遇到了很多混乱,但他们却很糟糕地解释东西!我希望我的简单解释可以帮助您在编码旅途中更好地理解 - 和平 :)
正如其他人所指出的那样,我将假设 OP 要求最大的浮点值,使得所有小于它的整数都可以精确表示。
您可以使用在 float.h
中定义的 FLT_MANT_DIG
和 DBL_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
请考虑您的编译器,它可能不遵循当前的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