<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>tag:blog.danieljanus.pl,2019:category:wat</id>
  <title>Daniel Janus – wat</title>
  <link href="http://blog.danieljanus.pl/category/wat/"/>
  <updated>2025-02-21T00:00:00Z</updated>
  <author>
    <name>Daniel Janus</name>
    <uri>http://danieljanus.pl</uri>
    <email>dj@danieljanus.pl</email>
  </author>
  <entry>
    <id>tag:blog.danieljanus.pl,2025-02-21:post:double-double-toil-and-trouble</id>
    <title>Double, double toil and trouble or, Corner-Cases of Comparing Clojure Numbers</title>
    <link href="http://blog.danieljanus.pl/double-double-toil-and-trouble/"/>
    <updated>2025-02-21T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;&lt;a href="https://www.destroyallsoftware.com/talks/wat"&gt;Let’s talk about&lt;/a&gt; Clojure.&lt;/p&gt;&lt;p&gt;In Clojure, comparing two numbers can throw an exception.&lt;/p&gt;&lt;img src="/img/blog/wat-shark.jpg" alt="Wat"&gt;
&lt;p&gt;Check this out:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-number"&gt;1/4&lt;/span&gt; &lt;span class="hljs-number"&gt;0.5M&lt;/span&gt;)
&lt;span class="hljs-comment"&gt;;=&amp;gt; true        ; as expected&lt;/span&gt;

