12.2. Number Concepts [CLHS-12.1]

12.2.1. Byte Operations on Integers [CLHS-12.1.1.3.2]
12.2.2. Rule of Float Substitutability [CLHS-12.1.3.3]
12.2.3. Floating-point Computations [CLHS-12.1.4]
12.2.3.1. Rule of Float Precision Contagion [CLHS-12.1.4.4]
12.2.3.2. Rule of Float and Rational Contagion [CLHS-12.1.4.1]
12.2.4. Complex Computations [CLHS-12.1.5]
12.2.5. Rule of Canonical Representation for Complex Rationals [CLHS-12.1.5.3]

12.2.1. Byte Operations on Integers [CLHS-12.1.1.3.2]

Byte specifiers are objects of built-in type BYTE, not INTEGERs.

12.2.2. Rule of Float Substitutability [CLHS-12.1.3.3]

When a mathematical function may return an exact (RATIONAL) or inexact (FLOAT) result, it always returns the exact result.

12.2.3. Floating-point Computations [CLHS-12.1.4]

There are four floating point types: SHORT-FLOAT, SINGLE-FLOAT, DOUBLE-FLOAT and LONG-FLOAT:

typesignmantissaexponentcomment
SHORT-FLOAT1 bit16+1 bits8 bitsimmediate
SINGLE-FLOAT1 bit23+1 bits8 bitsIEEE 754
DOUBLE-FLOAT1 bit52+1 bits11 bitsIEEE 754
LONG-FLOAT1 bit>=64 bits32 bitsvariable length

The single and double float formats are those of the IEEE 754Standard for Binary Floating-Point Arithmetic”, except that CLISP does not support features like ±0, ±inf, NaN, gradual underflow, etc. Common Lisp does not make use of these features, so, to reduce portability problems, CLISP by design returns the same floating point results on all platforms (CLISP has a floating-point emulation built in for platforms that do not support IEEE 754). Note that

  • When you got a NaN in your program, your program is broken, so you will spend time determining where the NaN came from. It is better to SIGNAL an ERROR in this case.
  • When you got unnormalized floats in your program, your results will have a greatly reduced accuracy anyway. Since CLISP has the means to cope with this - LONG-FLOATs of variable precision - it does not need unnormalized floats.

This is why *FEATURES* does not contain the :IEEE-FLOATING-POINT keyword.

Arbitrary Precision Floats. LONG-FLOATs have variable mantissa length, which is a multiple of 16 (or 32, depending on the word size of the processor). The default length used when LONG-FLOATs are READ is given by the place (EXT:LONG-FLOAT-DIGITS). It can be set by (SETF (EXT:LONG-FLOAT-DIGITS) n), where n is a positive INTEGER. E.g., (SETF (EXT:LONG-FLOAT-DIGITS) 3322) sets the default precision of LONG-FLOATs to about 1000 decimal digits.

12.2.3.1. Rule of Float Precision Contagion [CLHS-12.1.4.4]

The floating point contagion is controlled by the variable CUSTOM:*FLOATING-POINT-CONTAGION-ANSI*. When it is non-NIL, contagion is done as per the [ANSI CL]: SHORT-FLOATSINGLE-FLOATDOUBLE-FLOATLONG-FLOAT.

