Skip to content

Instantly share code, notes, and snippets.

@davidmurdoch
Created May 3, 2019 17:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidmurdoch/15d99ccbc03b8ba48e3e6cbb4b1bfa06 to your computer and use it in GitHub Desktop.
Save davidmurdoch/15d99ccbc03b8ba48e3e6cbb4b1bfa06 to your computer and use it in GitHub Desktop.
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