Search code examples
c++compiler-optimization

How can I preserve the order of multiplications and divisions?


On my 32 bit embedded C++ application, I need to perform the following computation:

calc(int milliVolts, int ticks) {
  return milliVolts * 32767 * 65536 / 1000 / ticks;
}

Now, since an int on my platform has 32 bits and milliVolts has a range of [-1000:1000], the milliVolts * 32767 * 65536 part is could cause an integer overflow. To avoid this, I have split the factor 65536 into 1024, 32 and 2, and reordered as follows:

calc(int milliVolts, int ticks) {
  return milliVolts*32767*32/1000*1024/ticks*2;
}

This way, as long as the order of multiplications and divisions is preserved by the compiler, the function will compute correctly.

Kerninghan and Ritchie state in section 2.12 of "The C programming language" (I don't have a copy of the C++ standard handy):

C, like most languages does not specify the order in which the operands of an operator are evaluated.

If I understand this correctly, the compiler is free to change my carefully chosen expression into something that will not work as intended.

How can I write my function in such a way that it is guaranteed to work?

EDIT: Several answers below suggest using floating point calculations to avoid this issue. This is not an option because the code is running on a CPU that does not have floating point operations. Furthermore the calculation is in the hard realtime part of my application, so the speed penalty of using emulated floating point is too big.

CONCLUSION: With the help of Merdad's answer and Matt McNabb's comment, I managed to find the relevant section in K&R, section A7 where it says:

The precedence and associativity of operators is fully specified, but the order of evaluation of expressions is, with certain exceptions, undefined, even if subexpressions involve side effects. That is, unless the definition of an operator guarantees ths its operands are evaluated in a particular order, the implementation is free to evaluate operands in any order, or even to interleave their evaluation. However, each operator combines the values produced by its operands in a way compatible with the parsing of the expressions in which it appears. This rule revokes the previous freedom to reorder expressions with operators that are matematically commutative and associative, but can fail to be computationally associative. The change affects only floating-point computations near the limits of their accuracy, and situations where overflow os possible.

So Merdad is right: There is nothing to worry about.


Solution

  • Actually:

    You (and the others) are misunderstanding what is being said. There is no problem here.

    They're saying if you have f(x) + g(y), there is no guarantee that f(x) is evaluated before g(y) (or vice-versa).

    But if you have

    milliVolts * 32767 * 32 / 1000 * 1024 / ticks * 2
    

    it is automatically interpreted as

    (((((milliVolts * 32767) * 32) / 1000) * 1024) / ticks) * 2
    

    which means evaluating the left- and right-hand sides of any operator out of order will not result in any problem, since all the expressions to the right-hand side of an operator are either variables or numbers, in either case of which the evaluation of the right-hand side is a no-op (no side-effects, contrary to a function call).

    Thus there is nothing to be worried about.