46
double r = 11.631;
double theta = 21.4;

在调试器中,这些显示为11.63100000000000021.399999618530273

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

4

14 回答 14

57

这些准确性问题是由于浮点数的内部表示造成的,您无法避免。

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

于 2008-10-07T07:42:32.183 回答
39

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

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

所以你可以想象,如果你想用十进制计算 3*1/3,而你没有时间永远写 3,你得到的结果将是 0.99999999,而不是 1,人们会生气你错了。

于 2008-10-07T08:01:40.227 回答
12

如果您有如下值:

double theta = 21.4;

你想做:

if (theta == 21.4)
{
}

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

if (fabs(theta - 21.4) <= 1e-6)
{
}
于 2008-10-07T07:51:26.237 回答
7

这部分是特定于平台的 - 我们不知道您使用的是什么平台。

这也是知道你真正看到什么的部分情况。调试器正在向您展示 - 在某种程度上,无论如何 - 存储在变量中的精确值。在我关于 .NET 中的二进制浮点数的文章中,有一个C# 类可以让您查看存储在双精度数中的绝对精确数字。在线版本目前无法使用 - 我会尝试将其放在另一个站点上。

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

于 2008-10-07T09:35:14.747 回答
5

decimal如果您希望在精度范围内保持稳定性,请使用定点类型。有开销,如果您希望转换为浮点数,则必须显式转换。如果您确实转换为浮点数,您将重新引入似乎困扰您的不稳定性。

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

我突然想到,您可以使用运算符重载来使 epsilon 比较透明。那会很酷。


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

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

于 2008-10-07T09:25:49.433 回答
4

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

这里的“非理性”是指无法以这种格式准确表示的事实。真正的无理数(如 π - pi)根本无法准确表示。

大多数人都熟悉 1/3 不能以十进制表示:0.3333333333333...

奇怪的是 1.1 在浮点数中不起作用。人们期望十进制值可以在浮点数中工作,因为他们对它们的看法:

1.1 是 11 x 10^-1

当他们实际上在base-2时

1.1 是 154811237190861 x 2^-47

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

于 2008-10-07T12:22:22.447 回答
3

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

于 2008-10-07T07:52:18.480 回答
3

在我看来,21.399999618530273 是 21.4 的单精度(浮点)表示。看起来调试器正在从 double 向下转换为 float 某处。

于 2008-10-07T09:41:06.030 回答
2

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

于 2008-10-07T07:57:29.720 回答
2

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

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

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

于 2008-10-07T10:50:27.217 回答
2

计算机算术的危险

于 2008-11-02T14:21:30.640 回答
1

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

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

于 2008-10-07T09:47:43.960 回答
0

请参阅通用十进制算术

在比较浮点数时还要注意,有关更多信息,请参阅此答案

于 2009-01-13T10:01:09.233 回答
0

根据javadoc

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

这里是源

于 2013-05-30T15:25:39.977 回答