(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-number"&gt;1/3&lt;/span&gt; &lt;span class="hljs-number"&gt;0.5M&lt;/span&gt;)
&lt;span class="hljs-comment"&gt;; Execution error (ArithmeticException) at java.math.BigDecimal/divide (BigDecimal.java:1783).&lt;/span&gt;
&lt;span class="hljs-comment"&gt;; Non-terminating decimal expansion; no exact representable decimal result.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But why? Why would comparing two perfectly cromulent numbers throw an &lt;code&gt;ArithmeticException&lt;/code&gt;?! Everybody knows that ⅓ &lt; 0.5 – we aren’t dividing by zero or anything like that, are we?&lt;/p&gt;&lt;p&gt;Well, the problem is that we’re comparing a ratio to a &lt;code&gt;BigDecimal&lt;/code&gt; (a decimal number of arbitrary precision). Java doesn’t offer a built-in way of comparing these (Clojure’s ratios aren’t part of the Java standard library), so it has to coerce one into the other. It chooses to coerce the ratio into a BigDecimal, so divides &lt;code&gt;(bigdec 1)&lt;/code&gt; by &lt;code&gt;(bigdec 3)&lt;/code&gt;…&lt;/p&gt;&lt;p&gt;…and that &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html#divide-java.math.BigDecimal-"&gt;throws!&lt;/a&gt; The decimal representation of ⅓ is infinite, so you can’t keep all the digits in finite memory.&lt;/p&gt;&lt;p&gt;You may ask: how exactly does Clojure know what coercions to apply and how to produce the result? Let’s look at the code.&lt;/p&gt;&lt;p&gt;The implementation of &lt;code&gt;clojure.core/&lt;&lt;/code&gt; calls the Java method &lt;code&gt;clojure.lang.Numbers.lt&lt;/code&gt;, which is implemented &lt;a href="https://github.com/clojure/clojure/blob/clojure-1.12.0/src/jvm/clojure/lang/Numbers.java#L252-L254"&gt;like this&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs java"&gt;&lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-type"&gt;boolean&lt;/span&gt; &lt;span class="hljs-title function_"&gt;lt&lt;/span&gt;&lt;span class="hljs-params"&gt;(Object x, Object y)&lt;/span&gt;{
	&lt;span class="hljs-keyword"&gt;return&lt;/span&gt; ops(x).combine(ops(y)).lt((Number)x, (Number)y);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What’s &lt;code&gt;ops&lt;/code&gt;? It’s an implementation of the &lt;code&gt;Ops&lt;/code&gt; interface, which has methods for addition, subtraction, etc.; each number class has its own implementation: there is a &lt;code&gt;LongOps&lt;/code&gt;, &lt;code&gt;RatioOps&lt;/code&gt;, &lt;code&gt;BigDecimalOps&lt;/code&gt; etc.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;combine&lt;/code&gt; method can alter the behaviour of an &lt;code&gt;Ops&lt;/code&gt; depending on the type of the other argument – for example, &lt;code&gt;RatioOps&lt;/code&gt; switches to &lt;code&gt;BigDecimalOps&lt;/code&gt; if the other argument is a &lt;code&gt;BigDecimal&lt;/code&gt;. It’s like a poor man’s implementation of multiple dispatch, which Java doesn’t have.&lt;/p&gt;&lt;p&gt;&lt;code&gt;BigDecimalOps.lt&lt;/code&gt; calls &lt;code&gt;toBigDecimal&lt;/code&gt; on both arguments, and it’s &lt;a href="https://github.com/clojure/clojure/blob/clojure-1.12.0/src/jvm/clojure/lang/Numbers.java#L297-L322"&gt;that method&lt;/a&gt; that performs the failing division:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs java"&gt;&lt;span class="hljs-keyword"&gt;static&lt;/span&gt; BigDecimal &lt;span class="hljs-title function_"&gt;toBigDecimal&lt;/span&gt;&lt;span class="hljs-params"&gt;(Object x)&lt;/span&gt; {
    &lt;span class="hljs-comment"&gt;// ... other cases ...&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (x &lt;span class="hljs-keyword"&gt;instanceof&lt;/span&gt; Ratio) {
        &lt;span class="hljs-type"&gt;Ratio&lt;/span&gt; &lt;span class="hljs-variable"&gt;r&lt;/span&gt; &lt;span class="hljs-operator"&gt;=&lt;/span&gt; (Ratio)x;
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; (BigDecimal)divide(&lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-title class_"&gt;BigDecimal&lt;/span&gt;(r.numerator), r.denominator);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Incidentally, this used to produce the expected result in Clojure up to 1.2.1. At that version, Clojure already used the &lt;code&gt;Ops&lt;/code&gt;-based multiple dispatch, but combining &lt;code&gt;RatioOps&lt;/code&gt; with &lt;code&gt;BigDecimalOps&lt;/code&gt; would yield the former, not the latter.&lt;/p&gt;&lt;p&gt;Is the current behaviour a bug? I’m not sure. It seems so, but maybe 1.3.0’s optimizations warrant this behaviour in the admitedly rare case. There’s an &lt;a href="https://ask.clojure.org/index.php/14411/comparing-ratios-with-bigdecimals-can-throw"&gt;ongoing discussion&lt;/a&gt; on the Ask Clojure Q&amp;A.&lt;/p&gt;&lt;p&gt;So, in current Clojure, how do you compare ratios to bigdecs? Simple, you think: just coerce the bigdec to a double!&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-number"&gt;1/3&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;double&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-number"&gt;0.5M&lt;/span&gt;))
&lt;span class="hljs-comment"&gt;;=&amp;gt; true&lt;/span&gt;

(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-number"&gt;2/3&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;double&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-number"&gt;0.5M&lt;/span&gt;))
&lt;span class="hljs-comment"&gt;;=&amp;gt; true&lt;/span&gt;

(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;=&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-number"&gt;1/2&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;double&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-number"&gt;0.5M&lt;/span&gt;))
&lt;span class="hljs-comment"&gt;;=&amp;gt; false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wait, WHAT?&lt;/p&gt;&lt;img src="/img/blog/wat-cat.jpg" alt="Wat"&gt;
&lt;p&gt;Yep. Comparing ratios to doubles for &lt;em&gt;inequality&lt;/em&gt; works fine, but a ratio is never &lt;em&gt;equal&lt;/em&gt; to a double (nor a bigdec), even if said double is an exact representation of the ratio.&lt;/p&gt;&lt;p&gt;This one is documented, but often forgotten about (and not hinted at by the docstring). From Clojure’s &lt;a href="https://clojure.org/guides/equality"&gt;equality guide&lt;/a&gt;:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Clojure’s &lt;code&gt;=&lt;/code&gt; is true when called with two immutable scalar values, if:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;Both arguments are nil, true, false, the same character, or the same string (i.e. the same sequence of characters).&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;Both arguments are symbols, or both keywords, with equal namespaces and names.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;Both arguments are numbers in the same 'category', and numerically the same, where category is one of:&lt;/span&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;integer or ratio&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;floating point (float or double)&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;BigDecimal.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt;&lt;p&gt;And indeed, the code for &lt;code&gt;Numbers.equal&lt;/code&gt; has &lt;a href="https://github.com/clojure/clojure/blob/clojure-1.12.0/src/jvm/clojure/lang/Numbers.java#L247-L250"&gt;a check for both operands’ categories&lt;/a&gt; before it delves to the &lt;code&gt;Ops&lt;/code&gt; business that we’ve seen. Remember also that Clojure has a numbers-only &lt;code&gt;==&lt;/code&gt; which doesn’t trigger that category check:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;==&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-number"&gt;1/2&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;double&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-number"&gt;0.5M&lt;/span&gt;))
&lt;span class="hljs-comment"&gt;;=&amp;gt; true ; yay&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Corollary: if you want to compare a ratio to a &lt;code&gt;BigDecimal&lt;/code&gt;, you &lt;em&gt;could&lt;/em&gt; coerce the bigdec to a double. That can return an incorrect result only in a very narrow range of cases: when the BigDecimal’s value is close enough to the ratio that it would be lost in the double conversion.&lt;/p&gt;&lt;p&gt;For 100% certainty, the only way I’m aware of is to remember to always use &lt;code&gt;==&lt;/code&gt; when comparing for equality, and explicitly coerce the bigdec to ratio:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;exactly-equals?&lt;/span&gt; [ratio bigdec]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;==&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;*&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt; (&lt;span class="hljs-name"&gt;clojure.lang.Numbers/toRatio&lt;/span&gt; bigdec)) ratio))

(&lt;span class="hljs-name"&gt;exactly-equals?&lt;/span&gt; &lt;span class="hljs-number"&gt;1/18446744073709551616&lt;/span&gt; &lt;span class="hljs-number"&gt;5.42101086242752217003726400434970855712890625E-20M&lt;/span&gt;)
&lt;span class="hljs-comment"&gt;;=&amp;gt; true ; correct even in this pathological case!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(Multiplying by 1 forces Clojure to normalize the ratio. Otherwise, converting &lt;code&gt;0.5M&lt;/code&gt; would have yielded &lt;code&gt;5/10&lt;/code&gt; which doesn’t test &lt;code&gt;==&lt;/code&gt; to &lt;code&gt;1/2&lt;/code&gt;. Go figure.)&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
</feed>
