UNPKG

@hastom/fixed-point

Version:

Light lib for fixed point math made around native bigint

331 lines (330 loc) 12.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FixedPoint = exports.Decimals = exports.Rounding = void 0; const math_1 = require("./math"); var Rounding; (function (Rounding) { Rounding[Rounding["ROUND_UP"] = 0] = "ROUND_UP"; Rounding[Rounding["ROUND_DOWN"] = 1] = "ROUND_DOWN"; Rounding[Rounding["ROUND_CEIL"] = 2] = "ROUND_CEIL"; Rounding[Rounding["ROUND_FLOOR"] = 3] = "ROUND_FLOOR"; Rounding[Rounding["ROUND_HALF_UP"] = 4] = "ROUND_HALF_UP"; Rounding[Rounding["ROUND_HALF_DOWN"] = 5] = "ROUND_HALF_DOWN"; Rounding[Rounding["ROUND_HALF_EVEN"] = 6] = "ROUND_HALF_EVEN"; Rounding[Rounding["ROUND_HALF_CEIL"] = 7] = "ROUND_HALF_CEIL"; Rounding[Rounding["ROUND_HALF_FLOOR"] = 8] = "ROUND_HALF_FLOOR"; })(Rounding || (exports.Rounding = Rounding = {})); var Decimals; (function (Decimals) { Decimals["left"] = "left"; Decimals["right"] = "right"; Decimals["min"] = "min"; Decimals["max"] = "max"; Decimals["add"] = "add"; Decimals["sub"] = "sub"; })(Decimals || (exports.Decimals = Decimals = {})); const pickPrecision = (aPrecision, bPrecision, precisionResolution) => { if (typeof precisionResolution !== 'string') { return BigInt(precisionResolution); } switch (precisionResolution) { case Decimals.left: return aPrecision; case Decimals.right: return bPrecision; case Decimals.min: return (0, math_1.min)(aPrecision, bPrecision); case Decimals.max: return (0, math_1.max)(aPrecision, bPrecision); case Decimals.add: return aPrecision + bPrecision; case Decimals.sub: return (0, math_1.max)(aPrecision, bPrecision) - (0, math_1.min)(aPrecision, bPrecision); } }; class FixedPoint { static min(arg0, ...args) { let min = arg0; for (const arg of args) { if (arg.lt(min)) { min = arg; } } return min; } static max(arg0, ...args) { let max = arg0; for (const arg of args) { if (arg.gt(max)) { max = arg; } } return max; } constructor(base, precision) { this.plus = this.add; this.minus = this.sub; this.times = this.mul; this.multipliedBy = this.mul; this.dividedBy = this.div; this.isEqualTo = this.eq; this.isGreaterThan = this.gt; this.isLessThan = this.lt; this.isGreaterThanOrEqualTo = this.gte; this.isLessThanOrEqualTo = this.lte; this.negated = this.neg; this.absoluteValue = this.abs; this.squareRoot = this.sqrt; this._base = base; this._precision = precision; } get base() { return this._base; } get precision() { return this._precision; } add(arg, resultPrecision) { const aPrecision = this.precision; const bPrecision = arg.precision; const calcPrecision = (0, math_1.max)(aPrecision, bPrecision); const targetPrecision = pickPrecision(aPrecision, bPrecision, resultPrecision ?? Decimals.left); const aBase = (0, math_1.toPrecision)(this.base, calcPrecision, aPrecision); const bBase = (0, math_1.toPrecision)(arg.base, calcPrecision, bPrecision); const result = new FixedPoint(aBase + bBase, calcPrecision); result.setPrecision(targetPrecision); return result; } sub(arg, resultPrecision) { const aPrecision = this.precision; const bPrecision = arg.precision; const calcPrecision = (0, math_1.max)(aPrecision, bPrecision); const targetPrecision = pickPrecision(aPrecision, bPrecision, resultPrecision ?? Decimals.left); const aBase = (0, math_1.toPrecision)(this.base, calcPrecision, aPrecision); const bBase = (0, math_1.toPrecision)(arg.base, calcPrecision, bPrecision); const result = new FixedPoint(aBase - bBase, calcPrecision); result.setPrecision(targetPrecision); return result; } mul(arg, resultPrecision) { const aPrecision = this.precision; const bPrecision = arg.precision; const calcPrecision = aPrecision + bPrecision; const targetPrecision = pickPrecision(aPrecision, bPrecision, resultPrecision ?? Decimals.max); const aBase = this.base; const bBase = arg.base; const result = new FixedPoint(aBase * bBase, calcPrecision); result.setPrecision(targetPrecision); return result; } div(arg, resultPrecision) { const aPrecision = this.precision; const bPrecision = arg.precision; const calcPrecision = aPrecision + bPrecision; const targetPrecision = pickPrecision(aPrecision, bPrecision, resultPrecision ?? Decimals.max); const aBase = this.base; const bBase = arg.base; const newBase = (0, math_1.toPrecision)(aBase, calcPrecision, aPrecision) / bBase; const result = new FixedPoint((0, math_1.toPrecision)(newBase, calcPrecision, aPrecision), calcPrecision); result.setPrecision(targetPrecision); return result; } cmp(arg, comparator) { const aPrecision = this.precision; const bPrecision = arg.precision; const newPrecision = (0, math_1.max)(aPrecision, bPrecision); const aBase = (0, math_1.toPrecision)(this.base, newPrecision, aPrecision); const bBase = (0, math_1.toPrecision)(arg.base, newPrecision, bPrecision); return comparator(aBase, bBase); } eq(arg) { return this.cmp(arg, (a, b) => a === b); } gt(arg) { return this.cmp(arg, (a, b) => a > b); } lt(arg) { return this.cmp(arg, (a, b) => a < b); } gte(arg) { return this.cmp(arg, (a, b) => a >= b); } lte(arg) { return this.cmp(arg, (a, b) => a <= b); } neg() { return new FixedPoint(-this.base, this.precision); } abs() { return new FixedPoint((0, math_1.abs)(this.base), this.precision); } sqrt() { if (this.isNegative()) { throw new Error('Cannot calculate square root of negative number'); } if (this.isZero()) { return new FixedPoint(0n, this.precision); } const workingPrecision = this.precision + 10n; const workingThis = new FixedPoint((0, math_1.toPrecision)(this.base, workingPrecision, this.precision), workingPrecision); let x = new FixedPoint(workingThis.base >> (workingPrecision / 2n), workingPrecision); if (x.isZero()) { x = new FixedPoint(10n ** (workingPrecision / 2n), workingPrecision); } const two = new FixedPoint(2n * (10n ** workingPrecision), workingPrecision); const epsilon = new FixedPoint(1n, workingPrecision); for (let i = 0; i < 50; i++) { const quotient = workingThis.div(x, workingPrecision); const newX = x.add(quotient, workingPrecision).div(two, workingPrecision); if (newX.sub(x, workingPrecision).abs().lte(epsilon)) { break; } x = newX; } return x.toPrecision(this.precision); } isZero() { return this.base === 0n; } isPositive() { return this.base > 0n; } isNegative() { return this.base < 0n; } floor() { return this.round(Rounding.ROUND_FLOOR); } ceil() { return this.round(Rounding.ROUND_CEIL); } round(mode = Rounding.ROUND_HALF_UP) { if (this.precision === 0n) { return new FixedPoint(this.base, this.precision); } const isNegative = this.isNegative(); const absBase = (0, math_1.abs)(this.base); const divisor = 10n ** this.precision; const integerPart = absBase / divisor; const fractionalPart = absBase % divisor; const isHalfwayCase = fractionalPart * 2n === divisor; let rounded = integerPart; switch (mode) { case Rounding.ROUND_UP: if (fractionalPart > 0n) { rounded = integerPart + 1n; } break; case Rounding.ROUND_DOWN: rounded = integerPart; break; case Rounding.ROUND_CEIL: if (fractionalPart > 0n) { if (!isNegative) { rounded = integerPart + 1n; } else { rounded = integerPart; } } break; case Rounding.ROUND_FLOOR: if (fractionalPart > 0n) { if (!isNegative) { rounded = integerPart; } else { rounded = integerPart + 1n; } } break; case Rounding.ROUND_HALF_UP: if (fractionalPart > divisor / 2n || (isHalfwayCase)) { rounded = integerPart + 1n; } break; case Rounding.ROUND_HALF_DOWN: if (fractionalPart > divisor / 2n) { rounded = integerPart + 1n; } break; case Rounding.ROUND_HALF_EVEN: if (fractionalPart > divisor / 2n) { rounded = integerPart + 1n; } else if (isHalfwayCase) { if (integerPart % 2n === 1n) { rounded = integerPart + 1n; } } break; case Rounding.ROUND_HALF_CEIL: if (fractionalPart > divisor / 2n) { rounded = integerPart + 1n; } else if (isHalfwayCase) { if (!isNegative) { rounded = integerPart + 1n; } } break; case Rounding.ROUND_HALF_FLOOR: if (fractionalPart > divisor / 2n) { rounded = integerPart + 1n; } else if (isHalfwayCase) { if (isNegative) { rounded = integerPart + 1n; } } break; } const roundedBase = isNegative ? -rounded * divisor : rounded * divisor; return new FixedPoint(roundedBase, this.precision); } setPrecision(newPrecision, rounding = Rounding.ROUND_DOWN) { if (newPrecision < this.precision) { const rounded = new FixedPoint(this.base, this.precision - newPrecision).round(rounding); this._base = (0, math_1.toPrecision)(rounded.base, newPrecision, this.precision); this._precision = newPrecision; } else if (newPrecision > this.precision) { this._base = (0, math_1.toPrecision)(this.base, newPrecision, this.precision); this._precision = newPrecision; } } toPrecision(resultPrecision, rounding = Rounding.ROUND_DOWN) { const newPrecision = BigInt(resultPrecision); if (newPrecision < this.precision) { const rounded = new FixedPoint(this.base, this.precision - newPrecision).round(rounding); return new FixedPoint((0, math_1.toPrecision)(rounded.base, newPrecision, this.precision), newPrecision); } else { return new FixedPoint((0, math_1.toPrecision)(this.base, newPrecision, this.precision), newPrecision); } } toString() { return this.base.toString(); } toJSON() { return this.toString(); } toDecimalString() { const isNegative = this.isNegative(); let str = (0, math_1.abs)(this.base).toString().padStart(Number(this.precision) + 1, '0'); if (isNegative) { str = `-${str}`; } if (this.precision === 0n) { return str; } return str.slice(0, -Number(this.precision)) + '.' + str.slice(-Number(this.precision)); } toDecimal() { return Number(this.toDecimalString()); } valueOf() { return this.toDecimal(); } } exports.FixedPoint = FixedPoint;