UNPKG

@freemework/common

Version:

Common library of the Freemework Project.

374 lines 13.8 kB
import { FException } from "../exception/f_exception.js"; import { FExceptionArgument } from "../exception/f_exception_argument.js"; import { FExceptionInvalidOperation } from "../exception/f_exception_invalid_operation.js"; export class FDecimal { static REGEXP = /^([+-]?)(0|[1-9][0-9]*)(\.([0-9]+))?$/; static _cfg = null; static get cfg() { const cfg = this._cfg; if (cfg !== null) { return cfg; } throw new FExceptionInvalidOperation(`${FDecimal.name} is not configured. Did you call ${FDecimal.name}.configure()?`); } static get backend() { return FDecimal.cfg.backend; } static get settings() { return FDecimal.cfg.settings; } static configure(backend) { if (FDecimal._cfg !== null) { throw new FExceptionInvalidOperation(`Cannot ${FDecimal.name}.configure() twice. By design you have to call ${FDecimal.name}.configure() once.`); } this._cfg = Object.freeze({ backend, settings: Object.freeze({ ...backend.settings }) }); } /** * Analog of Math​.abs() * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs */ static abs(value) { return FDecimal.backend.abs(value); } static add(left, right) { return FDecimal.backend.add(left, right); } static divide(left, right, roundMode) { return FDecimal.backend.divide(left, right, roundMode); } static equals(left, right) { return FDecimal.backend.equals(left, right); } static fromFloat(value, roundMode) { return FDecimal.backend.fromFloat(value, roundMode); } static fromInt(value) { return FDecimal.backend.fromInt(value); } static gt(left, right) { return FDecimal.backend.gt(left, right); } static gte(left, right) { return FDecimal.backend.gte(left, right); } static inverse(value) { return FDecimal.backend.inverse(value); } static isDecimal(test) { return FDecimal.backend.isDecimal(test); } static isNegative(test) { return FDecimal.backend.isNegative(test); } static isPositive(test) { return FDecimal.backend.isPositive(test); } static isZero(test) { return FDecimal.backend.isZero(test); } static lt(left, right) { return FDecimal.backend.lt(left, right); } static lte(left, right) { return FDecimal.backend.lte(left, right); } /** * Analog of Math.max() * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max */ static max(left, right) { return FDecimal.backend.max(left, right); } /** * Analog of Math.min() * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min */ static min(left, right) { return FDecimal.backend.min(left, right); } static mod(left, right, roundMode) { return FDecimal.backend.mod(left, right, roundMode); } static multiply(left, right, roundMode) { return FDecimal.backend.multiply(left, right, roundMode); } static parse(value) { return FDecimal.backend.parse(value); } static round(value, fractionDigits, roundMode) { return FDecimal.backend.round(value, fractionDigits, roundMode); } static subtract(left, right) { return FDecimal.backend.subtract(left, right); } } export function isDecimalFraction(test) { return Number.isSafeInteger(test) && test >= 0; } export function verifyDecimalFraction(test) { if (!isDecimalFraction(test)) { throw new FExceptionArgument("Wrong argument fraction. Expected integer >= 0"); } } export var FDecimalRoundMode; (function (FDecimalRoundMode) { /** * Round to the smallest Financial greater than or equal to a given Financial. * * In other words: Round UP * * Example of Ceil to fraction:2 * * 0.595 -> 0.60 * * 0.555 -> 0.56 * * 0.554 -> 0.56 * * -0.595 -> -0.59 * * -0.555 -> -0.55 * * -0.554 -> -0.55 */ FDecimalRoundMode["Ceil"] = "Ceil"; /** * Round to the largest Financial less than or equal to a given Financial. * * In other words: Round DOWN * * Example of Floor to fraction:2 * * 0.595 -> 0.59 * * 0.555 -> 0.55 * * 0.554 -> 0.55 * * -0.595 -> -0.60 * * -0.555 -> -0.56 * * -0.554 -> -0.56 */ FDecimalRoundMode["Floor"] = "Floor"; /** * Round to the Financial rounded to the nearest Financial. * * In other words: Round classic * * Example of Round to fraction:2 * * 0.595 -> 0.60 * * 0.555 -> 0.56 * * 0.554 -> 0.55 * * -0.595 -> -0.60 * * -0.555 -> -0.55 * * -0.554 -> -0.55 */ FDecimalRoundMode["Round"] = "Round"; /** * Round to the Financial by removing fractional digits. * * Works same as Floor in positive range. * * Works same as Ceil in negative range * * Example of Trunc to fraction:2 * * 0.595 -> 0.59 * * 0.555 -> 0.55 * * 0.554 -> 0.55 * * -0.595 -> -0.59 * * -0.555 -> -0.55 * * -0.554 -> -0.55 */ FDecimalRoundMode["Trunc"] = "Trunc"; })(FDecimalRoundMode || (FDecimalRoundMode = {})); export class FDecimalRoundModeUnreachableException extends FException { constructor(roundMode) { super(`Unsupported round mode: ${roundMode}`); } } (function (FDecimal) { /** * @deprecated Use FDecimalFraction instead */ let FractionDigits; (function (FractionDigits) { /** * @deprecated Use isDecimalFraction instead */ FractionDigits.isDecimalFractionDigits = isDecimalFraction; /** * @deprecated Use verifyDecimalFraction instead */ FractionDigits.verifyFraction = verifyDecimalFraction; })(FractionDigits = FDecimal.FractionDigits || (FDecimal.FractionDigits = {})); })(FDecimal || (FDecimal = {})); export class FDecimalBase { _instance; _backend; constructor(instance, backend) { this._instance = instance; this._backend = backend; } get instance() { return this._instance; } abs() { return this._backend.abs(this); } add(value) { return this._backend.add(this, value); } divide(value, roundMode) { return this._backend.divide(this, value, roundMode); } equals(value) { return this._backend.equals(this, value); } inverse() { return this._backend.inverse(this); } isNegative() { return this._backend.isNegative(this); } isPositive() { return this._backend.isPositive(this); } isZero() { return this._backend.isZero(this); } mod(value) { return this._backend.mod(this, value); } multiply(value, roundMode) { return this._backend.multiply(this, value, roundMode); } round(fractionDigits, roundMode) { return this._backend.round(this, fractionDigits, roundMode); } subtract(value) { return this._backend.subtract(this, value); } toNumber() { return this._backend.toNumber(this); } toString() { return this._backend.toString(this); } toJSON() { return this.toString(); } get backend() { return this._backend; } } export class FDecimalBackendNumber { static verifyInstance(test) { if (test instanceof _FDecimalNumber) { return; } throw new FExceptionInvalidOperation(`Mixed '${FDecimal.name}' implementations detected.`); } settings; /** * * @param roundMode Default value is FDecimalRoundMode.Round */ constructor(fractionalDigits, roundMode) { if (fractionalDigits < 0 || fractionalDigits > 20) { throw new FExceptionArgument("Range 0..20 overflow", "fractionalDigits"); } this.settings = { decimalSeparator: ".", fractionalDigits, roundMode }; } abs(value) { FDecimalBackendNumber.verifyInstance(value); return new _FDecimalNumber(Math.abs(value.instance), this); } add(left, right) { FDecimalBackendNumber.verifyInstance(left); FDecimalBackendNumber.verifyInstance(right); return new _FDecimalNumber(left.instance + right.instance, this); } divide(left, right, roundMode) { FDecimalBackendNumber.verifyInstance(left); FDecimalBackendNumber.verifyInstance(right); const divideValueInstance = left.instance / right.instance; const roundedDivideValueInstance = this._round(divideValueInstance, this.settings.fractionalDigits, roundMode); return new _FDecimalNumber(roundedDivideValueInstance, this); } equals(left, right) { FDecimalBackendNumber.verifyInstance(left); FDecimalBackendNumber.verifyInstance(right); return left.instance === right.instance; } fromFloat(value, _) { return new _FDecimalNumber(value, this); } fromInt(value) { return new _FDecimalNumber(value, this); } gt(left, right) { FDecimalBackendNumber.verifyInstance(left); FDecimalBackendNumber.verifyInstance(right); return left.instance > right.instance; } gte(left, right) { FDecimalBackendNumber.verifyInstance(left); FDecimalBackendNumber.verifyInstance(right); return left.instance >= right.instance; } inverse(value) { FDecimalBackendNumber.verifyInstance(value); return new _FDecimalNumber(value.instance * -1, this); } isDecimal(test) { return test instanceof _FDecimalNumber; } isNegative(test) { FDecimalBackendNumber.verifyInstance(test); return test.instance < 0; } isPositive(test) { FDecimalBackendNumber.verifyInstance(test); return test.instance > 0; } isZero(test) { FDecimalBackendNumber.verifyInstance(test); return test.instance == 0; } lt(left, right) { FDecimalBackendNumber.verifyInstance(left); FDecimalBackendNumber.verifyInstance(right); return left.instance < right.instance; } lte(left, right) { FDecimalBackendNumber.verifyInstance(left); FDecimalBackendNumber.verifyInstance(right); return left.instance <= right.instance; } max(left, right) { FDecimalBackendNumber.verifyInstance(left); FDecimalBackendNumber.verifyInstance(right); return new _FDecimalNumber(Math.max(left.instance, right.instance), this); } min(left, right) { FDecimalBackendNumber.verifyInstance(left); FDecimalBackendNumber.verifyInstance(right); return new _FDecimalNumber(Math.min(left.instance, right.instance), this); } mod(left, right, roundMode) { FDecimalBackendNumber.verifyInstance(left); FDecimalBackendNumber.verifyInstance(right); const modValueInstance = left.instance % right.instance; const roundedModValueInstance = this._round(modValueInstance, this.settings.fractionalDigits, roundMode); return new _FDecimalNumber(roundedModValueInstance, this); } multiply(left, right, roundMode) { FDecimalBackendNumber.verifyInstance(left); FDecimalBackendNumber.verifyInstance(right); const multiplyValueInstance = left.instance * right.instance; const roundedMultiplyValueInstance = this._round(multiplyValueInstance, this.settings.fractionalDigits, roundMode); return new _FDecimalNumber(roundedMultiplyValueInstance, this); } parse(value) { return new _FDecimalNumber(Number.parseFloat(value), this); } round(value, fractionDigits, roundMode) { FDecimalBackendNumber.verifyInstance(value); const roundedValueInstance = this._round(value.instance, fractionDigits, roundMode); return new _FDecimalNumber(roundedValueInstance, this); } _round(valueInstance, fractionDigits, roundMode) { class UnreachableRoundModeException extends FExceptionInvalidOperation { constructor(_) { super(); } } if (roundMode === undefined) { roundMode = this.settings.roundMode; } const powValue = Math.pow(10, fractionDigits + 1); const powedValueInstance = valueInstance * powValue; let powedRoundedValueInstance; switch (roundMode) { case FDecimalRoundMode.Ceil: powedRoundedValueInstance = Math.ceil(powedValueInstance); break; case FDecimalRoundMode.Floor: powedRoundedValueInstance = Math.floor(powedValueInstance); break; case FDecimalRoundMode.Round: powedRoundedValueInstance = Math.round(powedValueInstance); break; case FDecimalRoundMode.Trunc: powedRoundedValueInstance = Math.trunc(powedValueInstance); break; default: throw new UnreachableRoundModeException(roundMode); } const roundedValueInstance = powedRoundedValueInstance / powValue; return roundedValueInstance; } subtract(left, right) { FDecimalBackendNumber.verifyInstance(left); FDecimalBackendNumber.verifyInstance(right); return new _FDecimalNumber(left.instance - right.instance, this); } toNumber(value) { FDecimalBackendNumber.verifyInstance(value); return value.instance; } toString(value) { FDecimalBackendNumber.verifyInstance(value); return value.instance.toFixed(this.settings.fractionalDigits); } } class _FDecimalNumber extends FDecimalBase { } //# sourceMappingURL=f_decimal.js.map