这里的基本问题(IEEE-754表示的0.0001
)已经被充分解决了,但为了好玩,我复制了使用std::remainder
实现fmod
的代码,来自https://en.cppreference.com/w/cpp/numeric/math/fmod并将其与std::fmod
进行了比较。
#include <iostream>
#include <iomanip>
#include <cmath>
double fmod2(double x, double y)
{
#pragma STDC FENV_ACCESS ON
double result = std::remainder(std::fabs(x), (y = std::fabs(y)));
if (std::signbit(result)) result += y;
return std::copysign(result, x);
}
int main() {
double b = 0.0001;
std::cout << std::setprecision(25);
std::cout << " b:" << std::setw(35) << b << "\n";
double m = 10010000.0;
double c = m * b;
double d = 1001.0 - m * b;
std::cout << std::setprecision(32);
std::cout << " 10010000*b:" << std::setw(6) << c << "\n";
std::cout << std::setprecision(25);
std::cout << "1001-10010000*b:" << std::setw(6) << d << "\n";
long double m2 = 10010000.0;
long double c2 = m2 * b;
long double d2 = 1001.0 - m2 * b;
std::cout << std::setprecision(32);
std::cout << " 10010000*b:" << std::setw(35) << c2 << "\n";
std::cout << std::setprecision(25);
std::cout << "1001-10010000*b:" << std::setw(35) << d2 << "\n";
std::cout << " remainder:" << std::setw(35) << std::remainder(1001.0, b) << "\n";
std::cout << " fmod:" << std::setw(35) << std::fmod(1001.0, b) << "\n";
std::cout << " fmod2:" << std::setw(35) << fmod2(1001.0, b) << "\n";
std::cout << " fmod-remainder:" << std::setw(35) <<
std::fmod(1001.0, b) - std::remainder(1001.0, b) << "\n";
return 0;
}
结果为:
b: 0.0001000000000000000047921736
10010000*b: 1001
1001-10010000*b: 0
10010000*b: 1001.0000000000000479616346638068
1001-10010000*b: -4.796163466380676254630089e-14
remainder: -4.796965775988315527911254e-14
fmod: 9.999999995203034703229045e-05
fmod2: 9.999999995203034703229045e-05
fmod-remainder: 0.0001000000000000000047921736
正如输出的最后两行所示,在这个实现中,实际的
std::fmod
与 cppreference 页面上建议的实现匹配,至少对于这个例子是如此。
我们还看到,64位 IEEE-754 不足以精确表示
10010000 * 0.0001
与整数之间的差异。但如果我们使用128位,则小数部分可以明确表示,当我们从
1001.0
中减去它时,我们发现余数与
std::remainder
的返回值大致相同。(差异可能是由于
std::remainder
使用少于128位的计算;它可能使用80位算术。)
最后,需要注意的是std::fmod(1001.0, b) - std::remainder(1001.0, b)
的结果等于64位IEEE-754值0.0001
。
也就是说,这两个函数返回的结果在模0.0001000000000000000047921736
下同余,
但std::fmod
选择最小正值,而std::remainder
选择最接近零的值。
0.0001
无法在二进制浮点数中精确地表示。 - Barmarb
并带有许多位精度,你会茅塞顿开。 - Barmar0.0001
实际上是0.000100000000000000004792173602385929598312941379845142364501953125
。与此同时,1001.0
是精确的。 - Mark Ransom%a
来发布所有FP值以确保它们的精度,而不是使用数十个数字。 - chux - Reinstate Monicastd::cout
,你可以使用std::hexfloat
修饰符(需要C++11),这样可以将输出格式转换为十六进制浮点数。请注意,不要改变原有的意思。 - chtz