fmod(1001.0, 0.0001)
is giving 0.00009999999995
, which seems like a very low precision (10-5), given the expected result of 0
.
According to cppreference, fmod()
is implementable using remainder()
, but remainder(1001.0, 0.0001)
gives -4.796965775988316e-14
(still far from double
precision, but much better than 10-5).
Why does fmod
precision depend on input arguments that much? Is it normal?
MCVE:
#include <cmath>
#include <iomanip>
#include <iostream>
using namespace std;
int main() {
double a = 1001.0, b = 0.0001;
cout << setprecision(16);
cout << "fmod: " << fmod(a, b) << endl;
cout << "remainder: " << remainder(a, b) << endl;
cout << "actual: " << a-floor(a/b)*b << endl;
cout << "a/b: " << a / b << endl;
}
Output:
fmod: 9.999999995203035e-05
remainder: -4.796965775988316e-14
actual: 0
a/b: 10010000
(same result with GCC, Clang, MSVC, with and without optimization)
If we modify your program to:
#include <cmath>
#include <iomanip>
#include <iostream>
int main() {
double a = 1001.0, b = 0.0001;
std::cout << std::setprecision(32) << std::left;
std::cout << std::setw(16) << "a:" << a << "\n";
std::cout << std::setw(16) << "b:" << b << "\n";
std::cout << std::setw(16) << "fmod:" << fmod(a, b) << "\n";
std::cout << std::setw(16) << "remainder:" << remainder(a, b) << "\n";
std::cout << std::setw(16) << "floor a/b:" << floor(a/b) << "\n";
std::cout << std::setw(16) << "actual:" << a-floor(a/b)*b << "\n";
std::cout << std::setw(16) << "a/b:" << a / b << "\n";
std::cout << std::setw(16) << "floor 10009999:" << floor(10009999.99999999952) << "\n";
}
It outputs:
a: 1001
b: 0.00010000000000000000479217360238593
fmod: 9.9999999952030347032290447106817e-05
remainder: -4.796965775988315527911254321225e-14
floor a/b: 10010000
actual: 0
a/b: 10010000
floor 10009999: 10010000
we can see that 0.0001
is not representable as a double
so b
is actually set to 0.00010000000000000000479217360238593
.
This results in a/b
being 10009999.9999999995203034224
which therefore means fmod
should return 1001 - 10009999*0.00010000000000000000479217360238593
which is 9.99999999520303470323e-5
.
(numbers calculated in speedcrunch so may not match exactly IEEE double values)
The reason your "actual" value is different is that floor(a/b)
returns 10010000
not the exact value used by fmod
which is 10009999
, this is itself due to 10009999.99999999952
not being representable as a double so it is rounded to 10010000
before being passed to floor.