哪些数值无法被double正确表示?

10

Double数据类型不能正确表示某些十进制值。这是因为浮点数表示实数的方式。这意味着,在表示货币价值时,应使用十进制值类型以防止错误。(请随时更正此前言中的错误)

我想知道在标准.Net框架下(如果有差异,则为C#),在64位体系结构下,哪些值会在Double数据类型下出现此类问题?

我希望得到的答案是一个公式或规则,以查找此类值,但我也希望得到一些示例值。


在表示货币价值时,请使用十进制——这是正确的做法,无论是在64位还是32位架构上都应该遵循。 - JonH
或者以 BigInteger 表示的美分(或亚美分) :) - Joey
关于指定 .Net、64 位架构等,是为了考虑是否有任何实现细节需要考虑来回答我的问题。 - Gilles
4个回答

10
任何不能表示为正负2的次幂之和的数字都无法作为二进制浮点数准确地表示。常见的32位和64位浮点数 IEEE 格式还会强加其他限制,它们限制了尾数和指数中的二进制位数。因此,存在最大和最小可表示数(如果我没记错的话,约为 +/- 10^308(以10为底)),以及可表示数字精度的限制。这种精度限制意味着,对于64位数字,最大2的次幂和最小2的次幂之间指数的差距被限制在52之内。因此,如果你的数字包括一个2的52次方项,则不能同时包括一个2的-1次方项。在二进制浮点数中无法准确表示的简单例子包括 1/3、2/3 和 1/5。

由于浮点数集合(以任何表示方式)是有限的,而实数集合是无限的,因此找到一个不能被浮点数精确表示的实数的算法是随机选择一个实数。这个实数能够被精确表示为浮点数的概率是0


1
虽然 10^5000 不能用 double 表示,但它可以被表示为一个二进制浮点数。 - CodesInChaos
我有点生疏,但我相信0不能表示为2的正或负幂的和。它是否可以通过double精确地表示?我特意来这里寻找这个信息,看起来可能是可能的,但根据那个说法不行。如果值为0,我想要显示一个值,而且我不想看到一些像0.000000132这样的东西。 - Chuckk Hubbard
似乎任何整数直到最大值都可以用double表示。这就是为什么C++的“round”函数返回一个double的原因吗?然后它可以安全地转换为int,避免将5.9999强制转换为int(5)的可能性? - Chuckk Hubbard
@ChuckkHubbard: 0 可以被表示为没有任何数字的和, 0 在几乎所有你考虑过的进制下都是 0。 IEEE 二进制表示法中包括 2 个关于 0 的“值”,一个是 +0,一个是 -0。它们的小数部分和指数部分都只有 0,而 -0 的符号位为 1。大多数系统会隐藏 0 的符号,不让程序员和用户看到。 - High Performance Mark

3
你需要意识到,任何存储在 double 类型中的数值都可能存在一定的误差。除非你存储的是一个常量值,否则很可能会有一些误差。如果绝不能有任何误差,并且这些值不是常量,那么你可能不应该使用浮点类型。
在许多情况下,你应该问的是:“我该如何处理小的浮点误差?”你需要知道哪些操作可能导致大量误差,哪些操作不会。你需要确保将两个值进行“相等性”比较时实际上只是确保它们“足够接近”,而不是完全相等等等。

为什么这个被踩了——它本质上没有任何问题? - JonH
1
@JonH 我也有点好奇。我想从技术上讲,我没有回答这个问题,而是试图解决根本问题,无论实际提出的问题是什么。 - Servy
但是你的回答有助于回答问题,我会点赞它,但我担心这是某种报复,你的陈述没有任何问题。 - JonH
问题比最初预想的要复杂,所以我认为这是一个完全有效的答案。然而,我已经考虑到一些值无法在基数下表示,只能通过2^-n步进,我认为这应该做到。只要不被过度使用,但是long double complex的组件仍然与long double有所不同。至于cpowl(),那个问题基本上已经回答了,尽管我看不出为什么不将整数指数作为特殊情况进行处理。 - Zacariaz
1
“除非您存储的是常量值” - 这也不是真的。尝试将123.1存储为double,您最终会得到123.09999... - Ondra Žižka
@OndraŽižka 重点是,如果您正在存储常量,则知道是否具有精确值。 您可以存储您知道是精确的常量值。 这并不意味着每个常量都可以完全存储。 - Servy

1

这个问题实际上超越了任何单一的编程语言或平台。不准确性实际上是二进制数据固有的。

考虑到双精度浮点数,每个数字N在小数点左边(从0开始的索引I)表示值N * 2 ^ I,小数点右边的每个数字表示值N * 2 ^(-I)。

例如,5.625(十进制)将是101.101(二进制)。

根据这个计算,对于一个不能被表示为不同I值的2 ^(-I)之和的十进制值,其作为双精度浮点数的值将是不正确的。


1
这只是在回避问题。我怎么知道特定的十进制数可以表示为N * 2 ^ -i,其中 i 是有限的? - Servy
@Servy:http://cs.furman.edu/digitaldomain/more/ch6/dec_frac_to_bin.htm以及许多其他参考资料都可以回答你的问题。 - High Performance Mark
1
@HighPerformanceMark 那么你应该在你的答案中包含这个。如果没有它,你实际上并没有帮助他解决问题,而是给他一个新的问题要解决。 - Servy
实际上,我已经并计划继续仅使用可以用二进制表示的输入值,例如-2.0、1.75、0.875等。换句话说,使用形式为2^(-n)的步长。然而,复数显然表现出不同的行为,我只能推测这与实现有关。 - Zacariaz

1

浮点数在以下公式中表示为sem

s * m * 2^e

这意味着任何不能用给定表达式(在sem的各自域中)表示的数字都无法被准确地表示。

基本上,您可以表示所有介于02^53 - 1之间的数字乘以某个二的幂次方(可能是负幂次方)。

例如,介于02^53 - 1之间的所有数字都可以用2^0 = 1相乘来表示。您还可以通过将它们除以2(带有.5分数)来表示所有这些数字。等等。

这个答案并没有完全涵盖这个主题,但我希望它能有所帮助。


您好像很了解这个问题,但我不太明白您的回答,不知道是否可以请教一个问题。我的做法是从-2到+2遍历值集合,步长为2^-n,例如n=1时,遍历的值为-2,-1.5,-1,-0.5,0,0.5,1,1.5,2。我一直认为,在合理的范围内,这种方法几乎可以保证这些值被正确表示。现在我是否应该明白这是一个错误的假设?有没有更好的方法? - Zacariaz
@Zacariaz 只要 n 在浮点数支持的范围内,2^-n 就可以被精确地表示。在我的答案公式中,s 为 +-1,m = 1,e = n。浮点数的二进制表示将 s、m 和 e 存储为位。在您的示例序列中,1.5 似乎不适合,因为它不是 2^n 项,对吧?一个复杂的问题是如何生成这些 2^n 值。我不确定 Math.Pow 是否保证精确。 - usr
@Zacariaz 或者你想取特定的整数 m,然后将该数字与所有可能的 2 的 n 次幂相乘?这是可能的。我建议你尝试理解我的回答中的公式。它告诉你可以表示哪些数字。如果你能找到 s、m 和 e 的值,那么该数字就可以被表示。否则就不行。 - usr

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