不需要的除法运算符行为,我该怎么办?

时间:2012-12-17 16:55:06

标签: c++

问题描述

在流体模拟过程中,物理时间正在进行0, 0.001, 0.002, ..., 4.598, 4.599, 4.6, 4.601, 4.602, ...。现在我想从这个时间序列中选择时间= 0.1, 0.2, ..., 4.5, 4.6, ...,然后进行进一步的分析。所以我编写了以下代码来判断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是更好的解决方案吗?感谢

回应

的回答

David Schwartz

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 个答案:

答案 0 :(得分: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。

org乘以10恰好会向上舍入。

org划分为ddd恰好向下舍入(我说'碰巧',但你将一个向下舍入的数字除以一个向上舍入的数字,所以结果很自然少了)。

不同的输入将以不同的方式进行。

这只是一个误差,即使是一个微小的epsilon也很容易被忽略。

答案 1 :(得分:1)

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

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

六位小数,1/3.333333。但2/3.666667。所以:

2 / 3 = .666667  

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

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

答案 2 :(得分:0)

您无法准确代表4.6http://www.binaryconvert.com/result_double.html?decimal=052046054

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

<强>更新

您可能希望使用Boost库中的rational类: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!
}

答案 3 :(得分: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
}