【发布时间】:2010-09-15 16:54:48
【问题描述】:
double r = 11.631;
double theta = 21.4;
在调试器中,这些显示为11.631000000000000 和21.399999618530273。
我怎样才能避免这种情况?
【问题讨论】:
double r = 11.631;
double theta = 21.4;
在调试器中,这些显示为11.631000000000000 和21.399999618530273。
我怎样才能避免这种情况?
【问题讨论】:
这些accuracy problems 是由浮点数的internal representation 引起的,您无能为力。
顺便说一句,在运行时打印这些值通常仍然会产生正确的结果,至少使用现代 C++ 编译器是这样。对于大多数操作来说,这不是什么大问题。
【讨论】:
我喜欢Joel's explanation,它处理 Excel 2007 中类似的二进制浮点精度问题:
看到最后有很多 0110 0110 0110 吗?那是因为 0.1 没有精确的二进制表示...它是一个重复的二进制数。这有点像 1/3 没有十进制表示。 1/3 是 0.33333333,你必须永远写 3。如果你失去耐心,你会得到一些不准确的东西。
所以你可以想象一下,如果你尝试做 3*1/3,而你没有时间永远写 3,那么你可以想象得到的结果是 0.99999999,而不是 1,而人们会对你的错误感到生气。
【讨论】:
* 或/ 具有更高的运算符优先级。
如果你有类似的值:
double theta = 21.4;
而你想做的事:
if (theta == 21.4)
{
}
你必须有点聪明,你需要检查 theta 的值是否真的接近 21.4,但不一定是那个值。
if (fabs(theta - 21.4) <= 1e-6)
{
}
【讨论】:
这部分是特定于平台的 - 我们不知道您使用的是什么平台。
这也是了解您真正想要看到什么的情况。调试器正在向您展示 - 在某种程度上,无论如何 - 存储在变量中的精确值。在我的article on binary floating point numbers in .NET 中,有一个C# class 可以让您看到绝对准确 存储在双精度数中的数字。在线版本目前无法使用 - 我会尝试在其他网站上发布。
鉴于调试器看到的是“实际”值,它必须对要显示的内容做出判断 - 它可以向您显示四舍五入到小数点后几位的值,或更精确的值。一些调试器在阅读开发人员的想法方面比其他调试器做得更好,但这是二进制浮点数的一个根本问题。
【讨论】:
如果您希望在精度范围内保持稳定性,请使用定点 decimal 类型。有开销,如果您希望转换为浮点数,则必须显式转换。如果您确实转换为浮点数,您将重新引入似乎困扰您的不稳定性。
或者,您可以克服它并学习使用浮点运算的有限精度。例如,您可以使用舍入来使值收敛,或者您可以使用 epsilon 比较来描述容差。 “Epsilon”是您设置的定义容差的常数。例如,如果两个值相差在 0.0001 以内,您可以选择将它们视为相等。
在我看来,您可以使用运算符重载来使 epsilon 比较透明。那会很酷。
对于尾数指数表示,必须计算 EPSILON 以保持在可表示的精度范围内。对于数字 N,Epsilon = N / 10E+14
System.Double.Epsilon 是Double 类型的最小可表示正值。对于我们的目的而言,它太小了。阅读Microsoft's advice on equality testing
【讨论】:
我以前遇到过这种情况 (on my blog) - 我认为令人惊讶的往往是“非理性”数字是不同的。
这里的“非理性”是指无法以这种格式准确表示的事实。真正的无理数(如 π - pi)根本无法准确表示。
大多数人都熟悉 1/3 不能以十进制计算:0.3333333333333...
奇怪的是 1.1 在浮点数中不起作用。人们期望十进制值可以在浮点数中工作,因为他们对它们的看法:
1.1 是 11 x 10^-1
实际上它们在 base-2 中时
1.1 是 154811237190861 x 2^-47
你无法避免,你只需要习惯一些浮点数是“非理性”的事实,就像 1/3 一样。
【讨论】:
避免这种情况的一种方法是使用一个库,该库使用另一种表示十进制数的方法,例如BCD
【讨论】:
在我看来,21.399999618530273 是 21.4 的 单精度(浮点)表示。看起来调试器正在从 double 向下转换为 float 某处。
【讨论】:
如果您使用 Java 并且需要准确性,请使用 BigDecimal 类进行浮点计算。它更慢但更安全。
【讨论】:
您无法避免这种情况,因为您使用的是具有固定字节数的浮点数。实数与其有限符号之间根本不可能存在同构。
但大多数时候你可以简单地忽略它。 21.4==21.4 仍然是正确的,因为它仍然是具有相同错误的相同数字。但是 21.4f==21.4 可能不是真的,因为 float 和 double 的错误是不同的。
如果您需要固定精度,也许您应该尝试定点数。甚至整数。例如,我经常使用 int(1000*x) 传递给调试分页器。
【讨论】:
如果您觉得麻烦,您可以自定义调试期间某些值的显示方式。小心使用它:-)
【讨论】:
在比较浮点数时还要注意,请参阅this answer 了解更多信息。
【讨论】:
根据javadoc
"如果数字运算符的至少一个操作数是双精度类型,则
使用 64 位浮点运算进行运算,结果
数值运算符是 double 类型的值。如果另一个操作数不是双精度,则它是
首先通过数字提升(第 5.1.5 节)扩展为双精度类型(第 5.6 节)。”
【讨论】: