@freemework/common
Version:
Common library of the Freemework Project.
374 lines • 13.8 kB
JavaScript
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