Complex bakeoff - example 1
Frank Farance
uunet!farance.com!frank
Fri Oct 6 10:21:49 PDT 1995
> Date: 3 Oct 1995 10:40:19 -0800
> From: "Jim Thomas" <jim_thomasataligent.com>
> Subject: (SC22WG14.1732) Complex bakeoff - example 1
> To: "numeric-interest" <numeric-interestavalidgh.com>,
> "x3j11 wg14" <sc22wg14adkuug.dk>
>
> Infinities, NaNs, and signed zeros are key to IEEE arithmetic's completeness
> property: operations always produce a well-defined result, which in turn
> behaves predictably in subsequent operations. For real arithmetic, this
> property has proven useful for writing simpler more robust codes.
I feel I occassionally need to make the following points
about exceptional values (infinities, NaNs, signed zeroes,
etc.) that seems to get lost when we discuss details. These
exceptional values aren't really values, but placeholders
that conveniently help us write expressions that maintain
their conceptual clarity while handling exceptional cases.
For example, in
T1 = R1/S1;
the fact that finite/infinite == zero and finite/zero ==
infinite is convenient for us *assuming we write code with
full knowledge of how the exception handling is performed*.
For example, the following code:
T2 = R2/S2; /* choice 1 */
T2 = R2/(1.0/(1.0/S2)); /* choice 2 */
would produce the same result for varying values of S2
(including zero and infinity). Thus, we might believe that
regardless of how we write the expression, the true
``mathematical'' result is returned. However, this is, in
general, not the case. For example, if R2 and S2 are really
the values:
R2 = X*X;
S2 = X;
the true mathematical result of T2 should always be S2.
Likewise, if R2 and S2 are the values:
R2 = X;
S2 = X*X;
the true mathematical result of T2 should always be the
reciprocal of R2. Clearly, we can't talk about the true
mathmematical value of T2 unless we know something about the
nature of the expression that equates to it. Even if the
hardware could keep track of various types of infinities
(e.g., infinity vs. infinity squared, infinity (the number
of integers) vs. 2^infinity (the number of reals)) it still
might not be able to get the correct result because the
knowledge of the nature of R2 and S2 might be outside the
program: the program receives its data via binary values,
rather than attribute-loaded symbolic values (e.g., negative
2^infinity).
Another example is:
T3 = (1.0/P3)*(1.0/Q3)*(1.0/R3)*(1.0/S3);
which might produce the wrong mathematical answer if the
compiler rewrites the expressions. Likewise, if we rewrite
this as:
T3 = (1.0/P3);
T3 *= (1.0/Q3);
T3 *= (1.0/R3);
T3 *= (1.0/S3);
we can still get wrong mathematical result. In these cases,
I've demonstrated that you can get the wrong mathematical
result even with unattributed infinities. Adding in NaNs,
signed zeroes, and signed infinities doesn't resolve the
problems.
As Harry Cheng pointed out, there are other ``infinity
paradigms'' that have a single infinity in contrast to
signed infinities. Each paradigm is useful for a certain
set of applications, but none are applicable 100% of the
time (another way of saying ``yes, there are several
branches of mathemetics'').
So why do we care about this? Basically, some of the
hardware features are convenient for some of the paradigms.
it allows us to write:
function(param1,param2,param3)
{
/*
* ...
* Lots of expressions with no
* testing for exceptions.
* ...
*/
if ( isexception(result1) ||
isexception(result2) ||
isexception(result3) )
{
/*
* Figure out what went wrong.
*/
}
}
When writing code that is expected to be executed in
parallel on thousands of processors or heavily pipelined, it
is convenient to have a linear flow control rather many
branching cases (e.g., "if" statements, checking "errno").
In addition to performance reasons, it is useful for program
clarity -- imagine if every subexpression were bracketed by
"if" statements.
With respect to imaginaries vs. complex, there is no
*mathmematical* need for a separate type. When speaking
about a pure imaginary number, e.g., 1.0I, there is no
difference between 1.0I and 0+1.0I because the zero, in this
case, is the value zero rather than a limiting value of zero
(e.g., X+1.0I as X approaches zero). Since zero is the
identity element in addition, 1.0I is required to be
identical to 0+1.0I. Of course, as soon as we implement
this on machines, there is a problem with the implementation
because the exception handling infinity*(1.0I) is not the
same as infinity*(0+1.0I) although they are *identical*
mathematically. The difference, is that a NaN is produced
and dragged around, invalidating many subexpressions that,
in the end, have to be discovered and re-run with the
exception recognition code.
The rationale for adding pure imaginaries to the type system
is that, from a programming perspective, we desire pure
imaginaries to behave like pure reals. If your hardware
doesn't have this exception handling capability for
infinities and NaNs (e.g., some non-IEEE hardware), you have
no choice but to write the expressions bracketed by "if"
statements. Thus, systems that don't support this exception
handling have less of a need for a separate imaginary type.
The rationale for adding imaginary types is not that it
allows code to get mathematically correct results, but it
facilitates performance and conceptual clarity improvements
in the goal of getting mathematically correct results on
hardware that supports these features.
I am in favor of the inclusion of an imaginary type in the
CCE proposal.
-FF
--------------------------------------------------------------------
Frank Farance, Farance Inc. E-mail: frankafarance.com
Telephone: +1 212 486 4700 FAX: +1 212 759 1605
ISO JTC1/SC22/WG14 & ANSI X3J11 (C Programming Language) Project Editor
More information about the Numeric-interest
mailing list