Hacked together an implementation of big decimals using `BigInt`. Doesn't do division very well. Lots of issues.
function getDecimalSeparator(locale) { | |
const numberWithDecimalSeparator = 1.1; | |
return Intl.NumberFormat(locale) | |
.formatToParts(numberWithDecimalSeparator) | |
.find(part => part.type === 'decimal') | |
.value; | |
} | |
getDecimalSeparator(); | |
const RADIX_PREFIX = { | |
"2": "0b", | |
"8": "0o", | |
"16": "0x" | |
}; | |
function BigDecimal(value, exponent = BigInt(1)) { | |
if (!(this instanceof BigDecimal)){ | |
return new BigDecimal(value, exponent); | |
} | |
if (exponent < BigInt(0)) { | |
throw new Error("exponent must be a positive bigint or number"); | |
} | |
this.value = BigInt(value); | |
this.exponent = BigInt(exponent); | |
} | |
BigDecimal.prototype = { | |
toString: function(radix = 10) { | |
if (radix < 2 || radix > 36) { | |
throw new RangeError("radix argument must be between 2 and 36"); | |
} | |
if (radix != 10) { | |
const prefix = RADIX_PREFIX[radix] || ""; | |
return prefix + this.value.toString(radix); | |
} | |
const magnitude = this._magnitude(radix); | |
const significand = this._significand(magnitude); | |
const fractional = this._fractional(magnitude); | |
if (fractional !== BigInt(0)) { | |
return significand + "." + fractional.toString().padStart(Number(this.exponent), "0").replace(/0+$/, ""); | |
} | |
return significand.toString(); | |
}, | |
_magnitude: function(radix = 10) { | |
return (BigInt(radix) ** this.exponent); | |
}, | |
_significand: function(magnitude = BigInt(1)){ | |
return this.value / magnitude; | |
}, | |
_fractional: function(magnitude = BigInt(1)){ | |
return this.value % magnitude; | |
}, | |
valueOf: function(){ | |
return this.value; | |
}, | |
toNumber: function () { | |
return parseFloat(this.toString(10), 10); | |
}, | |
} | |
BigDecimal.add = function(a, b) { | |
let aV = a.value; | |
let bV = b.value; | |
const xDiff = a.exponent - b.exponent; | |
let bestEx; | |
if (xDiff === BigInt(0)) { | |
// do the math | |
bestEx = a.exponent; | |
} else if(xDiff < BigInt(0)) { | |
// b has more decimal places than a | |
aV *= BigInt(10)**-xDiff; | |
bestEx = b.exponent; | |
} else { | |
bV *= BigInt(10)**xDiff; | |
bestEx = a.exponent; | |
} | |
return BigDecimal(aV + bV, bestEx); | |
} | |
BigDecimal.subtract = function(a, b) { | |
let aV = a.value; | |
let bV = b.value; | |
const xDiff = a.exponent - b.exponent; | |
let bestEx; | |
if (xDiff === BigInt(0)) { | |
// do the math | |
bestEx = a.exponent; | |
} else if(xDiff < BigInt(0)) { | |
// b has more decimal places than a | |
aV *= BigInt(10)**-xDiff; | |
bestEx = b.exponent; | |
} else { | |
bV *= BigInt(10)**xDiff; | |
bestEx = a.exponent; | |
} | |
return BigDecimal(aV - bV, bestEx); | |
} | |
/** | |
* TODO: this isn't decimal division. I just hacked together long division with | |
* max precision. Probably need to store the remainer and denominator, | |
* collapsing them as needed (toString() and other operations) | |
*/ | |
BigDecimal.divide = function(a, b) { | |
let aV = a.value; | |
const bV = b.value; | |
const aExp = a.exponent; | |
const bExp = b.exponent; | |
const maxPrecision = BigInt(48); | |
let additionalDecimals = BigInt(0); | |
let remainder = aV % bV; | |
// long division on the remainder to get the number of additional decimals we | |
// need. this is terrible. really really terrible. just look away. | |
while (additionalDecimals < maxPrecision) { | |
remainder *= BigInt(10); | |
additionalDecimals++; | |
if (remainder > bV) { | |
remainder = remainder % bV; | |
} else if (remainder === BigInt(0)) { | |
break; | |
} | |
} | |
aV *= BigInt(10)**additionalDecimals; | |
const result = aV / bV; | |
return BigDecimal(result, additionalDecimals + aExp - bExp); | |
} | |
BigDecimal.multiply = function(a, b) { | |
let aV = a.value; | |
let bV = b.value; | |
const xDiff = a.exponent - b.exponent; | |
let bestEx = a.exponent * b.exponent; | |
if (xDiff < BigInt(0)) { | |
// b has more decimal places than a | |
aV *= BigInt(10)**-xDiff; | |
} else if (xDiff > BigInt(0)) { | |
bV *= BigInt(10)**xDiff; | |
} | |
return BigDecimal(aV * bV, bestEx); | |
} | |
const a = BigDecimal(11111, 7); | |
const b = BigDecimal(555, 9); | |
const result = BigDecimal.divide(a, b); | |
console.log(result.toString()); | |
console.log(.0011111 / .000000555, "<-- expected"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment