C++ 将浮点除法分解为整数部分和小数部分
我尝试使用双精度进行整数除法+模运算(用于基于样条曲线的插值),但在使用C++ 将浮点除法分解为整数部分和小数部分,c++,division,modulo,floating-accuracy,C++,Division,Modulo,Floating Accuracy,我尝试使用双精度进行整数除法+模运算(用于基于样条曲线的插值),但在使用std::floor和std::fmod时遇到了一些与浮点精度相关的问题 我一直在使用下面的div1的等价物,但在50时它会产生错误的结果(即整数部分是3,而模部分是除数减去ε)div2可以工作,但相当复杂div3至少是一致的,但没有返回我想要的结果类型(剩余部分可能是负数,因此需要进一步操作才能使用它) 我是做错了什么,还是std::floor(num/denom)+std::fmod(num,denom)不可靠?如果是,
std::floor
和std::fmod
时遇到了一些与浮点精度相关的问题
我一直在使用下面的div1的等价物,但在50时它会产生错误的结果(即整数部分是3,而模部分是除数减去ε)div2
可以工作,但相当复杂div3至少是一致的,但没有返回我想要的结果类型(剩余部分可能是负数,因此需要进一步操作才能使用它)
我是做错了什么,还是std::floor(num/denom)
+std::fmod(num,denom)
不可靠?如果是,什么是好的替代品?div2
是最佳选择吗
包含大多数答案的代码示例版本:
问题不在于您的
fmod
,而在于您对楼层的输入。由于浮点精度的模糊性,fmod
可以返回接近分母的值。问题是,您必须小心使用与余数相同的规则处理商,以便得出结果(使用模糊等式):
x/y==(quot,rem)==quot*y+rem
为了举例说明,我添加了div4
和div5
:
std::pairdiv4(int-num,double-denom){
现状;
自动rem=std::remquo(num,denom,&quo);
返回{quo,rem};
}
std::pairdiv5(int-num,double-denom){
自动整体=标准::地板(数量/静态_-cast(denom));
自动保持=std::fmod(num,denom);
返回{全部,保留};
}
下面是一个关于失败案例的例子。输出为:
div1: 50 / 16.6666666666666678509 = (whole, remain) = (3, 16.6666666666666642982) = 66.6666666666666571928
...
div4: 50 / 16.6666666666666678509 = (whole, remain) = (3, -3.55271367880050092936e-15) = 50
div5: 50 / 16.6666666666666678509 = (whole, remain) = (2, 16.6666666666666642982) = 50
对于div1
,您得到了整个3和(几乎)一个除数的余数。错误在于,由于浮点模糊性,发送到floor
的值正好在行上,因此会被提升到3,实际上应该是2
如果您使用我的div5
,它用于同时计算余数和商,您将得到类似的对(2,~除数)
,然后所有的对都正确地乘以50。(请注意,商以整数形式返回,而不是该标准函数中的浮点数。)[更新:如注释中所述,这仅对商中的3位精度有效,也就是说,它对需要检测四分之一或八分之一的周期函数有用,但对一般商无效。]
或者,如果您使用我的div4
,我使用了您的div1
逻辑,但在除法操作之前,将输入升级到floor
到long double
精度,这使它有足够的数字来正确计算floor。结果是(3,~0)
,它显示余数而不是商的模糊性
long double
方法最终只是以更高的精度解决了同样的问题。对于周期函数的有限情况,使用std::remquo
在数值上更可靠。你选择哪个版本取决于你更关心的是什么:数值计算还是漂亮的显示
更新:您还可以尝试使用FP异常来检测何时出现问题:
void printError()
{
如果(std::fetestexcept(FE_DIVBYZERO))std::cout你的核心问题是denom=100.0/6
与数学上精确的值denomMath=100/6=50/3
,因为它不能表示为二的幂和。我们可以编写denom=denomMath+eps
(有一个小的正或负epsilon)。赋值后,denom
与最近的浮点数无法区分!如果您现在尝试将某个值denomMath*k=denom*k+eps*k
除以denom
,对于足够大的k
,您将在数学上得到错误的结果(即在精确的算术中)已经-在这种情况下,您没有希望。这种情况多久发生取决于所涉及的大小(如果值<1,则所有div
将产生零的整部分,并且是精确的,而对于大于2^54
的值,您甚至不能表示奇数)
但即使在此之前,也不能保证将denomMath
的(数学)倍数除以denom
就可以得到floor
ed或fmod
ed到正确的整数。四舍五入可能会让你安全一段时间,但如上所示,只要误差不太大
因此:
div1
遇到此处描述的问题:
当初始化trunc
参数的x/y
四舍五入失去太多精度时,表达式x-trunc(x/y)*y
可能不等于fmod(x,y)
,例如:x=30.508474576271183309
,y=6.1016949152542370172
)
在您的例子中,50/denom
产生的数字与精确结果相比稍大(3
),因为denom
稍大于denomMath
)
您不能依靠std::floor(num/denom)+std::fmod(num,denom)
来等于num
div2
有上面描述的问题:在您的情况下它可以工作,但是如果您尝试更多的情况,您会发现num/denom
稍微太小,而不是太大,它也会失败
div3
具有上述承诺。它实际上为您提供了您所希望的最精确的结果
这确实让人觉得它可能是t中的一个bug
- div1: 3, 16.6666...
- div2: 3, 0
- div3: 3, -3e-15
div1: 50 / 16.6666666666666678509 = (whole, remain) = (3, 16.6666666666666642982) = 66.6666666666666571928
...
div4: 50 / 16.6666666666666678509 = (whole, remain) = (3, -3.55271367880050092936e-15) = 50
div5: 50 / 16.6666666666666678509 = (whole, remain) = (2, 16.6666666666666642982) = 50
std::pair<double, double> div4(int num, double denom){
double whole = std::floor(num / denom);
int n = trunc(num / denom) ;
double remain = num - n * denom ;
return {whole, remain};
}
== Using div1 for this run ==
49: 2, 15.6667 = 49
50: 3, 16.6667 = 66.6667
51: 3, 1 = 51
50: 3, 16.66666666666666429819088079966604709625244140625 = 3 * 16.666666666666667850904559600166976451873779296875 + 16.66666666666666429819088079966604709625244140625 = 66.666666666666657192763523198664188385009765625
== Using div4 for this run ==
49: 2, 15.6667 = 49
50: 3, 0 = 50
51: 3, 1 = 51
50: 3, 0 = 3 * 16.666666666666667850904559600166976451873779296875 + 0 = 50
std::pair<double, double> divPerfect(int num, double denom)
{
double whole = std::floor(num / denom);
double remain = std::fma(whole, -denom, num);
if (remain < 0)
{
--whole;
remain = std::fma(whole, -denom, num);
}
return {whole, remain};
}
std::pair<double, double> divPerfect(int num, double denom)
{
double whole = std::floor(num / denom);
double remain = std::fma(whole, -denom, num);
return 0 <= remain ? std::pair(whole, remain) : std::pair(whole - 1, remain + denom);
}