double r = 11.631;
double theta = 21.4;
在调试器中,这些显示为11.631000000000000
和21.399999618530273
。
我怎样才能避免这种情况?
double r = 11.631;
double theta = 21.4;
在调试器中,这些显示为11.631000000000000
和21.399999618530273
。
我怎样才能避免这种情况?
这些准确性问题是由于浮点数的内部表示造成的,您无法避免。
顺便说一句,在运行时打印这些值通常仍然会产生正确的结果,至少使用现代 C++ 编译器是这样。对于大多数操作来说,这不是什么大问题。
我喜欢Joel 的解释,它处理 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)
{
}
这部分是特定于平台的 - 我们不知道您使用的是什么平台。
这也是知道你真正想看到什么的部分情况。调试器正在向您展示 - 在某种程度上,无论如何 - 存储在变量中的精确值。在我关于 .NET 中的二进制浮点数的文章中,有一个C# 类可以让您查看存储在双精度数中的绝对精确数字。在线版本目前无法使用 - 我会尝试将其放在另一个站点上。
鉴于调试器看到“实际”值,它必须对要显示的内容做出判断——它可以显示四舍五入到小数点后的值,或更精确的值。一些调试器在阅读开发人员的想法方面比其他调试器做得更好,但这是二进制浮点数的一个基本问题。
decimal
如果您希望在精度范围内保持稳定性,请使用定点类型。有开销,如果您希望转换为浮点数,则必须显式转换。如果您确实转换为浮点数,您将重新引入似乎困扰您的不稳定性。
或者,您可以克服它并学习使用浮点运算的有限精度。例如,您可以使用舍入来使值收敛,或者您可以使用 epsilon 比较来描述容差。“Epsilon”是您设置的定义容差的常数。例如,如果两个值在 0.0001 范围内,您可以选择将它们视为相等。
我突然想到,您可以使用运算符重载来使 epsilon 比较透明。那会很酷。
对于尾数指数表示,必须计算 EPSILON 以保持在可表示的精度范围内。对于数字 N,Epsilon = N / 10E+14
System.Double.Epsilon
是该类型的最小可表示正值Double
。它对于我们的目的来说太小了。阅读微软关于平等测试的建议
我以前(在我的博客上)遇到过这个问题——我认为令人惊讶的往往是“非理性”数字是不同的。
这里的“非理性”是指无法以这种格式准确表示的事实。真正的无理数(如 π - 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) 传递给调试寻呼机。
如果它困扰您,您可以自定义调试期间某些值的显示方式。小心使用它:-)
根据javadoc
“如果数值运算符的至少一个操作数是 double 类型,则
使用 64 位浮点算术执行运算,
数值运算符的结果是 double 类型的值。如果另一个操作数不是双精度数,它
首先被扩展(第 5.1.5 节)以通过数字提升(第 5.6 节)键入双精度。”