Thoughts on Clojurescript and BigDecimal

Turns out, it's a yak barbershop.

Thoughts on Clojurescript and BigDecimal
Photo by Mika Baumeister / Unsplash

So you're writing an awesome scientific or financial app in Clojurescript with a backend as a service, only to discover the numbers feel really strange, as if they're just so slight off, and they are off, just ever-so-slightly. As it turns out, Clojurescript does not support the Clojure core form or type bigdec. Meaning, your calculations are using the dreaded IEEE 754. In fact, any attempt to create a BigDecimal (or BigInteger) will result in a no-operation, returning the value provided.

Let's back up a bit here. BigDecimal is the arbitrary-precision best friend we all know and love for computing (or not, pick your poison), but BigDecimal really isn't supported because Clojurescript doesn't have support for BigInteger. They're separate types, so why would that matter?

They're one in the same

Unfortunately, Clojure's implementation of BigInt and BigDecimal just wrap the Java versions. If we look at the Java implementation of BigDecimal, we can see under the hood BigDecimal uses a BigInteger object for the value, and a couple ints for the scale and precision.

    BigDecimal(BigInteger intVal, long val, int scale, int prec) {
        this.scale = scale;
        this.precision = prec;
        this.intCompact = val;
        this.intVal = intVal;
    }
BigDecimal from java math

Going further, we can see BigInteger stores an array of big-endian 32-bit integers, plus a single integer for the sign of the BigInteger:


    final int signum;
    final int[] mag;
Simplified BigInt

Digging deeper we can see a lot of thought has gone into the BigInteger implementation. Java's BigInteger implementation makes use of some very fast algorithms like Karatsuba Algorithm and Toom-Cook Multiplication depending on the arithmetic operation. I'm speculating a bit here, but, for Clojurescript to support a bigint and bigdec form, these operations (or similar) would have to be re-written.

Why not use Javascript's BigInt?

The BigInt implementation is fairly new to the ECMAscript spec, and adding it to Clojurescript could break existing implementations of Clojurescript. On top of that the BigInt implementation uses Javascript's Number implementation under the hood, and just like Number, suffers from precision defects since it uses double-precision 64-bit binary format IEEE 754 and is therefore, not arbitrary.

cljs.user> (js/BigInt 321321321321321321321321321321321321321)
#object[BigInt 321321321321321333487759338329222414336]
cljs.user> (.toString (js/BigInt 321321321321321321321321321321321321321))
"321321321321321333487759338329222414336"
cljs.user>
Using BigInt in the Clojurescript repl

If we do the same thing in plain-old Clojure, we can see there is no loss in precision for our ridiculous integer.

user> (bigint 321321321321321321321321321321321321321)
321321321321321321321321321321321321321N
user> (.toString (bigint 321321321321321321321321321321321321321))
"321321321321321321321321321321321321321"
user> 
Using bigint in Clojure repl

Update: Apparently, as some have pointed out, my repl is converting the integer to a js/Number before the js/BigInt constructor. js/BigInt does work for arbitrary precision.

user> (js/BigInt "32132132132132132132132132132132132132132132132132132132132132321")
#object[BigInt 32132132132132132132132132132132132132132132132132132132132132321]
user> (.toString (js/BigInt "32132132132132132132132132132132132132132132132132132132132132321"))
"32132132132132132132132132132132132132132132132132132132132132321" 
The works(ish)

What you can do about it

Right now, options are limited. One can't really replicate the Java BigInteger implementation because even integers in Javascript are really just IEEE 754 floating point binary under the hood and Javascript , so building your own Clojurescript library for bigint form is quite the challenge, but might be doable so long you can dodge the accuracy problems of each integer in your array, or maybe you're wicked smart and can come up with a better implementation for BigInteger using typed arrays in Clojurescript while ditching older implementations.

Some people have taken to forking Clojurescript and added javascript's bigint as a Clojurescript literal. Of course, this suffers from the caveats explained above, but might be worth pursuing if you can deal with them.

Another option I've seen in the wild was using the Google's closure compiler format("%2f") function to truncate the floating point values when sent to the view. Not exactly precise, but, if you don't care about the odd 0.00000000000000004 showing up in your calculations, this could work for you, but it is not my favourite option.

The final and obvious solution would be to write a simple backend in Clojure. Certainly an underwhelming conclusion here, but for applications where arbitrary precision matters, the architecture of your application should take into account how to do these calculations even if means adding another component to that architecture.

Thanks for reading. If you found this useful, I'm greatful. If you think I'm wrong, or want to discuss, you can find me on twitter @janetacarr. 

Subscribe to Janet A. Carr

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe