为什么我看到一个双精度变量初始化为某个值,比如21.4,但它显示为21.399999618530273?

49
double r = 11.631;
double theta = 21.4;

在调试器中,它们显示为11.63100000000000021.399999618530273
我应该如何避免这种情况?
14个回答

57

这些精度问题是由于浮点数的内部表示引起的,你很难避免。

顺便提一下,在运行时打印这些值通常仍会得到正确的结果,至少使用现代C++编译器来说是这样。对于大多数操作而言,这并不是太大的问题。


这虽然是程序员应该注意的事情,特别是当他们使用非常大或非常小的数字时,精度可能很重要。 - tloach
不一定非常大或非常小——浮点精度与整体数值大小无关。问题出现在当你混合非常大和非常小的值,例如将它们相加。 - Dark Shikari
4
"黑暗 - 实际上并非如此。可表示值的空间在接近0时更加密集,在无限趋近于正无穷时更加稀疏(例如,使用32位双精度IEEE浮点标准无法精确地表示2^24+1)。" - SquareCog
实际上越来越稀疏,因为您正在应用指数。 - Peter Wone

39

我喜欢 Joel 的解释,它涉及到Excel 2007中的类似二进制浮点精度问题:

看那里有很多0110 0110 0110吗?这是因为0.1在二进制中没有精确的表示... 它是一个重复的二进制数。这有点像1/3在十进制中没有表示法。1/3是0.33333333,你必须一直写3。如果你失去耐心,你会得到不精确的结果。

所以你可以想象,在十进制中,如果你尝试做3*1/3,而且你没有时间一直写3,你得到的结果将是0.99999999,而不是1,人们会因为你错误而生气。


7
如果你尝试计算31/3,你需要将三与一相乘得到三,然后将三除以三,不会有人生气。我猜Joel本意是要说3(1/3)。 - Nosredna
2
@Nosredna 这取决于您使用的编程语言是否将 */ 运算符优先级更高。 - Peter Olson

13

如果您有这样的值:

double theta = 21.4;

而你想要做的是:

if (theta == 21.4)
{
}

你需要有一些才智,你需要检查theta的值是否真正地接近21.4,但不一定要等于那个值。

if (fabs(theta - 21.4) <= 1e-6)
{
}

1
double theta = 21.4; bool b = theta == 21.4;// 这里b始终为真 - Alessandro Jacopson

7

这在某种程度上是与平台相关的 - 我们不知道您使用的是什么平台。

这也部分取决于您实际想要看到什么。调试器向您显示 - 在某种程度上 - 存储在变量中的精确值。在我关于.NET中二进制浮点数的文章中,有一个C#类,可以让您查看存储在double中的绝对精确数字。在线版本目前无法使用 - 我将尝试在另一个站点上发布。

考虑到调试器看到的是“实际”值,它必须做出判断来决定要显示什么 - 它可以向您显示舍入到几个小数位的值,或者更精确的值。一些调试器比其他人更擅长读取开发人员的想法,但这是二进制浮点数的根本问题。


2
Jon,这个问题最初被标记为C++/VC6,所以在有人决定这些信息不重要并编辑标签之前,我们实际上知道平台。 - Konrad Rudolph

5
使用固定点decimal类型,如果你想在精度极限处获得稳定性。这会增加开销,并且如果需要转换为浮点数,则必须显式地进行强制转换。如果您转换为浮点数,则会重新引入似乎困扰您的不稳定性。
或者,您可以克服它,学习如何使用浮点算术的有限精度。例如,您可以使用舍入使值收敛,或者您可以使用epsilon比较来描述公差。 "Epsilon"是您设置的常量,用于定义公差。例如,如果两个值相差不超过0.0001,您可以选择将其视为相等。
我认为您可以使用运算符重载使epsilon比较透明化。那将非常酷。
对于底数-指数表示法,必须计算EPSILON以保持可表示精度。对于一个数N,Epsilon = N / 10E+14。 System.Double.EpsilonDouble类型的最小可表示正值。它对我们来说太小了。请阅读Microsoft关于相等性测试的建议

快速提醒(但不是矛盾)- 如果您在.NET中使用System.Decimal类型,请注意它仍然是浮点类型。它是浮动小数点,但仍然是浮点。另外要注意System.Double.Epsilon,因为它不是您期望的那样 :) - Jon Skeet

4
我之前在我的博客上(链接在此)曾经遇到过这个问题-我认为人们惊讶的原因在于“无理数”不同。所谓“无理数”是指在这种格式下无法准确表示它们。实际上,真正的无理数(如π - pi)根本无法准确表示。
大多数人都知道1/3在小数中无法运行:0.3333333333333...
奇怪的是,1.1在浮点数中也无法工作。人们期望十进制值在浮点数中可以工作,因为他们这样考虑:

1.1相当于11 x 10^-1

但实际上它们是基于二进制的:

1.1相当于154811237190861 x 2 ^-47

你无法避免它,你只能逐渐适应某些浮点数是“无理”的事实,就像1/3一样。

1
Keith,实际上你的例子都不是无理数。根号2是无理数,圆周率是无理数,但任何整数除以整数都是根据定义有理数。 - Sklivvz
你说得很对 - 因此使用单引号。在数学理论中,它们是有理数,只是无法用所使用的存储机制来表示。 - Keith

3

您可以避免这种情况的一种方法是使用一个使用另一种表示十进制数的库,例如BCD

。该方法可以防止浮点数精度问题。


有比BCD更好的技术。 - Martin York
1
说出其中一两种技巧会很好。 - anon

3

如果您正在使用Java并且需要准确性,请使用BigDecimal类进行浮点数计算。它的速度会比较慢,但更加安全。


3

在我看来,21.399999618530273是对21.4的单精度(float)表示。看起来调试器在某处将双精度转换为单精度。


2
你使用的是带有固定字节数的浮点数,因此无法避免这种情况。实数和其有限符号之间根本不存在同构。
但大多数情况下你可以忽略它。21.4==21.4仍然为真,因为它们仍然是相同的数字,具有相同的误差。但是21.4f==21.4可能不成立,因为float和double的误差是不同的。
如果你需要固定精度,也许可以尝试使用定点数。甚至可以使用整数。例如,我经常使用int(1000*x)来传递给调试分页器。

我们可以使用int(1000*x+.5)来使21.4得到正确的结果。 - Reunanen

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