C++ 不需要的除法运算符行为,我应该怎么做? 问题描述

C++ 不需要的除法运算符行为,我应该怎么做? 问题描述,c++,C++,在我的流体模拟过程中,物理时间以0、0.001、0.002、…、4.598、4.599、4.6、4.601、4.602、的形式行进。现在我想从这个时间序列中选择time=0.1、0.2、…、4.5、4.6、,然后做进一步的分析。因此,我编写了以下代码来判断fractpart是否达到零 但是我非常惊讶,我发现以下两种除法得到了两种不同的结果,我该怎么办 double param, fractpart, intpart; double org = 4.6; double ddd = 0.1; //

在我的流体模拟过程中,物理时间以
0、0.001、0.002、…、4.598、4.599、4.6、4.601、4.602、
的形式行进。现在我想从这个时间序列中选择time=
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;

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

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

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

             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
您将看到,两个输入值都不是用双精度表示的,每个值都已向上或向下舍入到最接近的值<代码>组织已向下舍入,因为序列中的下一位将为0
ddd
已向上舍入,因为该序列中的下一位将是1

因此,当执行数学运算时,取整可以取消,也可以累积,具体取决于运算和原始数字的取整方式

在这种情况下,1/0.1恰好精确地返回到10

org
乘以10正好是四舍五入

org
除以
ddd
正好向下舍入(我说“恰巧到”,但你把一个向下舍入的数字除以一个向上舍入的数字,所以结果自然会更少)

不同的投入将产生不同的结果


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

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

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

有六位小数,
1/3
.333333
。但是
2/3
.66666 7
。因此:

2 / 3 = .666667  

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

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

您无法准确地表示
4.6

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

更新

您可能希望使用Boost库中的
rational
类:

关于您的任务

要查找所需的
double
请考虑精度,例如,要查找
4.6
请计算其“接近度”:

双倍时间;
...
双ε=0.001;

if(abs(time-4.6)Hm不确定您想要实现什么,但是如果您想要得到一个值,然后想要 在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
}
double org=…//来自某个地方
整数除数=1000;
int referenceValue=org*div;
对于(大小\u t步长=参考值-10;步长<参考值+10;++步长){
//使用(双)步进/除数为算法提供数据
}

你期望什么?你在观察什么?你想做什么?你看到的问题之一是0.1很难用浮点数或双倍数表示。你的示例可以更好地处理其他
ddd
@DanS值,你是在暗示
0.1
是罪魁祸首吗?@Daniel:Like
1/3
是重复的十进制,没有精确的十进制表示,
1/10
没有精确的二进制表示——它是
.00011001…
事实上我非常喜欢这个答案!你猜怎么着,看看我原来的帖子,因为我无法在评论中格式化这些结果。我不确定我应该在那里看到什么。我从来没有说过这句话y有限精度表示必须始终为每种情况给出不同的答案。只是有限精度表示中固有的一点是,它们有时给出不同的答案。我展示了一种情况,它们给出了不同的答案,而你展示了一种情况,它们没有给出不同的答案。(它们是不同的情况,因为我的情况涉及十进制精度,而你的情况涉及二进制精度。)(您是否同意使用六位十进制精度,
1/3
表示为
.333333
2/3
表示为
.6666667
?您是否不同意首先使用六位十进制精度计算
1/3
,然后乘以2,您将得到
.66666666
)我完全同意,但计算机似乎不是。它不仅仅是4.6,而是从0.1,0.2,0.3,…,4.4,4.5,4.6,4.7,…。因此我必须使用除以0.1的值。如果4.6不能精确地表示为您链接的值,那么4.6始终是“4.5999999999999999964472863211995e0”。然后我使用
param=org*(1/ddd)的第一种方法
将是
4.59999999999964472863211995e0*(1/0.1)=4.59999999999964472863211995e0*10
,然后在modf之后,它将始终是45和~1,为什么我的两种方法得到两个结果?我使用的是epsilon方法,我没有使用等式运算符。如果你使用的是epsilon值,你有什么问题?
2 / 3 = .666667  

2 * (1/3) = 2 * .333333 = .6666666  
double time;

...

double epsilon = 0.001;

if( abs(time-4.6) <= epsilon ) {
    // found!
}
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
}