Welcome! Please see the About page for a little more info on how this works.

0 votes
ago in Compiler by

Observe:

(< 1/4 0.5M)
;=> true        ; as expected

(< 1/3 0.5M)
; Execution error (ArithmeticException) at java.math.BigDecimal/divide (BigDecimal.java:1783).
; Non-terminating decimal expansion; no exact representable decimal result.

This is because Clojure tries to coerce the ratio to a BigDecimal, which can fail.

This worked (and produced expected results) in Clojure up to 1.2.1; the current behaviour is from 1.3.0 onwards. Not sure whether this is a bug or expected behaviour; in either case, I haven’t seen it documented.

>, <=, >= all exhibit the same behaviour. = works.

I’ve also noticed that = can’t determine whether a ratio is equal to a bigdec, although < and > do sometimes produce a good answer:

((juxt < = >) 1/2 0.5M)
;=> [false false false]

((juxt < = >) 1/4 0.5M)
;=> [true false false]

The same is true of ratios and doubles:

((juxt < = >) 1/2 0.5M)
;=> [false false false]

((juxt < = >) 1/4 0.5M)
;=> [true false false]

This seems contrary to the docstring of =, which states that „[it] compares numbers and collections in a type-independent manner”.

1 Answer

0 votes
ago by

Separating these a little bit:

= will return false if two numbers are compared across different numeric partitions (int+ratio+bigint, float, bigdec). This one is kind of special. You say "= works" above but I don't think it works presumably meaning it does not throw, but it will always return false for cross-partition, e.g. (= 1/2 0.5M).

Other numeric comparison operators coerce args to matching type, then compare. This includes == which thus does = across partitions. There are some cases where the coercion cannot occur - I think maybe irrational fraction to bigdecimal is the obvious (only?) case and in that case there is available coercion.

I consider this expected behavior, not sure where we could document it but open to ideas. Maybe bigdec docstring, or at https://clojure.org/reference/data_structures#Numbers ? Where did you look first?

ago by
As I say, I looked at a docstring of `=` and was confused by the wording I cited. Maybe that docstring could be a little more specific wrt numbers? (The [equality guide](https://clojure.org/guides/equality) explains it thoroughly – could be linked from the docstring.)

As to the coercion throwing, I think this is ultimately caused by `clojure.lang.Numbers$RatioOps.combine(BigDecimalOps)` returning a BigDecimalOps. In Clojure before 1.3, this used to return RatioOps which returns the correct result for comparisons.
ago by
I’ve expanded on this in a blog post – hopefully it doesn’t come across as smartass!

https://blog.danieljanus.pl/2025/02/21/double-double-toil-and-trouble/
...