[net.unix-wizards] Floating Point and Other Such Mysteries

chris.umcp-cs@Udel-Relay@sri-unix (09/18/82)

From:     Chris Torek <chris.umcp-cs@Udel-Relay>
Date:     14 Sep 82 4:07:30-EDT (Tue)
Whitesmith's C for 8080s has an interesting compromise.  If you use

	float x, y; x = y + .01;

they convert y to double, add .01 (double), and convert the result to
float and stuff it in x.  But if you use

	float x, y; x = .01; x += y;

they do the operation in single precision!  This works for 'char' too.
Anyway, it seems to me that the rules for arithmetic could be revised
as follows:

1. Before function calls, all chars/floats are widened to ints/doubles.
   There is no such thing as a float or char function (though of course
   there ARE float *, char *).
2. There are such things as float constants.  FP constants are double
   until the entire expression is known to be at most float, excluding
   any FP constants.
3. Operations are done in the highest precision of all the operands,
   INCLUDING THE LVALUE on the left of the asgop.

Examples:
	char	c1, c2, c3;
	int	i1;
	float	f1;
	double	foo();

	c1 = 'x' + 'z';		/* compile time, CHAR */
	c2 = c1 + c3;		/* done in CHAR */
	c3 = c1 + bar('y');	/* done in INT */

	i1 = <anything>;	/* INT of course */
	f1 = 3.14159265358979;	/* FLOAT, though constant is DOUBLE */
	f1 *= 6;		/* FLOAT */
	f1 *= (double) .3;	/* still FLOAT */
	f1 *= foo();		/* DOUBLE */

The reason for #3 above is:

	char c1, c2; int i;

	i = c1 + c2;		/* don't want to add these as
				   CHAR because result is INT */

If c1+c2 were evaluated as CHAR, '~'+'~' would yeild a negative
result due to sign extension.  (It yeilds 252 in existing C.)

Doing something like

	int i; char c1, c2;

	i = c = c1 + c2;	/* i = (int)(char) <foo> done here */

WOULD yeild a negative result.


There is an easy way around all of this, though, which no one
seems to have mentioned:  use pointers EVERYWHERE!  No longer
do you use "double cos();"; intstead you have

	cos0 (op, res) float *op, *res;	{return;}
	cos1 (op, res) float *op; double *res; {return;}
	cos2 (op, res) double *op; float *res; {return;}
	cos3 (op, res) double *op, *res; {return;}

(just like FORTRAN!).  Yes this is grungy but it will work
in all existing C compilers and it is portable, and not really
all that hard.  If you really want efficiency it's probably better
to pass pointers (which are nice handy machine word size) than
big hairy 8 byte floating point numbers.  (Of course, you
need to write a whole new math library...)

ark (09/19/82)

The trouble with saying that an expression is float unless some
wider context needs it as double is that then when you see

	a + b

and a and b are both float, you don't know the true meaning of +.
It seems a bad idea in general to make the interpretation of an
expression depend on its context.

mark (09/20/82)

Be especially careful of cases like this:
	char foo();
	char a, b;
	double x, y;

	x = y * foo(a, b+1);

Some of the previous proposals would note that the assignment is done in
double, and so would calculate foo(a, b+1) in double.  Not only is it
wasteful (and inaccurate) to calculate b+1 in double, but since foo
is separately compiled and probably expects integer arguments, passing
it doubles and expecting a double back simply won't work.

You can't just look for the widest part of an expression, you have to
consider the pieces of it separately.  Another language that has to do
this kind of thing, depending on context, is Ada.  There was a long
controversy as people proposed cubic and quadratic algorithms to figure
out the types necessary for expression evaluation.  Finally somebody
came up with a clever linear algorithm, but the moral is clear: C is
supposed to be simple, and this kind of complexity does not belong in
the language.  If someone would (and could) guarantee to implement
	float a, b, c;
	a = b + c;
with single precision arithmetic, it would be possible to break down
hairier expressions into exactly what the author intends, without
lots of complex rules about context.

	Mark Horton