rodz's blog

By rodz, 4 years ago, In English

Consider the following C++ code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
double f(ll x,ll y,ll p,ll q)
{
    double n = ((1.0 * p) * (1.0*y)) / (1.0*q) - x;
    double d = (1.0 - (1.0*p)/(1.0*q));
    return n/d;
}
int main()
{
	ll x = 1, y = 2, p = 499999999 , q = 500000000;
	cout << fixed << setprecision(6);
	cout << f(x, y, p, q) << endl;
        cin >> x >> y >> p >> q;
	cout << f(x, y, p, q) << endl;
	return 0;
}

When I run it on Custom Invocation with input 1 2 499999999 500000000 it prints two different values. I know floating point calculations may have precision issues but why does it make a difference whether I read the input from stdin or hardcode it into the variables?

 
 
 
 
  • Vote: I like it
  • +11
  • Vote: I do not like it

»
4 years ago, # |
  Vote: I like it +3 Vote: I do not like it

Maybe, it is because when computing the answer, you divide over d. And d is almost 0, so n / d is inf or nan? Or better said, undefined behavior. Then, the results could be different.

  • »
    »
    4 years ago, # ^ |
    Rev. 2   Vote: I like it 0 Vote: I do not like it

    In this case, d = 2e-9. It's a small value, but isn't NaN or inf. I don't know if it is small enough to cause undefined behavior. But yes, undefined behavior is the best explanation I have so far. I wanted to know if anyone here has a better / more detailed explanation.

»
4 years ago, # |
Rev. 2   Vote: I like it +36 Vote: I do not like it

In this particular case, optimizer is probably the one playing tricks. Add #pragma GCC optimize("O0") on the top of your code (somewhat equivalent to -O0 in command line) and see if behavior now becomes same for both invocations. For example, optimizer may:

  1. Inline f.
  2. Reorder calculations differently in two different cases.
  3. Pre-calculate the first value printed. Again, in different order.
  4. Use double (64-bit) in some places and FPU registers (80-bit) in other places, which will cause different precisions.

If you want to dig deeper, I suggest you look at optimized tree or assembly code generated.

I believe there are also a plenty of other reasons. I can't stress this enough: never rely on floating point computations! Re-ordering and "obvious optimizations" may and will change calculation error.

  • »
    »
    4 years ago, # ^ |
      Vote: I like it +10 Vote: I do not like it

    Great answer! It seems you are correct. Thank you. Will try harder to avoid using floating points.