UNPKG

@freemework/decimal.bignumberjs

Version:

decimal.bignumberjs library of the Freemework Project.

319 lines 13.3 kB
// This module provide a wrapper over https://www.npmjs.com/package/bignumber.js import { FDecimal, FDecimalBase, FDecimalRoundMode, FException, FExceptionArgument, FExceptionInvalidOperation } from "@freemework/common"; import { BigNumber } from "bignumber.js"; // See https://mikemcl.github.io/bignumber.js/#decimal-places const DECIMAL_PLACES_MAP = new Map(); function getBigNumberImpl(DECIMAL_PLACES) { const existentBN = DECIMAL_PLACES_MAP.get(DECIMAL_PLACES); if (existentBN !== undefined) { return existentBN; } const newBN = BigNumber.clone(); newBN.config({ DECIMAL_PLACES: DECIMAL_PLACES }); DECIMAL_PLACES_MAP.set(DECIMAL_PLACES, newBN); return newBN; } class _FDecimalBigNumber extends FDecimalBase { get backend() { return super.backend; } } export class FDecimalBackendBigNumber { static verifyText(test) { if (!FDecimal.REGEXP.test(test)) { throw new FExceptionArgument(`Bad FDecimal value: ${test}`); } } verifyInstance(test) { if (test instanceof _FDecimalBigNumber && test.backend === this) { return; } throw new FExceptionInvalidOperation(`Mixed '${FDecimal.name}' implementations detected.`); } settings; /** * * @param roundMode Default value is FDecimalRoundMode.Round */ constructor(fractionalDigits, roundMode) { if (fractionalDigits < 0 || fractionalDigits > 32) { throw new FExceptionArgument("Range 0..32 overflow", "fractionalDigits"); } this.settings = { decimalSeparator: ".", fractionalDigits, roundMode }; } abs(value) { this.verifyInstance(value); const unwrapValue = value.instance; const result = unwrapValue.abs(); return new _FDecimalBigNumber(result, this); } add(left, right) { this.verifyInstance(left); this.verifyInstance(right); const unwrapLeftValue = left.instance; const unwrapRightValue = right.instance; const result = unwrapLeftValue.plus(unwrapRightValue); return new _FDecimalBigNumber(result, this); } divide(left, right, roundMode) { this.verifyInstance(left); this.verifyInstance(right); const unwrapLeftValue = left.instance; const unwrapRightValue = right.instance; const BN = getBigNumberImpl(this.settings.fractionalDigits + 2); // Why +2 ? if (roundMode === undefined) { roundMode = this.settings.roundMode; } const result = new BN(unwrapLeftValue).div(unwrapRightValue); const bigNumberRoundMode = FDecimalBackendBigNumber.convertRoundMode(roundMode, result.isPositive()); const roundedResult = result.decimalPlaces(this.settings.fractionalDigits, bigNumberRoundMode); return new _FDecimalBigNumber(roundedResult, this); } equals(left, right) { this.verifyInstance(left); this.verifyInstance(right); const unwrapLeftValue = left.instance; const unwrapRightValue = right.instance; const result = unwrapLeftValue.isEqualTo(unwrapRightValue); return result; } fromFloat(value, _) { const fractionalDigits = this.settings.fractionalDigits; const BN = getBigNumberImpl(fractionalDigits); const raw = new BN(value); if (fractionalDigits < raw.decimalPlaces()) { let rawRoundMode; const roundMode = this.settings.roundMode; switch (roundMode) { case FDecimalRoundMode.Ceil: rawRoundMode = BigNumber.ROUND_CEIL; break; case FDecimalRoundMode.Floor: rawRoundMode = BigNumber.ROUND_FLOOR; break; case FDecimalRoundMode.Round: rawRoundMode = BigNumber.ROUND_HALF_EVEN; break; case FDecimalRoundMode.Trunc: rawRoundMode = BigNumber.ROUND_DOWN; break; default: throw new FDecimalBackendBigNumber.UnreachableRoundMode(roundMode); } const correctedRaw = raw.decimalPlaces(fractionalDigits, rawRoundMode); return new _FDecimalBigNumber(correctedRaw, this); } return new _FDecimalBigNumber(raw, this); } fromInt(value) { if (!Number.isSafeInteger(value)) { throw new FExceptionArgument(`Wrong value ${value}. Expected safe integer.`, "value"); } const CustomBigNumber = getBigNumberImpl(this.settings.fractionalDigits); const raw = new CustomBigNumber(value); return new _FDecimalBigNumber(raw, this); } gt(left, right) { this.verifyInstance(left); this.verifyInstance(right); const unwrapLeftValue = left.instance; const unwrapRightValue = right.instance; const result = unwrapLeftValue.gt(unwrapRightValue); return result; } gte(left, right) { this.verifyInstance(left); this.verifyInstance(right); const unwrapLeftValue = left.instance; const unwrapRightValue = right.instance; const result = unwrapLeftValue.gte(unwrapRightValue); return result; } inverse(value) { this.verifyInstance(value); const unwrapValue = value.instance; const result = unwrapValue.negated(); return new _FDecimalBigNumber(result, this); } isDecimal(test) { return test instanceof _FDecimalBigNumber; } isNegative(test) { this.verifyInstance(test); const unwrapValue = test.instance; const result = unwrapValue.isNegative() && !unwrapValue.isZero(); return result; } isPositive(test) { this.verifyInstance(test); const unwrapValue = test.instance; const result = unwrapValue.isPositive() && !unwrapValue.isZero(); return result; } isZero(test) { this.verifyInstance(test); const unwrapValue = test.instance; const result = unwrapValue.isZero(); return result; } lt(left, right) { this.verifyInstance(left); this.verifyInstance(right); const unwrapLeftValue = left.instance; const unwrapRightValue = right.instance; const result = unwrapLeftValue.lt(unwrapRightValue); return result; } lte(left, right) { this.verifyInstance(left); this.verifyInstance(right); const unwrapLeftValue = left.instance; const unwrapRightValue = right.instance; const result = unwrapLeftValue.lte(unwrapRightValue); return result; } max(left, right) { this.verifyInstance(left); this.verifyInstance(right); const unwrapLeftValue = left.instance; const unwrapRightValue = right.instance; const result = BigNumber.max(unwrapLeftValue, unwrapRightValue); return new _FDecimalBigNumber(result, this); } min(left, right) { this.verifyInstance(left); this.verifyInstance(right); const unwrapLeftValue = left.instance; const unwrapRightValue = right.instance; const result = BigNumber.min(unwrapLeftValue, unwrapRightValue); return new _FDecimalBigNumber(result, this); } mod(left, right, roundMode) { this.verifyInstance(left); this.verifyInstance(right); const unwrapLeftValue = left.instance; const unwrapRightValue = right.instance; const BN = getBigNumberImpl(this.settings.fractionalDigits + 2); // Why +2 ? if (roundMode === undefined) { roundMode = this.settings.roundMode; } const result = new BN(unwrapLeftValue).mod(unwrapRightValue); const bigNumberRoundMode = FDecimalBackendBigNumber.convertRoundMode(roundMode, result.isPositive()); const roundedResult = result.decimalPlaces(this.settings.fractionalDigits, bigNumberRoundMode); return new _FDecimalBigNumber(roundedResult, this); } multiply(left, right, roundMode) { this.verifyInstance(left); this.verifyInstance(right); const unwrapLeftValue = left.instance; const unwrapRightValue = right.instance; const BN = getBigNumberImpl(unwrapLeftValue.decimalPlaces() + unwrapRightValue.decimalPlaces()); if (roundMode === undefined) { roundMode = this.settings.roundMode; } const result = new BN(unwrapLeftValue).multipliedBy(unwrapRightValue); const bigNumberRoundMode = FDecimalBackendBigNumber.convertRoundMode(roundMode, result.isPositive()); const roundedResult = result.decimalPlaces(this.settings.fractionalDigits, bigNumberRoundMode); return new _FDecimalBigNumber(roundedResult, this); } parse(value) { FDecimalBackendBigNumber.verifyText(value); // raise error if wrong value const fractionalDigits = this.settings.fractionalDigits; const CustomBigNumber = getBigNumberImpl(fractionalDigits); const raw = new CustomBigNumber(value); const decimalPlaces = raw.decimalPlaces(); if (fractionalDigits < decimalPlaces) { const bigNumberRoundMode = FDecimalBackendBigNumber.convertRoundMode(this.settings.roundMode, raw.isPositive()); const roundedRaw = raw.decimalPlaces(fractionalDigits, bigNumberRoundMode); return new _FDecimalBigNumber(roundedRaw, this); } else { let rawStr = raw.toString(10); if (rawStr.length < value.length) { if (decimalPlaces > 0) { // Workaround for X.XXX00000 rawStr = rawStr.padEnd(value.length, "0"); } else if (decimalPlaces === 0 && (rawStr.length + 1) < value.length) { // Workaround for X.00000 rawStr += "."; rawStr = rawStr.padEnd(value.length, "0"); } } if (rawStr !== value) { throw new FExceptionArgument(`The value '${value}' cannot be represented in backend '${FDecimalBackendBigNumber.name}'`); } return new _FDecimalBigNumber(raw, this); } } round(value, fractionDigits, roundMode) { FractionDigitsGuard.verifyFraction(fractionDigits); this.verifyInstance(value); const unwrapValue = value.instance; if (fractionDigits < unwrapValue.decimalPlaces()) { if (roundMode === undefined) { roundMode = this.settings.roundMode; } const bigNumberRoundMode = FDecimalBackendBigNumber.convertRoundMode(roundMode, unwrapValue.isPositive()); const roundedResult = unwrapValue.decimalPlaces(fractionDigits, bigNumberRoundMode); return new _FDecimalBigNumber(roundedResult, this); } else { return value; } } subtract(left, right) { this.verifyInstance(left); this.verifyInstance(right); const unwrapLeftValue = left.instance; const unwrapRightValue = right.instance; const result = unwrapLeftValue.minus(unwrapRightValue); return new _FDecimalBigNumber(result, this); } toNumber(value) { this.verifyInstance(value); const unwrapValue = value.instance; const result = unwrapValue.toNumber(); return result; } toString(value) { this.verifyInstance(value); const unwrapValue = value.instance; const result = unwrapValue.toString(10); return result; } static convertRoundMode(roundMode, isPositive) { switch (roundMode) { case FDecimalRoundMode.Ceil: return isPositive === true ? BigNumber.ROUND_UP : BigNumber.ROUND_DOWN; case FDecimalRoundMode.Floor: return isPositive === true ? BigNumber.ROUND_DOWN : BigNumber.ROUND_UP; case FDecimalRoundMode.Round: return isPositive === true ? BigNumber.ROUND_HALF_UP : BigNumber.ROUND_HALF_DOWN; case FDecimalRoundMode.Trunc: return BigNumber.ROUND_DOWN; default: throw new FDecimalBackendBigNumber.UnreachableRoundMode(roundMode); } } } (function (FDecimalBackendBigNumber) { class UnreachableRoundMode extends FException { constructor(roundMode) { super(`Unsupported round mode: ${roundMode}`); } } FDecimalBackendBigNumber.UnreachableRoundMode = UnreachableRoundMode; })(FDecimalBackendBigNumber || (FDecimalBackendBigNumber = {})); var FractionDigitsGuard; (function (FractionDigitsGuard) { function isFraction(test) { return Number.isSafeInteger(test) && test >= 0; } FractionDigitsGuard.isFraction = isFraction; function verifyFraction(test) { if (!isFraction(test)) { throw new FExceptionArgument("Wrong argument fraction. Expected integer >= 0"); } } FractionDigitsGuard.verifyFraction = verifyFraction; })(FractionDigitsGuard || (FractionDigitsGuard = {})); //# sourceMappingURL=f_decimal_backend_big_number.js.map