Skip to main content

Command Palette

Search for a command to run...

Clojure Ratios

Updated
2 min read
Clojure Ratios

Computers do math really quickly. Yet, computers struggle with all kinds of things with math. Take, for example, the simple fraction: 2/3. This should be easy to store right? In most languages, it isn’t.

We’re going to use 2/3 throughout all our examples. We’ll use the literal 2/3 instead of (/ 2 3) because it’s more compact, and is equivalent:

(= 2/3 (/ 2 3))
;;=> true

2/3
;;=> 2/3

If you’ve programmed in most languages, you probably expected output like 0.6666667. In Clojure, we need to force that with a cast:

(float 2/3)
;;=> 0.6666667

After casting, we can see they’re not equal:

(= 2/3 (float 2/3))
;;=> false

So, what was that 2/3 we saw? Let’s inspect its type, and its value. I don’t want to write that more than once, so here’s a cool little helper function:

(defn value-and-type
  [& args]
  (map (juxt identity type) args))

Here it is in action:

(value-and-type 2/3 (float 2/3))
;;=> ([2/3 clojure.lang.Ratio]
;;    [0.6666667 java.lang.Float])

Aha! The 2/3 is a special Clojure Ratio type. What can we do with a Ratio?

Determine whether a value is one:

(ratio? 2/3)
;;=> true

(ratio? 3/3)
;;=> false

Note that rational? is a little more inclusive:

(rational? 3/3)
;;=> true

Inspect its numerator and denominator:

(numerator 2/3)
;;=> 2

(denominator 2/3)
;;=> 3

Do math operations without losing precision:

(value-and-type
 (* 2/3 3)
 (* (float 2/3) 3))
;;=> ([2N clojure.lang.BigInt]
;;    [2.0000000596046448 java.lang.Double])

That is pretty great!

You can attempt to turn a floating point number into a ratio. Beware!

(value-and-type
 (rationalize 2/3)
 (rationalize (float 2/3)))
;;=> ([2/3 clojure.lang.Ratio]
;;    [416666679084301/625000000000000
;;     clojure.lang.Ratio])

You may have guessed the following would fare better because our computers are binary, and you’d be right:

(value-and-type
 (rationalize (float 1/2))
 (rationalize (float 3/2)))
;;=> ([1/2 clojure.lang.Ratio]
;;    [3/2 clojure.lang.Ratio])

However, for even 2/3, BigInt can save the day!

(value-and-type
 (rationalize (/ 2N 3N)))
;;=> ([2/3 clojure.lang.Ratio])