处理浮点数中的精度问题

Dealing with accuracy problems in floating-point numbers

提问人:AndyUK 提问时间:2/26/2009 最后编辑:xanAndyUK 更新时间:2/27/2009 访问量:16831

问:

我想知道是否有一种方法可以克服精度问题,这似乎是我的机器内部浮点数表示的结果:

为清楚起见,问题总结为:

// str is "4.600";   atof( str ) is 4.5999999999999996  
double mw = atof( str )  

// The variables used in the columns calculation below are:   
//  
//                    mw = 4.5999999999999996  
//                    p = 0.2  
//                    g = 0.2  
//                    h = 1 (integer)  

int columns = (int) ( ( mw - ( h * 11 * p ) ) / ( ( h * 11 * p ) + g ) ) + 1;

在转换为整数类型之前,列计算的结果是 1.999999999999999996;离 2.0 的预期结果如此之近,但又如此之远。

欢迎任何建议。

C++ 浮点 精度

评论

0赞 Mitch Wheat 2/26/2009
这个问题之前已经问过和回答过了......只是在寻找它...
0赞 JeeBee 2/26/2009
阅读数值分析,在某些情况下这是一个大问题。也许使用替代(但速度较慢)的数学库,如 BigDecimal 等......

答:

2赞 Can Berk Güder 2/26/2009 #1

使用小数:decNumber++

评论

2赞 MSalters 2/26/2009
这能解决 3*(1/3) 问题吗?还是只有 10*(1/10) 问题?
1赞 j_random_hacker 2/26/2009
-1,正是 MSalters 给出的原因。十进制数字对于处理金钱很有用,不是因为它们具有卓越的精度,而是因为您的不精确计算将与其他人的计算相同。在所有其他方面,十进制数都存在完全相同的问题。
0赞 Zan Lynx 2/26/2009
尽管有一些库可以存储分数。4.6 将是其中之一的 4 + 3/5。只有当给定一个无法作为分数管理的操作时,它们才会分崩离析,例如乘以 pi。
1赞 j_random_hacker 2/27/2009
@Can:他们可能会解决这个特定实例,但肯定存在 mw、p、g 和 h 的值,完全相同的问题会再次出现。这就是使这个解决方案成为黑客的原因 - 它只适用于少数情况,而不是所有情况。
0赞 j_random_hacker 2/27/2009
@Zan:是的,有理数库可以解决这个问题,因为它可以准确地表示该代码片段可能产生的任何值。(正如你所说,如果代码被更改为使用无理数(例如,通过计算平方根或三角函数等),这将不再是正确的。
2赞 Josh Mein 2/26/2009 #2

你可以阅读这篇论文,找到你要找的东西。

您可以获得结果的绝对值,如下所示

x = 0.2;  
y = 0.3;  
equal = (Math.abs(x - y) < 0.000001)  
10赞 unwind 2/26/2009 #3

如果你还没有读过它,这篇论文的标题真的很正确。请考虑阅读它,以了解有关现代计算机上浮点运算的基础知识、一些陷阱以及它们为什么会这样做的解释。

15赞 kmkaplan 2/26/2009 #4

当你使用浮点运算时,严格相等几乎是没有意义的。您通常希望与一系列可接受的值进行比较。

请注意,某些值不能完全表示为浮点。

了解每个计算机科学家都应该了解的有关浮点运算和比较浮点数的知识。

5赞 Kip 2/26/2009 #5

将浮点数四舍五入为整数的一种非常简单有效的方法:

int rounded = (int)(f + 0.5);

注意:这仅在始终为正的情况下才有效。(感谢 J Random Hacker)f

评论

0赞 AndyUK 2/27/2009
是的,“列”在此应用程序中始终为正。
0赞 Moshe 5/28/2010
@j_random_hacker - 理论上可以使用绝对值。
2赞 j_random_hacker 5/28/2010
@Moshe:不确定 abs() 会给你带来很多好处,因为大概你希望最终答案有原始符号,这意味着你需要通过乘以原始符号来“反转”abs()。可能更简单,只是用 .0.5(0.5 - (f < 0))
0赞 Moshe 5/29/2010
@jP_random_hacker - 老实说,我不明白你发布的最后一点代码,但是的,这是一个有效的观点。
4赞 j_random_hacker 5/30/2010
@Moshe:这有点神秘,但我觉得它很可爱...... :)如果为正数或 0,则整个表达式的计算结果与以前相同,因此正数的舍入不受影响;但如果为负数,则计算结果为 ,然后从中减去得到 ,这将导致负数也四舍五入到最接近。f(f < 0)00.5f(f < 0)10.5-0.5
12赞 MSalters 2/26/2009 #6

没有准确性问题。

你得到的结果 (1.999999999999999996) 与数学结果 (2) 相差 1E-16。考虑到您的输入“4.600”,这是非常准确的。

当然,您确实有四舍五入的问题。C++ 中的默认舍入是截断;你想要类似于 Kip 的解决方案。详细信息取决于您的确切域名,您期望吗?round(-x)== - round(x)

5赞 ChrisF 2/26/2009 #7

如果精度真的很重要,那么您应该考虑使用双精度浮点数,而不仅仅是浮点数。虽然从你的问题来看,你似乎已经是了。但是,在检查特定值时仍然有问题。您需要如下代码(假设您正在检查您的值是否为零):

if (abs(value) < epsilon)
{
   // Do Stuff
}

其中“epsilon”是一些小的,但不是零的值。

评论

3赞 Max Lybbert 2/27/2009
我想你的意思是“abs(computed_value - expected_value) < epsilon”。否则,您只是在检查最终值是否真的很小;而不是最终值是否真的接近它应该有的水平。
1赞 ChrisF 2/27/2009
确实如此 - 但我确实提到该代码是针对零;)进行检查的示例
3赞 Mr.Ree 2/27/2009 #8

在计算机上,浮点数从来都不准确。它们总是只是一个近似值。(1E-16 很接近。

有时有些隐藏的部分你看不到。有时代数的基本规则不再适用:a*b != b*a。有时,将寄存器与内存进行比较会发现这些细微的差异。或者使用数学协处理器与运行时浮点库。(我这样做太久了。

C99 定义:(查看 math.h)

double round(double x);
float roundf(float x);
long double roundl(long double x);

.

或者你可以自己滚动:

template<class TYPE> inline int ROUND(const TYPE & x)
{ return int( (x > 0) ? (x + 0.5) : (x - 0.5) ); }

对于浮点等价,请尝试:

template<class TYPE> inline TYPE ABS(const TYPE & t)
{ return t>=0 ? t : - t; }

template<class TYPE> inline bool FLOAT_EQUIVALENT(
    const TYPE & x, const TYPE & y, const TYPE & epsilon )
{ return ABS(x-y) < epsilon; }