【问题标题】:Dealing with accuracy problems in floating-point numbers处理浮点数的精度问题
【发布时间】:2010-10-10 02:26:40
【问题描述】:

我想知道是否有办法克服似乎是我的机器内部浮点数表示导致的准确性问题:

为了清楚起见,问题总结如下:

// 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.9999999999999996;距离 2.0 的预期结果如此接近,但距离如此之远。

欢迎提出任何建议。

【问题讨论】:

  • 这个问题之前有人问过并回答过......只是在寻找它......
  • 阅读数值分析,在某些情况下这是一个大问题。也许使用其他(但速度较慢)数学库,如 BigDecimal 等......

标签: c++ floating-point floating-accuracy


【解决方案1】:

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

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

注意:这仅在f 始终为正时才有效。 (感谢 j 随机黑客)

【讨论】:

  • 是的,“列”在此应用程序中始终为正数。
  • @j_random_hacker - 理论上你可以使用绝对值。
  • @Moshe:不确定 abs() 是否会为您买太多,因为大概您希望最终答案具有原始符号,这意味着您需要通过乘以“反转” abs()原来的标志。将0.5 替换为(0.5 - (f < 0)) 可能更简单。
  • @jP_random_hacker - 老实说,我不明白你发布的最后一段代码,但是是的,这是一个有效的观点。
  • @Moshe:这太神秘了,但我觉得它很可爱... :) 如果f 是正数或0,(f < 0)0,所以整个表达式计算为0.5和以前一样,正数的四舍五入不受影响;但如果f 为负数,(f < 0) 的计算结果为1,然后从0.5 中减去-0.5,这将导致负数也被舍入到最近。
【解决方案2】:

您可以阅读此paper 以找到您要查找的内容。

你可以得到结果的绝对值,如here:

x = 0.2;  
y = 0.3;  
equal = (Math.abs(x - y) < 0.000001)  

【讨论】:

    【解决方案3】:

    在计算机上,浮点数从来都不是精确的。它们始终只是一个近似值。 (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; }
    

    【讨论】:

      【解决方案4】:

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

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

      其中“epsilon”是一个很小但非零的值。

      【讨论】:

      • 我认为您的意思是“abs(computed_value - expected_value)
      • 确实——但我确实提到该代码是检查零的示例;)
      【解决方案5】:

      不存在准确性问题。

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

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

      【讨论】:

        【解决方案6】:

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

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

        请参阅 What Every Computer Scientist Should Know About Floating-Point ArithmeticComparing floating point numbers

        【讨论】:

          【解决方案7】:

          如果你没看过,this paper的标题真的是对的。请考虑阅读它,以了解更多关于现代计算机上浮点运算的基础知识、一些陷阱以及它们为何会如此行事的解释。

          【讨论】:

            【解决方案8】:

            使用小数:decNumber++

            【讨论】:

            • 这能解决 3*(1/3) 的问题吗?还是只有 10*(1/10) 的问题?
            • -1,正是 MSalters 给出的原因。十进制数字对处理金钱很有用,不是因为它们具有更高的精度,而是因为您的不精确计算将与​​其他所有人相同。在所有其他方面,十进制数都会遇到完全相同的问题。
            • 虽然有一些库存储分数。 4.6 将是其中之一的 4 + 3/5。只有当给定一个不可能作为分数管理的操作时,它们才会分崩离析,比如乘以 pi。
            • @Can:他们可能会解决这个特定的实例,但肯定存在 mw、p、g 和 h 的值,它们会再次出现完全相同的问题。这就是使该解决方案成为 hack 的原因 - 它仅适用于少数情况,而不适用于所有情况。
            • @Zan:是的,有理数库可以解决这个问题,因为它可以完全表示该代码 sn-p 可以任何值生产。 (如您所说,如果将代码更改为使用无理数(例如,通过计算平方根或三角函数等),这将不再正确。)
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-01-20
            相关资源
            最近更新 更多