Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BigDecimal in JavaScript

I'm very new to JavaScript (I come from a Java background) and I am trying to do some financial calculations with small amounts of money.

My original go at this was:

<script type="text/javascript">     var normBase = ("[price]").replace("$", "");     var salesBase = ("[saleprice]").replace("$", "");     var base;     if (salesBase != 0) {         base = salesBase;     } else {         base = normBase;     }     var per5  = (base - (base * 0.05));     var per7  = (base - (base * 0.07));     var per10 = (base - (base * 0.10));     var per15 = (base - (base * 0.15));     document.write         (         '5% Off: $'  + (Math.ceil(per5  * 100) / 100).toFixed(2) + '<br/>' +         '7% Off: $'  + (Math.ceil(per7  * 100) / 100).toFixed(2) + '<br/>' +         '10% Off: $' + (Math.ceil(per10 * 100) / 100).toFixed(2) + '<br/>' +         '15% Off: $' + (Math.ceil(per15 * 100) / 100).toFixed(2) + '<br/>'     ); </script> 

This worked well except it always rounded up (Math.ceil). Math.floor has the same issue, and Math.round is also no good for floats.

In Java, I would have avoided the use of floats completely from the get-go, however in JavaScript there does not seem to be a default inclusion of something comparable.

The problem is, all the libraries mentioned are either broken or for a different purpose. The jsfromhell.com/classes/bignumber library is very close to what I need, however I'm having bizarre issues with its rounding and precision... No matter what I set the Round Type to, it seems to decide on its own. So for example, 3.7107 with precision of 2 and round type of ROUND_HALF_UP somehow winds up as 3.72 when it should be 3.71.

I also tried @JasonSmith BigDecimal library (a machined port from Java's BigDecimal), but it seems to be for node.js which I don't have the option of running.

How can I accomplish this using vanilla JavaScript (and be reliable) or is there a modern (ones mentioned above are all years old now) library that I can use that is maintained and is not broken?

like image 355
SnakeDoc Avatar asked May 24 '13 19:05

SnakeDoc


People also ask

Is there BigDecimal in Javascript?

BigDecimal for Javascript is a pure-Javascript implementation of immutable, arbitrary-precision, signed decimal numbers. BigDecimal supports decimal math with arbitrary precision. For a limited time, we will throw in BigInteger support at no extra charge!

What is BigDecimal used for?

The BigDecimal class provides operations on double numbers for arithmetic, scale handling, rounding, comparison, format conversion and hashing. It can handle very large and very small floating point numbers with great precision but compensating with the time complexity a bit.

What is a BigDecimal?

A BigDecimal consists of an arbitrary precision integer unscaled value and a 32-bit integer scale. If zero or positive, the scale is the number of digits to the right of the decimal point. If negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale.


1 Answers

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 and divide can be numeric, string, or instances of BigDecimal
  • 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 to toString), 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
like image 56
trincot Avatar answered Oct 18 '22 01:10

trincot