| // MIT License |
| |
| // Copyright (c) 2020 U-Zyn Chua <[email protected]> |
| |
| // Permission is hereby granted, free of charge, to any person obtaining a copy |
| // of this software and associated documentation files (the "Software"), to deal |
| // in the Software without restriction, including without limitation the rights |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| // copies of the Software, and to permit persons to whom the Software is |
| // furnished to do so, subject to the following conditions: |
| |
| // The above copyright notice and this permission notice shall be included in all |
| // copies or substantial portions of the Software. |
| |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| // SOFTWARE. |
| |
| globalThis.BigDenary = (function () { |
| 'use strict'; |
| |
| function bigIntAbs(n) { |
| if (n >= 0n) { |
| return n; |
| } |
| return n * -1n; |
| } |
| function getDecimals(n) { |
| if (isNaN(n)) { |
| throw new Error("InvalidNumber"); |
| } |
| const [preDec, postDec] = _splitString(n.toString(), "."); |
| return postDec.length; |
| } |
| function extractExp(n) { |
| const [mul, expStr] = _splitString(n, "e"); |
| if (expStr === "") { |
| return [n, 0]; |
| } |
| const exp = parseInt(expStr, 10); |
| if (isNaN(exp)) { |
| throw new Error("InvalidNumber"); |
| } |
| return [mul, exp]; |
| } |
| function countTrailingZeros(n, upTo) { |
| if (n === 0n) { |
| return 0; |
| } |
| let count = 0; |
| let c = n < 0 ? n * -1n : n; |
| while (c % 10n === 0n && count < upTo) { |
| count += 1; |
| c = c / 10n; |
| } |
| return count; |
| } |
| function _splitString(input, char) { |
| const pos = input.indexOf(char); |
| if (pos === -1) { |
| return [input, ""]; |
| } |
| const after = input.substr(pos + 1); |
| if (after.indexOf(char) !== -1) { |
| throw new Error("InvalidNumber"); // Multiple occurences |
| } |
| return [input.substr(0, pos), after]; |
| } |
| |
| const BDCompare = { |
| Greater: 1, |
| Less: -1, |
| Equal: 0, |
| }; |
| |
| class BigDenary { |
| constructor(n) { |
| if (n instanceof BigDenary) { |
| this.base = n.base; |
| this._decimals = n.decimals; |
| } |
| else if (typeof n === "number") { |
| this._decimals = getDecimals(n); |
| this.base = BigInt(n * Math.pow(10, this._decimals)); |
| } |
| else if (typeof n === "string") { |
| const [mul, exp] = extractExp(n); |
| const mulDec = getDecimals(mul); |
| if (exp > mulDec) { |
| this.base = BigInt(mul.replace(".", "")) * |
| BigInt(Math.pow(10, (exp - mulDec))); |
| this._decimals = 0; |
| } |
| else { |
| this.base = BigInt(mul.replace(".", "")); |
| this._decimals = mulDec - exp; |
| } |
| } |
| else if (typeof n === "bigint") { |
| this.base = n * this.decimalMultiplier; |
| this._decimals = 0; |
| } |
| else if (typeof n === "object" && n !== null) { |
| if (n.decimals < 0) { |
| throw new Error("InvalidBigDenaryRaw"); |
| } |
| this.base = n.base; |
| this._decimals = n.decimals; |
| } |
| else { |
| throw new Error("UnsupportedInput"); |
| } |
| this.trimTrailingZeros(); |
| } |
| toString() { |
| if (this.base === 0n) { |
| return "0"; |
| } |
| const negative = (this.base < 0); |
| let base = this.base; |
| if (negative) { |
| base = base * -1n; |
| } |
| const baseStr = base.toString(); |
| const position = baseStr.length - this._decimals; |
| let pre; |
| let post; |
| if (position < 0) { |
| pre = ""; |
| post = `${_strOfZeros(position * -1)}${baseStr}`; |
| } |
| else { |
| pre = baseStr.substr(0, position); |
| post = baseStr.substr(position); |
| } |
| let result; |
| if (pre.length === 0) { |
| result = `0.${post}`; |
| } |
| else if (post.length === 0) { |
| result = `${pre}`; |
| } |
| else { |
| result = `${pre}.${post}`; |
| } |
| if (negative) { |
| return `-${result}`; |
| } |
| return result; |
| } |
| valueOf() { |
| return Number.parseFloat(this.toString()); |
| } |
| toFixed(digits) { |
| if (!digits) { |
| return this.toString(); |
| } |
| const temp = new BigDenary(this); |
| temp.scaleDecimalsTo(digits); |
| return temp.toString(); |
| } |
| get decimals() { |
| return this._decimals; |
| } |
| /** |
| * Alters the decimal places, actual underlying value does not change |
| */ |
| scaleDecimalsTo(_decimals) { |
| if (_decimals > this._decimals) { |
| this.base = this.base * |
| BigDenary.getDecimalMultiplier(_decimals - this._decimals); |
| } |
| else if (_decimals < this._decimals) { |
| const adjust = this._decimals - _decimals; |
| const multiplier = BigDenary.getDecimalMultiplier(adjust); |
| const remainder = this.base % multiplier; |
| this.base = this.base / multiplier; |
| if (bigIntAbs(remainder * 2n) >= multiplier) { |
| if (this.base >= 0) { |
| this.base += 1n; |
| } |
| else { |
| this.base -= 1n; |
| } |
| } |
| } |
| this._decimals = _decimals; |
| } |
| get decimalMultiplier() { |
| return BigDenary.getDecimalMultiplier(this._decimals); |
| } |
| static getDecimalMultiplier(decimals) { |
| return 10n ** BigInt(decimals); |
| } |
| trimTrailingZeros() { |
| const trailingZerosCount = countTrailingZeros(this.base, this.decimals); |
| if (trailingZerosCount > 0) { |
| this.scaleDecimalsTo(this.decimals - trailingZerosCount); |
| } |
| } |
| /** |
| * Operations |
| */ |
| plus(operand) { |
| const curr = new BigDenary(this); |
| const oper = new BigDenary(operand); |
| const targetDecs = Math.max(curr.decimals, oper.decimals); |
| curr.scaleDecimalsTo(targetDecs); |
| oper.scaleDecimalsTo(targetDecs); |
| return new BigDenary({ |
| base: curr.base + oper.base, |
| decimals: targetDecs, |
| }); |
| } |
| minus(operand) { |
| return this.plus((new BigDenary(operand)).negated()); |
| } |
| multipliedBy(operand) { |
| const curr = new BigDenary(this); |
| const oper = new BigDenary(operand); |
| const targetDecs = curr.decimals + oper.decimals; |
| return new BigDenary({ |
| base: curr.base * oper.base, |
| decimals: targetDecs, |
| }); |
| } |
| dividedBy(operand) { |
| const MIN_DIVIDE_DECIMALS = 20; |
| const curr = new BigDenary(this); |
| const oper = new BigDenary(operand); |
| const targetDecs = Math.max(curr.decimals * 2, oper.decimals * 2, MIN_DIVIDE_DECIMALS); |
| curr.scaleDecimalsTo(targetDecs); |
| return new BigDenary({ |
| base: curr.base / oper.base, |
| decimals: curr.decimals - oper.decimals, |
| }); |
| } |
| negated() { |
| return new BigDenary({ |
| base: this.base * -1n, |
| decimals: this.decimals, |
| }); |
| } |
| absoluteValue() { |
| if (this.base >= 0n) { |
| return this; |
| } |
| return this.negated(); |
| } |
| /** |
| * Comparisons |
| */ |
| comparedTo(comparator) { |
| const curr = new BigDenary(this); |
| const comp = new BigDenary(comparator); |
| const targetDecs = Math.max(curr.decimals, comp.decimals); |
| curr.scaleDecimalsTo(targetDecs); |
| comp.scaleDecimalsTo(targetDecs); |
| if (curr.base > comp.base) { |
| return BDCompare.Greater; |
| } |
| else if (curr.base < comp.base) { |
| return BDCompare.Less; |
| } |
| return BDCompare.Equal; |
| } |
| equals(comparator) { |
| return (this.comparedTo(comparator) === BDCompare.Equal); |
| } |
| greaterThan(comparator) { |
| return (this.comparedTo(comparator) === BDCompare.Greater); |
| } |
| greaterThanOrEqualTo(comparator) { |
| return ((this.comparedTo(comparator) === BDCompare.Greater) || |
| (this.comparedTo(comparator) === BDCompare.Equal)); |
| } |
| lessThan(comparator) { |
| return (this.comparedTo(comparator) === BDCompare.Less); |
| } |
| lessThanOrEqualTo(comparator) { |
| return ((this.comparedTo(comparator) === BDCompare.Less) || |
| (this.comparedTo(comparator) === BDCompare.Equal)); |
| } |
| /** |
| * Shortforms |
| */ |
| add(operand) { |
| return this.plus(operand); |
| } |
| sub(operand) { |
| return this.minus(operand); |
| } |
| mul(operand) { |
| return this.multipliedBy(operand); |
| } |
| div(operand) { |
| return this.dividedBy(operand); |
| } |
| neg() { |
| return this.negated(); |
| } |
| abs() { |
| return this.absoluteValue(); |
| } |
| cmp(comparator) { |
| return this.comparedTo(comparator); |
| } |
| eq(comparator) { |
| return this.equals(comparator); |
| } |
| gt(comparator) { |
| return this.greaterThan(comparator); |
| } |
| gte(comparator) { |
| return this.greaterThanOrEqualTo(comparator); |
| } |
| lt(comparator) { |
| return this.lessThan(comparator); |
| } |
| lte(comparator) { |
| return this.lessThanOrEqualTo(comparator); |
| } |
| } |
| function _strOfZeros(count) { |
| return "0".repeat(count); |
| } |
| |
| BigDenary.BDCompare = BDCompare; |
| |
| return BigDenary; |
| |
| })(); |