BigDecimal in JavaScript
There are several implementations of BigDecimal in js:
- js-big-decimal
- big.js
- bignumber.js
- decimal.js
The last 3 come from the same author: see the differences.
Since we have native support for BigInt
, it doesn't require much code any more to implement BigDecimal
.
Here is a BigDecimal
class based on BigInt
with the following characteristics:
- The number of decimals is configured as a constant, applicable to all instances.
- Whether excessive digits are truncated or rounded is configured as a boolean constant.
- An instance stores the decimal number as a
BigInt
, multiplied by a power of 10 so to include the decimals. - All calculations happen with those
BigInt
values. - The arguments passed to
add
,subtract
,multiply
anddivide
can be numeric, string, or instances ofBigDecimal
- These methods return new instances, so a
BigDecimal
is treated as immutable. - The
toString
method reintroduces the decimal point. - A
BigDecimal
can coerce to a number (via implicit call totoString
), but that will obviously lead to loss of precision.
class BigDecimal {
// Configuration: constants
static DECIMALS = 18; // number of decimals on all instances
static ROUNDED = true; // numbers are truncated (false) or rounded (true)
static SHIFT = BigInt("1" + "0".repeat(BigDecimal.DECIMALS)); // derived constant
constructor(value) {
if (value instanceof BigDecimal) return value;
let [ints, decis] = String(value).split(".").concat("");
this._n = BigInt(ints + decis.padEnd(BigDecimal.DECIMALS, "0")
.slice(0, BigDecimal.DECIMALS))
+ BigInt(BigDecimal.ROUNDED && decis[BigDecimal.DECIMALS] >= "5");
}
static fromBigInt(bigint) {
return Object.assign(Object.create(BigDecimal.prototype), { _n: bigint });
}
add(num) {
return BigDecimal.fromBigInt(this._n + new BigDecimal(num)._n);
}
subtract(num) {
return BigDecimal.fromBigInt(this._n - new BigDecimal(num)._n);
}
static _divRound(dividend, divisor) {
return BigDecimal.fromBigInt(dividend / divisor
+ (BigDecimal.ROUNDED ? dividend * 2n / divisor % 2n : 0n));
}
multiply(num) {
return BigDecimal._divRound(this._n * new BigDecimal(num)._n, BigDecimal.SHIFT);
}
divide(num) {
return BigDecimal._divRound(this._n * BigDecimal.SHIFT, new BigDecimal(num)._n);
}
toString() {
const s = this._n.toString().padStart(BigDecimal.DECIMALS+1, "0");
return s.slice(0, -BigDecimal.DECIMALS) + "." + s.slice(-BigDecimal.DECIMALS)
.replace(/\.?0+$/, "");
}
}
// Demo
var a = new BigDecimal("123456789123456789876");
var b = a.divide("10000000000000000000");
var c = b.add("9.000000000000000004");
console.log(b.toString());
console.log(c.toString());
console.log(+c); // loss of precision when converting to number
I like using accounting.js
for number, money and currency formatting.
Homepage - https://openexchangerates.github.io/accounting.js/
Github - https://github.com/openexchangerates/accounting.js