Previous: Fixnum arithmetic, Up: Efficiency Tips


4.3.5 Flonum arithmetic

Getting efficient flonum arithmetic is much more complicated and harder than getting efficient fixnum arithmetic.

Flonum consing

One of the main disadvantages of generic arithmetic is that not all kinds of number fit in a machine register. Flonums have to be boxed because a 64-bit IEEE floating-point number (the representation that MIT/GNU Scheme uses) does not fit in a regular machine word. This is true even on 64-bit architectures because some extra bits are needed to distinguish floating-point numbers from other objects like pairs and strings. Values are boxed by storing them in a small record in the heap. Every floating-point value that you see at the REPL is boxed. Floating-point values are unboxed only for short periods of time when they are in the machine's floating-point unit and actual floating-point operations are being performed.

Numerical calculations that happen to be using floating-point numbers cause many temporary floating-point numbers to be allocated. It is not uncommon for numerical programs to spend over half of their time creating and garbage collecting the boxed flonums.

Consider the following procedure for computing the distance of a point (x,y) from the origin.

     (define (distance x y)
       (sqrt (+ (* x x) (* y y))))

The call `(distance 0.3 0.4)' returns a new, boxed flonum, 0.5. The calculation also generates three intermediate boxed flonums. This next version works only for flonum inputs, generates only one boxed flonum (the result) and runs eight times faster:

     (define (flo:distance x y)
       (flo:sqrt (flo:+ (flo:* x x) (flo:* y y))))

Note that flo: operations are usually effective only within a single arithmetic expression. If the expression contains conditionals or calls to procedures then the values tend to get boxed anyway.

Flonum vectors

Flonum vectors are vectors that contain only floating-point values, in much the same way as a string is a `vector' containing only character values.

Flonum vectors have the advantages of compact storage (about half that of a conventional vector of flonums) and judicious use of flonum vectors can decrease flonum consing.

The disadvantages are that flonum vectors are incompatible with ordinary vectors, and if not used carefully, can increase flonum consing. Flonum vectors are a pain to use because they require you to make a decision about the representation and stick with it, and it might not be easy to ascertain whether the advantages in one part of the program outweigh the disadvantages in another.

The flonum vector operations are:

— procedure: flo:vector-cons n

Create a flonum vector of length n. The contents of the vector are arbitrary and might not be valid floating-point numbers. The contents should not be used until initialized.

— procedure: flo:vector-ref flonum-vector index
— procedure: flo:vector-set! flonum-vector index value
— procedure: flo:vector-length flonum-vector

These operations are analogous to the ordinary vector operations.

Examples

The following operation causes no flonum consing because the flonum is loaded directly from the flonum vector into a floating-point machine register, added, and stored again. There is no need for a temporary boxed flonum.

     (flo:vector-set! v 0 (flo:+ (flo:vector-ref v 0) 1.2))

In this next example, every time g is called, a new boxed flonum has to be created so that a valid Scheme object can be returned. If g is called more often than the elements of v are changed then an ordinary vector might be more efficient.

     (define (g i)
       (flo:vector-ref v i))
Common pitfalls

Pitfall 1: Make sure that your literals are floating-point constants:

     (define (f1 a) (flo:+ a 1))
     (define (f2 a) (flo:+ a 1.))

f1 will most likely cause a hardware error, and certainly give the wrong answer. f2 is correct.

Pitfall 2: It is tempting to insert calls to exact->inexact to coerce values into flonums. This does not always work because complex numbers may be exact or inexact too. Also, the current implementation of exact->inexact is slow.

Pitfall 3: A great deal of care has to be taken with the standard math procedures. For example, when called with a flonum, both sqrt and asin can return a complex number (e.g with argument -1.5).