ok@quintus.UUCP (Richard A. O'Keefe) (01/18/88)
Formerly, C treated parentheses according to mathematical practice: they had only a grouping effect, and whether (a+b)-c was reordered or not was up to the compiler. FORTRAN and some other languages give parentheses an operational effect as well. That is, (a+b)-c and a+b-c are different expressions and the programmer is entitled to assume that the calculation is as written, not (a-c)+b or any other re-ordering. The ANSI C committee, realising that control over the evaluation of floating-point calculations was desirable, suggested adding an identity function to C: +(expr) would have the same value as (expr) but would not allow re-ordering across the "+". This is less of a kludge than the FORTRAN method, because it would let the reader see when the ordering mattered (a unary "+" would flag this case) and when parentheses were just for syntax. The snag with the FORTRAN method is that you get strict ordering whether you want it or not. Apparently, a lot of people wrote to the ANSI C committee said that they would write numerical software in C, only if it looked like FORTRAN. (Sorry, only if it had strict parens.) The point of this message is that strict parentheses do not provide sufficient control over floating-point arithmetic. Something stronger is needed. This is just as true in Fortran as in C. (In fact I met the problem in Fortran several years before I first heard of C.) The problem is this: Several machines support higher precision in their floating point registers than in storage. The PR1ME superminis are an example. Floating-point coprocessors with an 80-bit format (such as the MC68881) are another. This means that DOUBLE PRECISION TEMP, X, Y | double temp, x, y; TEMP = X+Y | temp = x+y; IF (TEMP .LT. 0.0D0) CALL HOME | if (temp < 0.0) home(); and IF (X+Y .LT. 0.0D0) CALL HOME | if (x+y < 0.0) home(); may behave differently. Putting parentheses around X+Y doesn't cure the problem, because this tends to be compiled as load X into FP reg [converts to high precision] add Y to FP reg [result is in high precision] test sign of FP reg [high precision comparison] whereas the version which stores into TEMP would have rounded or truncated. Basically, any floating-point comparison with an operand which is neither a constant, nor a variable, nor a function call beware of intrinsics!) is suspect. In fact, function calls in C may not be safe either (they should be OK in FORTRAN, as defining the result looks like a store into memory). The answer is obvious: you have to say exactly what precision you want. FORTRAN and C both permit this. FLOAT(X) | (float)(x) DBLE(X) | (double)(x) specify that X is to be converted to single or double precision, respectively. Since FLOAT(I) converts an integer UP to single precision and FLOAT(D) converts a double DOWN to single precision, you would think that FLOAT(X) means "convert X to *exactly* single precision", and DBLE(X) means "convert X to *exactly* double precision". So you'd write IF (DBLE(X+Y) .LT. 0.0D0) CALL HOME or if ((double)(x+y) < 0.0) home(); in the assurance that since you had now specified the precision you wanted, that's what you'd get. 'Tisn't so. In the compilers I've tried, if an expression is already in double [*really* "extended"] precision, DBLE(expr) or (double)(expr) leave the intermediate result unchanged instead of converting it as would be done for storing in memory. I haven't got a copy of the FORTRAN standard, so I don't know whether this is legal in FORTRAN, and the ANSI C standard isn't finished yet. It seems to me that FLOAT()/(float) and DBLE()/(double) should mean "convert the operand to exactly the precision that would be given to a REAL {DOUBLE PRECISION} variable in storage." Note that only converting an expression which already appears to have the desired type is changed, and then only if intermediate results are calculated in a higher precision. Most programs should notice no change. Without this definition in the standard, you have to explicitly introduce temporary variables, as shown above. As things stand, all FLOAT(float) or DBLE(double) are good for is providing strict parentheses... I haven't had this problem myself, for the simple reason that a friend of mine had so much trouble with it that it has always stuck in my memory. But it hits, for example, the subroutine MACHAR in the Cody & Waite ELEFUNT package. Can anyone think of a reason why FLOAT & DBLE should *not* have the effect I suggest?
ags@j.cc.purdue.edu (Dave Seaman) (01/21/88)
In article <546@cresswell.quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe) writes: >Formerly, C treated parentheses according to mathematical practice: >they had only a grouping effect, and whether (a+b)-c was reordered >or not was up to the compiler. >FORTRAN and some other languages give parentheses an operational >effect as well. That is, (a+b)-c and a+b-c are different expressions >and the programmer is entitled to assume that the calculation is as >written, not (a-c)+b or any other re-ordering. >... The snag with the FORTRAN >method is that you get strict ordering whether you want it or not. It is not true that FORTRAN gives you strict ordering. Taking your example, if a FORTRAN programmer writes (a+b)-c, then he is entitled to assume that it is interpreted as one of the following: (a+b)-c (b+a)-c -c+(a+b) -c+(b+a) but not as: (a-c)+b (b-c)+a (-c+a)+b (-c+b)+a b+(a-c) a+(b-c) b+(-c+a) a+(-c+b) The C programmer, on the other hand, cannot rule out any of the interpretations in the second group. The difference is crucial because floating point arithmetic is commutative but not associative. Therefore the FORTRAN programmer knows that the value of the expression cannot be changed by the compiler's evaluation order, while the C programmer has no such assurance. >It seems to me that FLOAT()/(float) and DBLE()/(double) should mean >"convert the operand to exactly the precision that would be given >to a REAL {DOUBLE PRECISION} variable in storage." >Note that only converting an expression which already appears to >have the desired type is changed, and then only if intermediate >results are calculated in a higher precision. Most programs should >notice no change. > >Without this definition in the standard, you have to explicitly >introduce temporary variables, .... This seems to be a reasonable requirement, especially in view of the fact that without it you have absolutely no guarantee, EVEN IF YOU INTRODUCE TEMPORARY VARIABLES, that the result will be as it would be stored in memory. This is because an optimizing compiler is perfectly free to assign your temporary variables to registers rather than to memory. -- Dave Seaman ags@j.cc.purdue.edu