next up previous contents
Next: 5.11.8 Specialized Arrays Up: 5.11 Numbers Previous: 5.11.6 Word Integers

5.11.7 Floating Point Efficiency

   

Arithmetic on objects of type single-float and double-float is efficiently implemented using non-descriptor representations and open coding. As for integer arithmetic, the arguments must be known to be of the same float type. Unlike for integer arithmetic, the results and intermediate values usually take care of themselves due to the rules of float contagion, i.e. (1+ (the single-float x)) is always a single-float.

Although they are not specially implemented, short-float and long-float are also acceptable in declarations, since they are synonyms for the single-float and double-float types, respectively.


change_begin
Some versions of CMU Common Lisp include extra support for floating point arithmetic. In particular, if *features* includes :propagate-float-type, list-style float type specifiers such as (single-float 0.0 1.0) will be used to good effect.

For example, in this function,

(defun square (x)
      (declare (type (single-float 0f0 10f0)))
      (* x x))
  
Python can deduce that the return type of the function square is (single-float 0f0 100f0).

Many union types are also supported so that

(+ (the (or (integer 1 1) (integer 5 5)) x)
       (the (or (integer 10 10) (integer 20 20)) y))
  
has the inferred type (or (integer 11 11) (integer 15 15) (integer 21 21) (integer 25 25)). This also works for floating-point numbers. Member types, however, are not because in general the member elements do not have to be numbers. Thus, instead of (member 1 4), you should write (or (integer 1 1) (integer 4 4)).

In addition, if :propagate-fun-type is in *features*, Python knows how to infer types for many mathematical functions including square root, exponential and logarithmic functions, trignometric functions and their inverses, and hyperbolic functions and their inverses. For numeric code, this can greatly enhance efficiency by allowing the compiler to use specialized versions of the functions instead of the generic versions. The greatest benefit of this type inference is determining that the result of the function is real-valued number instead of possibly being a complex-valued number.

For example, consider the function

(defun fun (x)
      (declare (type (single-float 0f0 100f0) x))
      (values (sqrt x) (log x 10f0)))
  
With this declaration, the compiler can determine that the argument to sqrt and log are always non-negative so that the result is always a single-float. In fact, the return type for this function is derived to be (values (single-float 0f0 10f0) (single-float * 2f0)).

If the declaration were reduced to just (declare single-float x), the argument to sqrt and log could be negative. This forces the use of the generic versions of these functions because the result could be a complex number.

Union types are not yet supported for functions.

We note, however, that proper interval arithmetic is not fully implemented in the compiler so the inferred types may be slightly in error due to round-off errors. This round-off error could accumulate to cause the compiler to erroneously deduce the result type and cause code to be removed as being unreachable.gifThus, the declarations should only be precise enough for the compiler to deduce that a real-valued argument to a function would produce a real-valued result. The efficiency notes (see section 5.13.3) from the compiler will guide you on what declarations might be useful.


change_end

When a float must be represented as a descriptor, a pointer representation is used, creating consing overhead. For this reason, you should try to avoid situations (such as full call and non-specialized data structures) that force a descriptor representation. See sections 5.11.8, 5.11.9 and 5.11.10.

See section 2.1.3 for information on the extensions to support IEEE floating point.


next up previous contents
Next: 5.11.8 Specialized Arrays Up: 5.11 Numbers Previous: 5.11.6 Word Integers

Raymond Toy
Mon Jul 14 09:11:27 EDT 1997