3

I am looking for ways to force gcc to respect a particular order of operations (it does not have to be the order that minimizes floating point error, just whatever order I happen to want) regarding double-precision arithmetic of a particular section of code, while allowing full -O3 -funsafe-math-optimizations elsewhere. I have seen a few posts on this matter:

Are floating point operations in C associative?

https://devblogs.microsoft.com/cppblog/do-you-prefer-fast-or-precise/

C/C++ Math Order of Operation

The above links discuss global options, but I'd like to understand if local compiler directives exist. For example, when doing hardware synthesis, it is possible to force the compiler to synthesize a logical expression verbatim without doing any reduction.

For example, consider

#include <iostream>
#include <string>
#include <iomanip>

using std::cout;

int main()
{
    double a = 1.0 + 1.3e-16;
    double b = 1.0;
    double c = 100.0;

    double d = (a - b) / c;
    double e = a / c - b / c;

    cout << std::scientific;
    cout << "Calculation d = " << d << "\n";
    cout << "Calculation e = " << e << "\n";

    double f = 1.0;
    double g = 2e-15;
    double h = 1.0;
    double i = 1e-15;

    double j = (f + g) - (h + i);
    double k = (f - h) + (g - i);
    double m = f + g - h - i;
    double n = f - h + g - i;

    cout << "Calculation j = " << j << "\n";
    cout << "Calculation k = " << k << "\n";
    cout << "Calculation m = " << m << "\n";
    cout << "Calculation n = " << n << "\n";
}

Using http://cpp.sh, I get

Calculation d = 2.220446e-18
Calculation e = 1.734723e-18
Calculation j = 8.881784e-16
Calculation k = 1.000000e-15
Calculation m = 9.984014e-16
Calculation n = 1.000000e-15

This was independent of the optimization level I used, so it seems that the compiler will calculate things in the order I expect based on the expression, plus even maintains left-right order for the +/- But is this absolutely guaranteed, when optimizations are turned on?

I can think of a scenario for example where one of the operands is already sitting in some useful register whereby it would save some memory move to compute things in a different order than I wrote, but would maintain symbolic correctness.

Basically I can't begin to try different hacks to fix the issue, e.g., I read that declaring sub-products as volatile variables would help, since my example doesn't show the problem. So I'd like to know if this is indeed a non-problem, or if not, what tricks exist.

Colin Weltin-Wu
  • 347
  • 1
  • 2
  • 10
  • Would memory barriers do what you're looking for? – Christian Gibbons Sep 18 '19 at 22:17
  • 1
    While you can use compiler options, there is no guarantee that the compiler obeys your expected order of operations, precisely. A compiler only guarantees to not change the meaning of the given program. – Ali Abbasinasab Sep 18 '19 at 22:26
  • 3
    It's a good question! One that needs some thought. But, in the meantime, please use 'better' code - rather than `using namespace std` (lazy) do `using std::cout`. You're better than that. – Adrian Mole Sep 18 '19 at 22:33
  • Hoookay no lazy :(. I looked at memory barriers, I had to Google, and it looks like it would do what I want but like swatting a fly with a hammer (this is actually very hard). I think it would also be a massive performance hit and I can't afford that where these computations lie. Thanks though. – Colin Weltin-Wu Sep 18 '19 at 23:44
  • 2
    lol - if it gets that important, asm. – Michael Dorgan Sep 18 '19 at 23:46
  • 1
    If you don't want the compiler to optimise a specific function have a look at this: https://stackoverflow.com/questions/4713320/is-there-any-way-to-disable-compiler-optimisation-for-a-specific-line-of-code You will need to make sure that the code you put into that function is optimised by hand... – Jerry Jeremiah Sep 19 '19 at 01:46
  • Keep in mind that no matter what you do, [floating point math will always be slightly inaccurate](https://stackoverflow.com/q/588004/10957435). If you need that level of accuracy, you may just need a more precision. –  Sep 19 '19 at 05:20
  • 1
    You can put every intermediate result in a volatile variable, and re-read it from that variable. – Marc Glisse Sep 19 '19 at 06:22
  • My only constraint is I rely on the deterministic behavior of floating point computations, which as I understand is guaranteed by the IEEE standard, secondary consideration is the absolute precision. I just need to guarantee the exact sequence of operations is what I want it to be. I think I will end up going the assembly route one day. I would try the trick with volatiles but first I need to concoct a test case. Thanks everyone for the ideas! – Colin Weltin-Wu Sep 19 '19 at 17:14

0 Answers0