The fundamental issue here (the IEEE-754 representation of 0.0001
) has been well-established already, but just for kicks, I copied the implementation of fmod
using std::remainder
from https://en.cppreference.com/w/cpp/numeric/math/fmod and compared it with std::fmod
.
#include <iostream>
#include <iomanip>
#include <cmath>
// Possible implementation of std::fmod according to cppreference.com
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() {
// your code goes here
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;
}
The results are:
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
As illustrated by the last two lines of output, the actual std::fmod
(at least in this implementation) matches the implementation suggested on the cppreference page, at least for this example.
We also see that 64 bits of IEEE-754 is not enough precision to show that
10010000 * 0.0001
differs from an integer.
But if we go to 128 bits, the fractional part is clearly represented,
and when we subtract this from 1001.0
we find that the remainder is approximately the same as the return value of std::remainder
.
(The difference is presumably due to std::remainder
being computed with fewer than 128 bits; it may be using 80-bit arithmetic.)
Finally, note that std::fmod(1001.0, b) - std::remainder(1001.0, b)
turns out to be equal to the 64-bit IEEE-754 value of 0.0001
.
That is, both functions are returning results that are congruent to the same value modulo 0.0001000000000000000047921736
,
but std::fmod
chooses the smallest positive value, while
std::remainder
chooses the value closest to zero.
0.0001
can't be represented precisely in binary floating point. – Tailorb
with many digits of precision you'll be enlightened. – Tailor0.0001
is actually0.000100000000000000004792173602385929598312941379845142364501953125
to the computer, assuming the almost ubiquitous IEEE-754 representation. Meantime1001.0
is exact. – Myrticemyrtie%a
for their exactness without dozens of digits. – Tejadastd::hexfloat
modifier, if you want to keep usingstd::cout
(requires C++11). – Cerement