@freemework/decimal.bignumberjs
Version:
decimal.bignumberjs library of the Freemework Project.
323 lines • 13.8 kB
JavaScript
"use strict";
// This module provide a wrapper over https://www.npmjs.com/package/bignumber.js
Object.defineProperty(exports, "__esModule", { value: true });
exports.FDecimalBackendBigNumber = void 0;
const common_1 = require("@freemework/common");
const bignumber_js_1 = require("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_js_1.BigNumber.clone();
newBN.config({ DECIMAL_PLACES: DECIMAL_PLACES });
DECIMAL_PLACES_MAP.set(DECIMAL_PLACES, newBN);
return newBN;
}
class _FDecimalBigNumber extends common_1.FDecimalBase {
get backend() { return super.backend; }
}
class FDecimalBackendBigNumber {
static verifyText(test) {
if (!common_1.FDecimal.REGEXP.test(test)) {
throw new common_1.FExceptionArgument(`Bad FDecimal value: ${test}`);
}
}
verifyInstance(test) {
if (test instanceof _FDecimalBigNumber && test.backend === this) {
return;
}
throw new common_1.FExceptionInvalidOperation(`Mixed '${common_1.FDecimal.name}' implementations detected.`);
}
settings;
/**
*
* @param roundMode Default value is FDecimalRoundMode.Round
*/
constructor(fractionalDigits, roundMode) {
if (fractionalDigits < 0 || fractionalDigits > 32) {
throw new common_1.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 common_1.FDecimalRoundMode.Ceil:
rawRoundMode = bignumber_js_1.BigNumber.ROUND_CEIL;
break;
case common_1.FDecimalRoundMode.Floor:
rawRoundMode = bignumber_js_1.BigNumber.ROUND_FLOOR;
break;
case common_1.FDecimalRoundMode.Round:
rawRoundMode = bignumber_js_1.BigNumber.ROUND_HALF_EVEN;
break;
case common_1.FDecimalRoundMode.Trunc:
rawRoundMode = bignumber_js_1.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 common_1.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_js_1.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_js_1.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 common_1.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 common_1.FDecimalRoundMode.Ceil: return isPositive === true ? bignumber_js_1.BigNumber.ROUND_UP : bignumber_js_1.BigNumber.ROUND_DOWN;
case common_1.FDecimalRoundMode.Floor: return isPositive === true ? bignumber_js_1.BigNumber.ROUND_DOWN : bignumber_js_1.BigNumber.ROUND_UP;
case common_1.FDecimalRoundMode.Round: return isPositive === true ? bignumber_js_1.BigNumber.ROUND_HALF_UP : bignumber_js_1.BigNumber.ROUND_HALF_DOWN;
case common_1.FDecimalRoundMode.Trunc: return bignumber_js_1.BigNumber.ROUND_DOWN;
default:
throw new FDecimalBackendBigNumber.UnreachableRoundMode(roundMode);
}
}
}
exports.FDecimalBackendBigNumber = FDecimalBackendBigNumber;
(function (FDecimalBackendBigNumber) {
class UnreachableRoundMode extends common_1.FException {
constructor(roundMode) {
super(`Unsupported round mode: ${roundMode}`);
}
}
FDecimalBackendBigNumber.UnreachableRoundMode = UnreachableRoundMode;
})(FDecimalBackendBigNumber || (exports.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 common_1.FExceptionArgument("Wrong argument fraction. Expected integer >= 0");
}
}
FractionDigitsGuard.verifyFraction = verifyFraction;
})(FractionDigitsGuard || (FractionDigitsGuard = {}));
//# sourceMappingURL=f_decimal_backend_big_number.js.map