Rationale:
See it pragmatically: save what you can and let others worry about the rest.
Brief:
Common Lisp knows the number's precision, not accuracy, so preserving the precision can be accomplished reliably, while anything relating to the accuracy is just a speculation - only the user (programmer) knows what it is in each case.
Detailed:
A computer float is an approximation of a real number. One can think of it as a random variable with the mean equal to itself and standard deviation equal to half the last significant digit. E.g., 1.5 is actually 1.5±0.05. Consider adding 1.5 and 1.75. [ANSI CL] requires that (+ 1.5 1.75) return 3.25, while traditional CLISP would return 3.3. The implied random variables are: 3.25±0.005 and 3.3±0.05. Note that the traditional CLISP way does lie about the mean: the mean is 3.25 and nothing else, while the standard way could be lying about the deviation (accuracy): if the implied accuracy of 1.5 (0.05) is its actual accuracy, then the accuracy of the result cannot be smaller that that. Therefore, since Common Lisp has no way of knowing the actual accuracy, [ANSI CL] (and all the other standard engineering programming languages, like C, Fortran etc) decides that keeping the accuracy correct is the business of the programmer, while the language should preserve what it can - the precision.
Experience:
Rounding errors accumulate, and if a computation is conducted with insufficient precision, an outright incorrect result can be returned. (E.g., E(x2) - E(x)2 can be negative!) The user should not mix floats of different precision (that's what CUSTOM:*WARN-ON-FLOATING-POINT-CONTAGION* is for), but one should not be penalized for this too harshly.

When CUSTOM:*FLOATING-POINT-CONTAGION-ANSI* is NIL, the traditional CLISP method is used, namely the result of an arithmetic operation whose arguments are of different float types is rounded to the float format of the shortest (least precise) of the arguments: RATIONALLONG-FLOATDOUBLE-FLOATSINGLE-FLOATSHORT-FLOAT (in contrast to 12.1.4.4 Rule of Float Precision Contagion!)

Rationale:
See it mathematically. Add intervals: {1.0 ± 1e-8} + {1.0 ± 1e-16} = {2.0 ± 1e-8}. So, if we add 1.0s0 and 1.0d0, we should get 2.0s0.
Brief:
Do not suggest accuracy of a result by giving it a precision that is greater than its accuracy.
Example:
(- (+ 1.7 PI) PI) should not return 1.700000726342836417234L0, it should return 1.7f0 (or 1.700001f0 if there were rounding errors).
Experience:
If in a computation using thousands of SHORT-FLOATs, a LONG-FLOAT (like PI) happens to be used, the long precision should not propagate throughout all the intermediate values. Otherwise, the long result would look precise, but its accuracy is only that of a SHORT-FLOAT; furthermore much computation time would be lost by calculating with LONG-FLOATs when only SHORT-FLOATs would be needed.

If the variable CUSTOM:*WARN-ON-FLOATING-POINT-CONTAGION* is non-NIL, a WARNING is emitted for every coercion involving different floating-point types. As explained above, float precision contagion is not a good idea. You can avoid the contagion by doing all your computations with the same floating-point type (and using FLOAT to convert all constants, e.g., PI, to your preferred type).

This variable helps you eliminate all occurrences of float precision contagion: set it to T to have CLISP SIGNAL a WARNING on float precision contagion; set it to ERROR to have CLISP SIGNAL an ERROR on float precision contagion, so that you can look at the stack backtrace.

12.2.3.2. Rule of Float and Rational Contagion [CLHS-12.1.4.1]

The contagion between floating point and rational numbers is controlled by the variable CUSTOM:*FLOATING-POINT-RATIONAL-CONTAGION-ANSI*. When it is non-NIL, contagion is done as per the [ANSI CL]: RATIONALFLOAT.

When CUSTOM:*FLOATING-POINT-RATIONAL-CONTAGION-ANSI* is NIL, the traditional CLISP method is used, namely if the result is mathematically an exact rational number, this rational number is returned (in contrast to 12.1.4.1 Rule of Float and Rational Contagion!)

CUSTOM:*FLOATING-POINT-RATIONAL-CONTAGION-ANSI* has an effect only in those few cases when the mathematical result is exact although one of the arguments is a floating-point number, such as (* 0 1.618), (/ 0 1.618), (ATAN 0 1.0), (EXPT 2.0 0), (PHASE 2.718).

If the variable CUSTOM:*WARN-ON-FLOATING-POINT-RATIONAL-CONTAGION* is non-NIL, a WARNING is emitted for every avoidable coercion from a rational number to a floating-point number. You can avoid such coercions by calling FLOAT to convert the particular rational numbers to your preferred floating-point type.

This variable helps you eliminate all occurrences of avoidable coercions to a floating-point number when a rational number result would be possible: set it to T to have CLISP SIGNAL a WARNING in such situations; set it to ERROR to have CLISP SIGNAL an ERROR in such situations, so that you can look at the stack backtrace.

A similar variable, CUSTOM:*PHASE-ANSI*, controls the return value of PHASE when the argument is an exact nonnegative REAL. Namely, if CUSTOM:*PHASE-ANSI* is non-NIL, it returns a floating-point zero; if CUSTOM:*PHASE-ANSI* is NIL, it returns an exact zero. Example: (PHASE 2/3)

12.2.4. Complex Computations [CLHS-12.1.5]

Complex numbers can have a real part and an imaginary part of different types. For example, (SQRT -9.0) evaluates to the number #C(0 3.0), which has a real part of exactly 0, not only 0.0 (which would mean “approximately 0”).

The type specifier for this is (COMPLEX INTEGER SINGLE-FLOAT), and (COMPLEX type-of-real-part type-of-imaginary-part) in general.

The type specifier (COMPLEX type) is equivalent to (COMPLEX type type).

12.2.5. Rule of Canonical Representation for Complex Rationals [CLHS-12.1.5.3]

Complex numbers can have a real part and an imaginary part of different types. If the imaginary part is EQL to 0, the number is automatically converted to a real number.

This has the advantage that (LET ((x (SQRT -9.0))) (* x x)) - instead of evaluating to #C(-9.0 0.0), with x = #C(0.0 3.0) - evaluates to #C(-9.0 0) = -9.0, with x = #C(0 3.0).


These notes document CLISP version 2.41Last modified: 2006-10-13