Thoughts on Clojurescript and BigDecimal
So you&aposre 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&aposre 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&aposs 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&apost supported because Clojurescript doesn&apost have support for BigInteger
. They&aposre separate types, so why would that matter?
They&aposre one in the same
Unfortunately, Clojure&aposs 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 mathGoing 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 BigIntDigging deeper we can see a lot of thought has gone into the BigInteger
implementation. Java&aposs BigInteger
implementation makes use of some very fast algorithms like Karatsuba Algorithm and Toom-Cook Multiplication depending on the arithmetic operation. I&aposm 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&aposs 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&aposs 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 replIf 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 replUpdate: 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&apost 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&aposre 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&aposs 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&aposve seen in the wild was using the Google&aposs closure compiler format
("%2f") function to truncate the floating point values when sent to the view. Not exactly precise, but, if you don&apost 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.