Skip to content

Instantly share code, notes, and snippets.

@Ariex
Last active May 2, 2018 00:49
Show Gist options
  • Save Ariex/ecf1fef24f0a473cf6ff8abb0feb0a76 to your computer and use it in GitHub Desktop.
Save Ariex/ecf1fef24f0a473cf6ff8abb0feb0a76 to your computer and use it in GitHub Desktop.
var BigNumber = (()=>{
// scale numbers represented in array format up/down by 10 to eliminate decimals
function alignArrayNumbers(arr1, decimalPos1, arr2, decimalPos2) {
var diff = Math.abs(decimalPos1 - decimalPos2);
var a1 = arr1.slice()
, a2 = arr2.slice();
if (decimalPos1 > decimalPos2) {
a2 = [...a2, ...Array(decimalPos1-decimalPos2).fill(0)];
} else if (decimalPos1 < decimalPos2) {
a1 = [...a1, ...Array(decimalPos2-decimalPos1).fill(0)];
}
return [a1, a2];
}
// remove 0 at left side of array
function trimLeadingZeros(arr) {
var a = arr.slice();
while (a[0] === 0 && a.length > 1) {
a.shift();
}
return a;
}
// assume arr1 and arr2 are all represent an unsigned number in array form
function arrayCompare(arr1, arr2) {
var [a1,a2] = [arr1.slice(), arr2.slice()];
while (a1[0] === 0) {
a1.splice(0, 1);
}
while (a2[0] === 0) {
a2.splice(0, 1);
}
if (a1.length > a2.length) {
return 1;
} else if (a1.length < a2.length) {
return -1;
}
if (a1.length === a2.length) {
for (var i = 0; i < a1.length; i++) {
if (a1[i] > a2[i]) {
return 1;
} else if (a1[i] < a2[i]) {
return -1;
}
}
}
return 0;
}
function arrayAdd(arr1, arr2) {
if (arr1.length > arr2.length) {
arr2 = [...Array(arr1.length-arr2.length).fill(0), ...arr2];
} else if (arr1.length < arr2.length) {
arr1 = [...Array(arr2.length-arr1.length).fill(0), ...arr1];
}
var res = Array(arr1.length + 1).fill(0);
for(var i = arr1.length-1;i>-1;i--){
var r = arr1[i] + arr2[i] + res[i+1];
if (r >= 10) {
r = r - 10;
res[i] += 1;
}
res[i+1] = r;
}
return res;
}
function arraySubtract(arr1, arr2) {
var res = Array(arr1.length).fill(0);
res.isNegative = false;
if (arrayCompare(arr1, arr2) < 0) {
[arr1,arr2] = [arr2, arr1];
res.isNegative = true;
}
if (arr1.length > arr2.length) {
arr2 = [...Array(arr1.length-arr2.length).fill(0), ...arr2];
} else if (arr1.length < arr2.length) {
arr1 = [...Array(arr2.length-arr1.length).fill(0), ...arr1];
}
for (var i = arr1.length - 1; i > -1; i--) {
var r = arr1[i] - arr2[i];
if (r < 0) {
r += 10;
arr1[i - 1] -= 1;
}
res[i] = r;
}
return res;
}
function arrayMultiply(arr1, arr2) {
var res = [];
for (var i = 0; i < arr2.length; i++) {
var n = arr2[arr2.length - i - 1];
var r = Array(arr1.length + 1).fill(0);
for (var j = arr1.length - 1; j > -1; j--) {
var v = n * arr1[j] + r[j];
if (v >= 10) {
var t = Math.floor(v / 10);
v = v - t * 10;
r[j] += t;// what if r[j] is 10 now?
}
r[j + 1] += v;
}
r = [...r, ...Array(i).fill(0)];
res.push(r);
}
return res.reduce((s,a)=>arrayAdd(s, a), [0]);
}
// perform divide on int only, no decimal supported
function arrayIntDivide(arr1, arr2) {
if (arr2.length === 1 && arr2[0] === 0) {
throw "divide by 0";
}
for (var i = 0; i < 10; i++) {
switch (arrayCompare(arr1, arrayMultiply(arr2, [i]))) {
case 0:
return i;
case -1:
return i - 1;
}
}
return 9;
}
// perform divide on any number, this will scale number represented in array in the same size to make them integer, then apply int divide, then restore the decimal
function arrayDivide(arr1, arr2, decimalPos=10) {
var res = [];
res.decimalPos = decimalPos;
var a1 = [...arr1, ...Array(decimalPos).fill(0)];
var startAt = arr2.length;
var divider = a1.slice(0, startAt);
while (startAt <= a1.length) {
var r = arrayIntDivide(divider, arr2);
var reminder = arraySubtract(divider, arrayMultiply(arr2, [r]));
divider = [...reminder, a1[startAt++]];
res.push(r);
}
return res;
}
// function outputNum(arr) {
// var res = arr.join("").replace(/^0*/g, "");
// if (res.length < 1) {
// res = "0";
// }
// if (typeof arr.decimalPos === "number") {
// if (res.length > arr.decimalPos) {
// res = res.slice(0, -arr.decimalPos) + "." + res.slice(-arr.decimalPos);
// } else {
// res = "0." + res.padStart(arr.decimalPos, "0");
// }
// }
// return (arr.isPositive === void (0) || arr.isPositive ? "" : "-") + res;
// }
// function test(num1, num2, opt) {
// var nums1 = num1.toString().split("").map(n=>parseInt(n));
// var nums2 = num2.toString().split("").map(n=>parseInt(n));
// var res = [];
// switch (opt) {
// case "+":
// res[0] = outputNum(arrayAdd(nums1, nums2));
// res[1] = parseFloat(num1) + parseFloat(num2);
// res[2] = res[0] === res[1].toString();
// break;
// case "-":
// res[0] = outputNum(arraySubtract(nums1, nums2));
// res[1] = parseFloat(num1) - parseFloat(num2);
// res[2] = res[0] === res[1].toString();
// break;
// case "*":
// res[0] = outputNum(arrayMultiply(nums1, nums2));
// res[1] = parseFloat(num1) * parseFloat(num2);
// res[2] = res[0] === res[1].toString();
// break;
// case "/":
// res[0] = outputNum(arrayDivide(nums1, nums2, 106));
// res[1] = (parseFloat(num1) / parseFloat(num2)).toFixed(17);
// res[2] = res[0] === res[1].toString();
// break;
// }
// return res;
// }
// console.group("Plus");
// console.log(test(0, 5432, "+"));
// console.log(test(0, 0, "+"));
// console.log(test(12345, 5432, "+"));
// console.log(test(12345678909, 5432, "+"));
// console.groupEnd("Plus");
// console.group("Minus");
// console.log(test(0, 5432, "-"));
// console.log(test(0, 0, "-"));
// console.log(test(12345, 5432, "-"));
// console.log(test(12345678909, 5432, "-"));
// console.log(test(5432, 12345678909, "-"));
// console.groupEnd("Minus");
// console.group("multiply");
// console.log(test(11, 12, "*"));
// console.log(test(0, 12, "*"));
// console.log(test(11, 0, "*"));
// console.log(test(113141, 12, "*"));
// console.log(test("11", "1312311231231232", "*"));
// console.groupEnd("multiply");
// console.group("divide");
// console.log(test(11, 12, "/"));
// console.log(test(0, 12, "/"));
// console.log(test(11, 3, "/"));
// console.log(test(113141, 12, "/"));
// console.log(test("1230", "321", "/"));
// console.log(test("11", "1312311231231232", "/"));
// console.groupEnd("divide");
return class BigNumber {
/**
* Creates an instance of BigNumber.
* @param {any} num a number represented as number or string or array or BigNumber
* @param {any} decimalPos if num is an array, this will indicate where is the decimal
* @param {any} isNegative if num is an array, this will indicate if it is a negative number
*/
constructor(num, decimalPos, isNegative) {
if (num instanceof BigNumber) {
this.decimalPos = num.decimalPos;
this.isNegative = num.isNegative;
this.num = trimLeadingZeros(num.num);
return;
}
if (Array.isArray(num)) {
this.num = trimLeadingZeros(num);
this.decimalPos = decimalPos || 0;
this.isNegative = isNegative === void (0) ? true : isNegative;
return;
}
var str = num.toString();
// assume there is no format issue
this.isNegative = str.slice(0, 1) === "-";
str = str.replace("-", "");
this.decimalPos = str.indexOf(".") > -1 ? str.length - str.indexOf(".") - 1 : 0;
this.num = str.replace(".", "").split("").map(s=>parseInt(s));
}
toString() {
// trim leading zeros
var res = this.num.join("").replace(/^0*/g, "");
// display 0 correctly
if (res.length < 1) {
return "0";
}
// display leading 0 for decimals less than 1
if (this.decimalPos >= res.length) {
res = "0" + res.padStart(this.decimalPos, "0");
}
var decimal = this.decimalPos < 1 ? "" : res.slice(-this.decimalPos);
res = `${this.isNegative ? "-" : ""}${res.slice(0, res.length - decimal.length)}${decimal.length > 0 ? "." : ""}${decimal}`;
if(res.indexOf(".") === -1){
return res;
}
// remove trailing zeros
res = res.replace(/0*$/g, "");
if(res.slice(-1)==="."){
res = res.slice(0, -1);
}
return res;
}
add(num) {
if (!(num instanceof BigNumber)) {
throw "Only support operations on BigNumber instance";
}
if (this.isNegative !== num.isNegative) {
// -3 + 2
if (this.isNegative) {
return num.minus(new BigNumber(this.num,this.decimalPos,false));
} else {
// 3 + -2
return this.minus(new BigNumber(num.num,num.decimalPos,false));
}
}
// 3 + 2 or -3 + -2 => -(3 + 2)
var isNegative = this.isNegative;
var [n1,n2] = alignArrayNumbers(this.num, this.decimalPos, num.num, num.decimalPos);
var res = arrayAdd(n1, n2);
return new BigNumber(res,Math.max(this.decimalPos, num.decimalPos),isNegative);
}
minus(num) {
if (!(num instanceof BigNumber)) {
throw "Only support operations on BigNumber instance";
}
if (this.isNegative !== num.isNegative) {
// -3 - 2 => - (3+2)
if (this.isNegative) {
var res = num.add(new BigNumber(this.num,this.decimalPos,false));
res.isNegative = true;
return res;
} else {
// 3 - -2 => 3+2
var res = this.add(new BigNumber(num.num,num.decimalPos,false));
return res;
}
}
// now we dealing with -3 - -2
if (this.isNegative) {
var n1 = new BigNumber(this);
n1.isNegative = false;
var n2 = new BigNumber(num);
n2.isNegative = false;
return n2.minus(n1);
}
// now we dealing with 3 - 2
var [n1,n2] = alignArrayNumbers(this.num, this.decimalPos, num.num, num.decimalPos);
var res = arraySubtract(n1, n2);
return new BigNumber(res,Math.max(this.decimalPos, num.decimalPos),res.isNegative);
}
multiply(num) {
if (!(num instanceof BigNumber)) {
throw "Only support operations on BigNumber instance";
}
var [n1,n2] = [this.num, num.num];
var res = trimLeadingZeros(arrayMultiply(n1, n2));
return new BigNumber(res,this.decimalPos + num.decimalPos,this.isNegative !== num.isNegative);
}
divide(num) {
if (!(num instanceof BigNumber)) {
throw "Only support operations on BigNumber instance";
}
var [n1,n2] = [this.num, num.num];
var res = arrayDivide(n1, n2, 10 - Math.abs(this.decimalPos - num.decimalPos));
return new BigNumber(trimLeadingZeros(res),Math.abs(this.decimalPos - num.decimalPos) + res.decimalPos,this.isNegative !== num.isNegative);
}
}
}
)();
console.group("add");
console.assert(new BigNumber(0.1).add(new BigNumber(0.2)).toString() === "0.3");
console.assert(new BigNumber(-0.1).add(new BigNumber(-0.2)).toString() === "-0.3");
console.assert(new BigNumber(0).add(new BigNumber(0.2)).toString() === "0.2");
console.assert(new BigNumber(0).add(new BigNumber(0)).toString() === "0");
console.assert(new BigNumber("1234567890987654321").add(new BigNumber("1234567890987654321")).toString() === "2469135781975308642");
console.assert(new BigNumber("-1234567890987654321").add(new BigNumber("1234567890987654321")).toString() === "0");
console.assert(new BigNumber("-1234567890987654321").add(new BigNumber("-1234567890987654321")).toString() === "-2469135781975308642");
console.assert(new BigNumber("9999").add(new BigNumber("1")).toString() === "10000");
console.groupEnd("add");
console.group("minus");
console.assert(new BigNumber(0.1).minus(new BigNumber(0.2)).toString() === "-0.1");
console.assert(new BigNumber(0).minus(new BigNumber(0.2)).toString() === "-0.2");
console.assert(new BigNumber(0).minus(new BigNumber(0)).toString() === "0");
console.assert(new BigNumber("1234567890987654321").minus(new BigNumber("1234567890987654321")).toString() === "0");
console.assert(new BigNumber("-1234567890987654321").minus(new BigNumber("1234567890987654321")).toString() === "-2469135781975308642");
console.assert(new BigNumber("-1234567890987654321").minus(new BigNumber("-1234567890987654321")).toString() === "0");
console.assert(new BigNumber("10000").minus(new BigNumber("1")).toString() === "9999");
console.groupEnd("minus");
console.group("multiply");
console.assert(new BigNumber(0.1).multiply(new BigNumber(0.2)).toString() === "0.02");
console.assert(new BigNumber(0).multiply(new BigNumber(0.2)).toString() === "0");
console.assert(new BigNumber(0).multiply(new BigNumber(0)).toString() === "0");
console.assert(new BigNumber("1234567890987654321").multiply(new BigNumber("1234567890987654321")).toString() === "1524157877457704723228166437789971041");
console.assert(new BigNumber("-1234567890987654321").multiply(new BigNumber("1234567890987654321")).toString() === "-1524157877457704723228166437789971041");
console.assert(new BigNumber("-1234567890987654321").multiply(new BigNumber("-1234567890987654321")).toString() === "1524157877457704723228166437789971041");
console.groupEnd("multiply");
console.group("divide");
console.assert(new BigNumber(0.1).divide(new BigNumber(0.2)).toString() === "0.5");
console.assert(new BigNumber(0).divide(new BigNumber(0.2)).toString() === "0");
try{
console.assert(new BigNumber(0).divide(new BigNumber(0)).toString() === "0");
}catch(e){
console.assert(e === "divide by 0");
}
console.assert(new BigNumber("1234567890987654321").divide(new BigNumber("1234567890987654321")).toString() === "1");
console.assert(new BigNumber("-1234567890987654321").divide(new BigNumber("1234567890987654321")).toString() === "-1");
console.assert(new BigNumber("-1234567890987654321").divide(new BigNumber("-1234567890987654321")).toString() === "1");
console.groupEnd("divide");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment