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?
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!
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.
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.
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:
BigInt
, multiplied by a power of 10 so to include the decimals.BigInt
values.add
, subtract
, multiply
and divide
can be numeric, string, or instances of BigDecimal
BigDecimal
is treated as immutable.toString
method reintroduces the decimal point.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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With