Lisp: coerce float to rational

2007-07-10

Since all floats are mathematical rationals you might expect to be able to coerce a float to a rational. But you can’t:

* (coerce 1d0 'rational)

debugger invoked on a SIMPLE-TYPE-ERROR:
  1.0d0 can't be converted to type RATIONAL.

Here’s a function which does what (coerce x 'rational) might do:

(defun coerce-float-to-rational (x)
  (declare (type float x))
  (let ((b (float-radix x)))
    (multiple-value-bind (signif e s) (integer-decode-float x)
      (* s signif (expt b e))))) 

[Edit on 2007-07-12: jaoswald points out that this functionality is provided by the function rational. So my code could be (part of) an implementation of rational.]

Example:

CL-USER> (coerce-float-to-rational 3.141592653589793)
13176795/4194304                                                                
CL-USER> (coerce-float-to-rational 3.141592653589793d0)
884279719003555/281474976710656                                                 

Numbers that are mathematical integers (which includes all sufficiently large ones) come out as integer objects. Note that for very large numbers you quickly exhaust the precision of the underlying float type:

CL-USER> (coerce-float-to-rational 1e10)
10000000000                                                                     
CL-USER> (coerce-float-to-rational 1e20)
100000002004087734272                                                           

2 Responses to “Lisp: coerce float to rational”

  1. jaoswald Says:

    Look in the Hyperspec permuted symbol index under “R” for “Rational.” You can follow the links to the functions RATIONAL and RATIONALIZE.

    (rational 1e20) will give you a precise rational value, (rationalize 1e20) will be more likely to get a “round” value.

    http://www.lisp.org/HyperSpec/Body/fun_rationalcm_rationalize.html

  2. drj11 Says:

    @jaoswald: Thanks for that. I’m a bit of a Lisp novice, so it’s easy for me to not find or simply not know about stuff. I’ve edited the article to add a link.


Leave a comment