6

问题描述

在我的流体模拟过程中,物理时间以0, 0.001, 0.002, ..., 4.598, 4.599, 4.6, 4.601, 4.602, .... 现在我想0.1, 0.2, ..., 4.5, 4.6, ...从这个时间序列中选择 time = 然后做进一步的分析。所以我写了下面的代码来判断是否fractpart命中为零。

但是我很惊讶,我发现以下两种除法方法得到了两种不同的结果,我该怎么办?

double param, fractpart, intpart;
double org = 4.6;
double ddd = 0.1;

// This is the correct one I need. I got intpart=46 and fractpart=0
// param = org*(1/ddd);

// This is not what I want. I got intpart=45 and fractpart=1
param = org/ddd;

fractpart = modf(param , &intpart);
Info<< "\n\nfractpart\t=\t"
    << fractpart
    << "\nAnd intpart\t=\t"
    << intpart
    << endl;

为什么会这样?
如果你们能容忍我一点,我可以大声喊叫:“C++ 委员会可以对此做点什么吗?因为这很混乱。” :)

获得正确余数以避免截止误差效应的最佳方法是什么?fmod 是更好的解决方案吗?谢谢

回复下面的回答

大卫·施瓦茨

double aTmp = 1;
double bTmp = 2;
double cTmp = 3;
double AAA = bTmp/cTmp;
double BBB = bTmp*(aTmp/cTmp);
Info<< "\n2/3\t=\t"
    << AAA
    << "\n2*(1/3)\t=\t"
    << BBB
    << endl;

我得到了两个,

2/3     =       0.666667
2*(1/3) =       0.666667
4

4 回答 4

7

浮点值不能完全代表每个可能的数字,因此您的数字是近似的。这在计算中使用时会产生不同的结果。

如果您需要比较浮点数,则应始终使用较小的 epsilon 值,而不是测试是否相等。在你的情况下,我会四舍五入到最接近的整数(而不是向下舍入),从原始值中减去它,然后将结果的 abs() 与 epsilon 进行比较。

如果问题是,为什么总和不同,简单的答案是它们是不同的总和。对于更长的解释,以下是所涉及数字的实际表示:

             org:  4.5999999999999996 = 0x12666666666666 * 2^-50
             ddd: 0.10000000000000001 = 0x1999999999999a * 2^-56
           1/ddd:                  10 = 0x14000000000000 * 2^-49
   org * (1/ddd):                  46 = 0x17000000000000 * 2^-47
       org / ddd:  45.999999999999993 = 0x16ffffffffffff * 2^-47

您将看到两个输入值都没有精确地表示为双精度值,每个值都已向上或向下舍入到最接近的值。org已向下舍入,因为序列中的下一位将为 0。ddd已向上舍入,因为该序列中的下一位将为 1。

因此,当执行数学运算时,舍入可以取消或累加,具体取决于运算和原始数字的舍入方式。

在这种情况下,1/0.1 恰好巧妙地四舍五入到 10。

乘以org10 恰好四舍五入。

除以碰巧向下舍orgddd(我说“碰巧”,但是您将向下舍入的数字除以向上舍入的数字,因此结果自然会较小)。

不同的输入会以不同的方式舍入。

这只是一点点错误,即使是很小的 epsilon 也可以很容易地忽略它。

于 2012-12-17T17:00:18.753 回答
1

如果我正确理解了您的问题,那就是:为什么使用有限精度的算术X/Y不一样X * (1/Y)

原因很简单:例如,考虑使用六位小数精度。虽然这不是doubles 实际所做的,但概念完全相同。

有六个十进制数字,1/3.333333。但是2/3.666667。所以:

2 / 3 = .666667  

2 * (1/3) = 2 * .333333 = .6666666  

这就是固定精度数学的本质。如果您不能容忍这种行为,请不要使用有限精度类型。

于 2012-12-17T17:08:19.430 回答
0

嗯,不太确定你想要实现什么,但是如果你想获得一个值,然后想在 1/1000 的范围内进行一些改进,为什么不使用整数而不是浮点数/双精度数呢?

您将有一个除数,即 1000,并且有您需要乘以除数的值进行迭代。

所以你会得到类似的东西

double org = ... // comes from somewhere
int divisor = 1000;
int referenceValue = org * div;
for (size_t step = referenceValue - 10; step < referenceValue + 10; ++step) {
   // use   (double) step / divisor to feed to your algorithm
}
于 2012-12-17T17:26:31.503 回答
0

你不能4.6精确地表示:http: //www.binaryconvert.com/result_double.html? decimal=052046054

在分隔整数和小数部分之前使用舍入。

更新

您可能希望使用rationalBoost 库中的类:http: //www.boost.org/doc/libs/1_52_0/libs/rational/rational.html

关于你的任务

要找到所需double的精度,例如,要4.6计算与它的“接近度”:

double time;

...

double epsilon = 0.001;

if( abs(time-4.6) <= epsilon ) {
    // found!
}
于 2012-12-17T17:14:43.177 回答