-9,007,199,254,740,992 和 9,007,199,254,740,991 之间的所有整数都可以精确地表示为双精度数。 (不过,请继续阅读。)
上限派生为2^53 - 1。如果您原谅我的十六进制语法,它的内部表示类似于 (0x1.fffffffffffff * 2^52)。
在这个范围之外,许多整数如果是 2 的幂的倍数,仍然可以精确表示。
因此,可以准确表示的最高整数将是9,007,199,254,740,991 * (2 ^ 1023)
,它甚至高于Decimal.MaxValue
但这是一个非常没有意义的事实,因为该值不会改变,例如,当您1
在double
算术中减去时。
根据评论和进一步的研究,我添加了有关 .NET 和 C# 的 Mono 实现的信息,这些信息相对化了您和我可能想要做出的大多数结论。
Math.Pow
似乎并不能保证任何特定的准确性,而且它似乎比 adouble
可以表示的要少一两个。对于浮点函数,这并不奇怪。英特尔浮点硬件没有求幂指令,我希望计算涉及对数和乘法指令,其中中间结果会丢失一些精度。BigInteger.Pow
如果需要积分精度,可以使用。
但是,甚至会(decimal)(double)9007199254740991M
导致往返违规。然而,这一次是一个已知的错误,直接违反了 C# 规范的第 6.2.1 节。有趣的是,即使在 Mono 2.8 中,我也看到了同样的错误。(引用的来源表明,即使值低得多,这个转换错误也会发生。)
双重文字不那么四舍五入,但仍然有点:9007199254740991D 打印为 9007199254740990D。这是解析字符串文字时内部乘以 10 的产物(在上限和下限double
基于“小数点后的第一个零”收敛到相同值之前)。这再次违反了 C# 规范,这次是第 9.4.4.3 节。
与 C 不同,C# 没有十六进制浮点字面量,因此我们无法通过任何其他语法避免乘以 10,除非可能通过Decimal
or BigInteger
,如果这些仅提供准确的转换运算符。我没有测试过BigInteger
。
以上几乎可以让您怀疑 C# 是否没有发明自己独特的降低精度的浮点格式。不,第 11.1.6 节引用了 64 位IEC 60559表示。所以上面确实是bug。
因此,总而言之,您甚至应该能够将 9007199254740991M 精确地放入 double 中,但是要获得正确的值是一个相当大的挑战!
这个故事的寓意是,“算术应该比数据和期望的结果更精确”的传统信念是错误的,正如这篇著名的文章所证明的(第 36 页),尽管是在不同的编程语言的背景下。
除非必须,否则不要将整数存储在浮点变量中。