A fundamental part of "1974 C"'s simplicity, which unfortunately fell by the wayside as the language evolved, was that all operations other than loads and stores involved one of four data types: integers, floating-point numbers, object pointers, and function pointers. If compilers or programmers encountered a function call like:
int myFunction();
int x;
test(4, 1.0, &x, myFunction);
they could know the precise types of the arguments that test
should be expecting, even if they knew nothing about test
and myFunction
beyond the fact that the latter was a function returning int
.
Having computations like float1 = float2 + float3;
would have required that the logic include logic to handle the addition of float
values, in addition to the logic that would likely be needed elsewhere to add double
values, convert float
to double
, and convert double
to float
. By contrast, having all float
values converted to double
as soon as they're read allows compiler logic to be simplified.
Additionally, though I don't know if this was a consideration when Ritchie was first implementing C, this design also facilitated software floating-point implementations, at least until long double
was added in a manner that broke it. Processing a packed floating-point number without floating-point hardware generally requires performing a bunch of steps to unpack all of the bits into separate parts for the exponent, sign, and mantissa. If an implementation for the Motorola 68000 were to represent its double
type using a 64-bit mantissa without an implied 1, along with a 16-bit value that held the exponent and sign, the time required to perform a computation like a-b+c
in cases where all values were of similar magnitude may be less than the costs of converting the intermediate results to/from an IEEE-754 32-bit single-precision value or worse, an IEEE-754 64-bit double-precision value. The documentation for the Standard Apple Numerical Environment used on the Macintosh strongly recommended using 80-bit extended-precision type for most compuations, and minimizing the use of IEEE-754 64-bit type, for this reason.
Having different sizes of integer or floating-point values would have made the language much harder to work with in the days before function prototypes. Under 1974 rules, the function calls foo(v * 1.1f)
and foo(v * 1.1)
will both pass their argument the same way, unaffected by whether v
is float
or double
. If the program wants the value scaled by the numerical value 1.1, the scaling constant can be written as 1.1 without having to worry about whether the function might be expecting a float
.
The addition of integer types whose ranges go beyond INT_MIN
and INT_MAX
, as well as the long double
type could have been made less disruptive if there were a means by whcih variadic functions could indicate what type of integer and what type of floating-point value they wanted, and arguments that were not expressly cast into some other type would be coerced to the indicated form. Thus, for example, something like:
printf("%d %ld", x, (long)y)
would be able to handle out any integer value within the range of int
stored in x, regardless of its type, and would correctly process any numeric value in y
, again regardless of type. Code wanting to format long
values would need to include explicit casts in the argument expressions, but code where the format string matched the arguments would continue to do so even if objects of some integer types were replaced by objects of other integer types, and likewise for objects of floating-point types.
+F
is single-precision floating point add (N1 := N1 + N2),+FD
is double-precision (N1,N2 := N1,N2 + N3,N4).