In Common Lisp, arithmetic operations are generic.12
The +
function can be passed fixnum
s, bignum
s,
ratio
s, and various kinds of float
s and
complex
es, in any combination. In addition to the inherent
complexity of bignum
and ratio
operations, there is also
a lot of overhead in just figuring out which operation to do and what
contagion and canonicalization rules apply. The complexity of generic
arithmetic is so great that it is inconceivable to open code it.
Instead, the compiler does a function call to a generic arithmetic
routine, consuming many instructions before the actual computation
even starts.
This is ridiculous, since even Common Lisp programs do a lot of arithmetic, and the hardware is capable of doing operations on small integers and floats with a single instruction. To get acceptable efficiency, the compiler special-cases uses of generic arithmetic that are directly implemented in the hardware. In order to open code arithmetic, several constraints must be met:
(+ a b)
in the call (+ a b c)
must be known to be a good type of
number.
The “good types” are (signed-byte 32)
,
(unsigned-byte 32)
, single-float
,
double-float
, (complex single-float)
, and (complex
double-float)
. See sections fixnums, word-integers
and float-efficiency for more discussion of good numeric types.
float
is not a good type, since it might mean either
single-float
or double-float
. integer
is not a
good type, since it might mean bignum
. rational
is not
a good type, since it might mean ratio
. Note however that
these types are still useful in declarations, since type inference may
be able to strengthen a weak declaration into a good one, when it
would be at a loss if there was no declaration at all
(see type-inference). The integer
and
unsigned-byte
(or non-negative integer) types are especially
useful in this regard, since they can often be strengthened to a good
integer type.
As noted above, CMUCL has support for (complex single-float)
and (complex double-float)
. These can be unboxed and, thus,
are quite efficient. However, arithmetic with complex types such as:
(complex float) (complex fixnum)
will be significantly slower than the good complex types but is still
faster than bignum
or ratio
arithmetic, since the
implementation is much simpler.
Note: don’t use /
to divide integers unless you want the
overhead of rational arithmetic. Use truncate
even when you
know that the arguments divide evenly.
You don’t need to remember all the rules for how to get open-coded arithmetic, since efficiency notes will tell you when and where there is a problem—see efficiency-notes.
As Steele notes in CLTL II, this is a generic conception of generic, and is not to be confused with the CLOS concept of a generic function.