Search code examples
ccompiler-construction

What does computationally associative mean?


K&R's C language has the following sentence:

A compiler's license to treat mathematically associative operators as computationally associative is revoked.

This is in the Appendix C, which tells what's different from before ANSI C. But I don't know how computationally associated is different from mathematically associated. Maybe I guess the mathematically associative is a * b * c = (a * b) * c (left), or a * (b * c) (right).


Solution

  • Consider this code:

    #include <stdio.h>
    
    int main(void)
    {
        double a = 0x1p64;  //  Two the power of 64, 18,446,744,073,709,551,616.
        double b = 1;
        double c = -a;
        printf("%g\n", a+b+c);
    }
    

    In the C grammar, a+b+c is equivalent to (a+b)+c, so a and b are added first, and then c is added. In the format commonly used for double, a+b yields 264, not 264+1, because the double format does not have enough precision to represent 264+1, so the result of the addition is the ideal mathematical result rounded to the nearest representable value, which is 264. Then adding c yields zero, so “0” is printed.

    If instead we calculated a+c+b, adding a and c would give zero, and then adding b would give one, and “1” would be printed.

    Thus, floating-point operations are not generally associative; a+b+c is not the same as a+c+b.

    In ordinary mathematics with real numbers, a+b+c is the same as a+c+b; addition of real numbers is associative.

    Prior to standardization, some C compilers would treat floating-point expressions as if operators were associative (for those operators whose counterparts in real-number-arithmetic were associative). The C standard does not permit that in implementations that conform to the standard. Conforming compilers must produce results as if the operations were performed in the order specified by the C grammar.

    Some compilers may still treat floating-point operators as associative when operating in non-standard modes, which may be selected by flags or switches passed to the compiler. Also, because the C standard allows implementations to perform floating-point arithmetic with more precision than the nominal type (e.g., when computing a+b+c, it can calculate it as if the types were long double instead of double), that can produce results that are the same as if operations were rearranged, so you can still get results that look like operators have been reordered associatively, depending on the C implementation and the flags used.