为什么我看到一个双精度变量初始化为21.399999618530273的某个值,如21.4?

Why do I see a double variable initialized to some value like 21.4 as 21.399999618530273?

提问人: 提问时间:10/7/2008 最后编辑:8 revs, 5 users 29%yesraaj 更新时间:2/9/2017 访问量:19893

问:

double r = 11.631;
double theta = 21.4;

在调试器中,它们显示为 和 。11.63100000000000021.399999618530273

我怎样才能避免这种情况?

浮点 精度

评论


答:

57赞 Konrad Rudolph 10/7/2008 #1

这些精度问题是由于浮点数的内部表示造成的,您无能为力来避免它。

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

评论

0赞 tloach 10/7/2008
不过,这是程序员应该注意的事情,特别是当他们处理非常大或非常小的数字时,准确性可能很重要。
0赞 Dark Shikari 10/7/2008
不一定非常大或非常小 - 无论总体数字大小如何,浮点精度都是相同的。问题在于,当您混合非常大和非常小的值时,例如将它们相加。
4赞 SquareCog 10/11/2008
黑暗 -- 这实际上不是真的。在 0 附近,可表示值的空间要密集得多,并且随着无穷大而更加稀疏(例如,2^24+1 不能使用 IEEE 浮点标准精确表示 32 位双精度值)
0赞 Peter Wone 10/12/2008
事实上,指数稀疏,因为你正在应用指数。
13赞 Mark Ingram 10/7/2008 #2

如果您有如下值:

double theta = 21.4;

你要做的是:

if (theta == 21.4)
{
}

你必须有点聪明,你需要检查theta的值是否真的接近21.4,但不一定是那个值。

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

评论

1赞 Alessandro Jacopson 11/4/2008
双θ = 21.4;bool b = theta == 21.4;// 这里 b 总是真的
3赞 SmacL 10/7/2008 #3

避免这种情况的一种方法是使用使用替代方法来表示十进制数的库,例如 BCD

评论

0赞 Martin York 10/7/2008
有比 BCD 更好的技术。
1赞 10/7/2008
说其中一两种技术会很好。
3赞 Shimi Bandiel 10/7/2008 #4

如果您使用的是 Java 并且需要准确性,请使用 BigDecimal 类进行浮点计算。它较慢但更安全。

39赞 Jeff Atwood 10/7/2008 #5

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

看看最后有很多 0110 0110 0110?那是因为 0.1 在二进制中没有确切的表示......这是一个重复的二进制数。这有点像 1/3 在十进制中没有表示。1/3 是 0.33333333,你必须永远写 3。如果你失去耐心,你会得到一些不准确的东西。

所以你可以想象一下,如果你试着做 3*1/3,而你没有时间永远写 3,你得到的结果将是 0.99999999,而不是 1,人们会因为你错了而生气。

评论

7赞 Nosredna 6/15/2009
如果你尝试做 3*1/3,你会把 3 乘以 1 得到 3。然后你把三除以三,没有人应该生气。我假设乔尔的意思是说 3*(1/3)。
2赞 Peter Olson 6/10/2011
@Nosredna 这取决于您使用的语言是否具有更高的运算符优先级 或 。*/
5赞 Peter Wone 10/7/2008 #6

如果希望在精度限制下保持稳定,请使用定点类型。存在开销,如果要转换为浮点,则必须显式强制转换。如果你确实转换为浮点数,你将重新引入似乎困扰你的不稳定性。decimal

或者,您可以克服它并学会使用浮点运算的有限精度。例如,您可以使用舍入来使值收敛,也可以使用 epsilon 比较来描述容差。“Epsilon”是您设置的用于定义容差的常量。例如,如果两个值之间的距离在 0.0001 以内,则可以选择将它们视为相等。

在我看来,您可以使用运算符重载来使 epsilon 比较透明。那会很酷。


对于尾数指数表示,必须计算 EPSILON 以保持在可表示的精度范围内。对于数字 N,Epsilon = N / 10E+14

System.Double.Epsilon是该类型的最小可表示正值。对于我们的目的来说,它小了。阅读 Microsoft 关于平等测试的建议Double

