Skip to main content

Command Palette

Search for a command to run...

Clojure Ratios

Updated
2 min read
Clojure Ratios
B

I like programming (at work) and learning for fun. You'll often find me cooking and working on my house in my spare time.

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])