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.
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,
Python can deduce that the return type of the function square is (single-float 0f0 100f0).(defun square (x) (declare (type (single-float 0f0 10f0))) (* x x))
Many union types are also supported so that
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)).(+ (the (or (integer 1 1) (integer 5 5)) x) (the (or (integer 10 10) (integer 20 20)) y))
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
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)).(defun fun (x) (declare (type (single-float 0f0 100f0) x)) (values (sqrt x) (log x 10f0)))
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.Thus, 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.
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.