评论

0赞 Jon Skeet 10/7/2008
快速说明(但并不矛盾)- 如果在 .NET 中使用 System.Decimal 类型,请注意它仍然是浮点类型。它是一个浮点小数点,但仍然是一个浮点数。哦,还要提防 System.Double.Epsilon,因为它不是你所期望的那样:)
7赞 Jon Skeet 10/7/2008 #7

这在一定程度上是特定于平台的 - 我们不知道您使用的是什么平台。

这在一定程度上也是知道你真正看到什么的情况。调试器在某种程度上向你显示存储在变量中的精确值。在我关于 .NET 中的二进制浮点数的文章中,有一个 C# 类可以让你看到存储在双精度值中的绝对精确的数字。在线版本目前不起作用 - 我会尝试在另一个网站上放置一个。

鉴于调试器看到的是“实际”值,它必须对要显示的内容做出判断 - 它可以显示四舍五入到小数点后几位的值,或者更精确的值。一些调试器在读取开发人员的想法方面比其他调试器做得更好,但这是二进制浮点数的一个基本问题。

评论

2赞 Konrad Rudolph 10/7/2008
Jon,这个问题最初被标记为 C++/VC6,所以我们实际上在有人认为这些信息不重要并编辑标签之前就已经知道了这个平台。
3赞 MikeJ-UK 10/7/2008 #8

在我看来,21.399999618530273 是 21.4 的单精度(浮点数)表示。看起来调试器正在从双精度向下投射到浮动的某个地方。

1赞 Larry 10/7/2008 #9

如果它困扰您,您可以自定义某些值在调试期间的显示方式。小心使用:-)

使用调试器显示属性增强调试

2赞 akalenuk 10/7/2008 #10

您无法避免这种情况,因为您使用的是具有固定字节数的浮点数。实数与其有限的符号之间根本不可能同构。

但大多数时候你可以简单地忽略它。21.4==21.4 仍然为 true,因为它仍然是相同的数字,但误差相同。但是 21.4f==21.4 可能不是真的,因为 float 和 double 的误差不同。

如果你需要固定精度,也许你应该尝试定点数。甚至是整数。例如,我经常使用 int(1000*x) 传递给调试寻呼机。

评论

0赞 Reunanen 11/6/2008
实际上,人们可能更喜欢 int(1000*x+.5) 来使 21.4 按预期出现。
4赞 Keith 10/7/2008 #11

我以前(在我的博客上)遇到过这个问题——我认为令人惊讶的是“无理”数字是不同的。

这里所说的“非理性”,我只是指它们不能以这种格式准确表示的事实。真正的无理数(如 π - pi)根本无法准确表示。

大多数人都熟悉 1/3 不以十进制工作:0.33333333333333...

奇怪的是,1.1 在浮点数中不起作用。人们希望十进制值在浮点数中起作用,因为他们如何看待它们:

1.1 是 11 x 10^-1

实际上,当它们在 base-2 中时

1.1 是 154811237190861 x 2^-47

你无法避免它,你只需要习惯一些浮动是“非理性”的事实,就像 1/3 一样。

评论

1赞 Sklivvz 10/10/2008
基思,事实上,你的例子都不是不合理的。Sqrt(2) 是无理的,PI 是无理的,但根据定义,任何除以整数的整数都是有理的。
0赞 Keith 10/10/2008
你说得很对——因此用单引号。在数学理论中,这些是有理数,它们不能用所使用的存储机制来表达。
2赞 Chobicus #12

计算机算术的危险

0赞 2 revsgrom #13

参考一般十进制算术

在比较浮点数时也要注意,有关详细信息,请参阅此答案

0赞 2 revs, 2 users 91%MaheshVarma #14

根据 javadoc

“如果数值运算符的至少一个操作数是 double 类型,则使用 64 位浮点运算执行运
算,
数值运算符的结果是 double 类型的值。如果另一个操作数不是双精度,则
首先将其扩展 (§5.1.5) 以通过数字提升 (§5.6) 类型双精度。

这是来源