c++floating-pointprecisionfmod

std::fmod abysmal double precision


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)

Live demo


Solution

  • 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.