Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does setting a const variable (which will be stored with the same value) lead to a different result once divided?

Pretty basic code:

#include <iostream>

int main() {
    std::cout.precision(100);

    double a            = 9.79999999999063220457173883914947509765625;
    double b            = 0.057762265046662104872599030613855575211346149444580078125;
    const double bConst = 0.057762265046662104872599030613855575211346149444580078125;
    double c            = a * b;

    std::cout << "        a: " << a << std::endl;
    std::cout << "        b: " << b << std::endl;
    std::cout << "   bConst: " << bConst << std::endl;
    std::cout << "        c: " << c << std::endl << std::endl;  
    std::cout << "      c/b: " << c / b << std::endl;   
    std::cout << " c/bConst: " << c / bConst << std::endl;  
}

Which outputs:

        a: 9.79999999999063220457173883914947509765625
        b: 0.057762265046662104872599030613855575211346149444580078125
   bConst: 0.057762265046662104872599030613855575211346149444580078125
        c: 0.5660701974567474703547986791818402707576751708984375

      c/b: 9.7999999999906304282148994388990104198455810546875
 c/bConst: 9.79999999999063220457173883914947509765625

As you can see, b and bConst seem to be treated using the same value - i.e. it prints for both the same 0.057762265046662104872599030613855575211346149444580078125 value. So I guess they are "stored" both the same. The only difference is that b is not const.

Then, I do the same c / b operation twice: one time using b, another time using bConst.

As you can see, it leads to two different results. And this makes me wonder.

Can you explain technically why this happens?

like image 526
markzzz Avatar asked Feb 15 '19 10:02

markzzz


2 Answers

The "issue" is due to the -freciprocal-math switch (implied by -Ofast):

Allow the reciprocal of a value to be used instead of dividing by the value if this enables optimizations. For example x / y can be replaced with x * (1/y), which is useful if (1/y) is subject to common subexpression elimination. Note that this loses precision and increases the number of flops operating on the value.

The compiler can calculate d = 1/bConst at compile time and change from:

c/bConst

to

c * d

but multiplication and division are different instructions with different performance and precision.

See: http://coliru.stacked-crooked.com/a/ba9770ec39ec5ac2

like image 65
manlio Avatar answered Sep 28 '22 06:09

manlio


You are using -Ofast in your link, which enables all -O3 optimizations and includes both -ffast-math, which in turns includes -funsafe-math-optimizations.

From what I could glean, with optimizations enabled, -funsafe-math-optimizations allows the compiler to reduce the precision of some computations. This seems to be what happens in the c/bConst case.

like image 36
Nelfeal Avatar answered Sep 28 '22 07:09

Nelfeal