【问题标题】:Small numerical error when calculating Weight Average计算重量平均值时的小数值误差
【发布时间】:2018-01-21 04:10:00
【问题描述】:

这是物理引擎中的一部分。
简化函数 centerOfMass 计算两个刚体 (demo) 的一维质心:-

#include <iostream>
#include <iomanip> 
float centerOfMass(float pos1,float m1, float pos2,float m2){
    return (pos1*m1+pos2*m2)/(m1+m2);
}
int main(){
    float a=5.55709743f;
    float b= centerOfMass(a,50,0,0);
    std::cout << std::setprecision(9) << a << '\n';  //5.55709743
    std::cout << std::setprecision(9) << b << '\n';  //5.55709696
}

我需要 b 精确 = 5.55709743。

有时(我的真实情况 = 5%)微小的差异会引入令人讨厌的物理分歧。
有一些方法可以解决它,例如大量做一些条件检查。
但是,这对我来说很容易出错。

问题:如何解决计算错误,同时保持代码干净、快速且易于维护?

顺便说一句,如果不能优雅地完成,我可能需要改进调用者以更好地抵抗这种数字错误。

编辑

(澄清重复的问题)

是的,原因是存储/计算格式的精度错误(在Is floating point math broken? 中提到)。

然而,这个问题询问如何在一个非常具体的情况下中和它的症状。

【问题讨论】:

  • 请注意,浮点数是实数的不精确近似值。浮点数运算有许多众所周知的限制。
  • @Code-Apprentice 嗯,类似。就我而言,我知道原因并且我想要一个解决方法,但这个问题集中在数字原因上。
  • 您不应该依赖== 来比较浮点数。在您的情况下,从 float 切换到 double 可以解决 @Aziz 所述的特定情况,但您应该认真解决浮点表示的限制,否则您会对结果感到惊讶。
  • @cppBeginner -- 真正的问题是您进行了“数学书”计算,并且没有修改,编写了等效的计算机程序,期望得到相同的结果。浮点数不是一本数学书——在许多情况下,由于舍入误差,直接翻译不适用于二进制计算机。为了解决这个问题,必须重写许多公式,所以简而言之,您应该进行研究,以便以某种方式重新排列计算以减少错误。

标签: c++ weighted-average numerical-computing


【解决方案1】:

您正试图获得 9 位小数的精度,但数据类型 float 有一个 precision of about 7 decimal digits

改用double。 (demo)

【讨论】:

  • 对于double,每个aa==b
  • 这是故事的一部分,但这并没有解决 OP 试图避免的真正问题。
  • @CroCo 我想是的。我正在尝试找到这样的示例来显示double 的问题。
  • @cppBeginner - 不,假设 a “接近” 1.0,a 和 b 有时会相差 1e-15 的数量级。举个例子,a=6.7820743978696196 将产生 b=6.7820743978696187
【解决方案2】:

使用双精度,而不是浮点数。 IEEE 754 双精度大约有 16 位小数。

#include <iostream>
#include <iomanip> 
double centerOfMass(double pos1, double m1, double pos2, double m2) {
    return (pos1*m1 + pos2 * m2) / (m1 + m2);
}
int main() {
    double a = 5.55709743;
    double b = centerOfMass(a, 50, 0, 0);
    std::cout << std::setprecision(16) << a << '\n';  //5.55709743
    std::cout << std::setprecision(16) << b << '\n';  //5.55709743
    std::cout << std::setprecision(16) << (b - a) << '\n';  // 0
}

对于给出的示例 centerOfMass(a, 50, 0, 0),以下将给出 a 的所有值的准确结果,但当然该示例看起来并不现实。

double centerOfMass(double pos1, double m1, double pos2, double m2) {
    double divisor = m1 + m2;
    return pos1*(m1/divisor) + pos2*(m2/ divisor);
}

【讨论】:

  • 还会有同样的问题 - 但数值不同吗?
  • @cppBeginner - 是的,总是。如果您的 a “接近” 1.0,则 a 和 b 之间的差异将是 1e-15 的数量级(假设 IEEE 754 双倍)。举个例子,a=6.7820743978696196 将产生 b=6.7820743978696187
  • 感谢您的电话!我可以摆脱所有的问题吗?我的意思是——不仅仅是缓解它。 PaulMcKenzie(OP 附近的评论)以某种方式提到可以重新排列公式,但我不知道如何。
  • @cppBeginner 没有无限量的内存就无法完成,谁能负担得起?
  • @cppBeginner - 重新排列公式可能没有太大帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-19
  • 1970-01-01
  • 2021-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多