PHP WebShell

Текущая директория: /usr/lib/node_modules/bitgo/node_modules/@vechain/sdk-core/src/vcdm

Просмотр файла: FixedPointNumber.ts

import { InvalidDataType, InvalidOperation } from '@vechain/sdk-errors';
import { type VeChainDataModel } from './VeChainDataModel';
import { Txt } from './Txt';

/**
 * Represents a fixed-point number for precision arithmetic.
 */
class FixedPointNumber implements VeChainDataModel<FixedPointNumber> {
    /**
     * Base of value notation.
     */
    private static readonly BASE = 10n;

    /**
     * The default number of decimal places to use for fixed-point math.
     *
     * @see
     * [bignumber.js DECIMAL_PLACES](https://mikemcl.github.io/bignumber.js/#decimal-places)
     *
     * @constant {bigint}
     */
    protected static readonly DEFAULT_FRACTIONAL_DECIMALS = 20n;

    /**
     * Not a Number.
     *
     * @remarks {@link fractionalDigits} and {@link scaledValue} not meaningful.
     *
     * @see [Number.NaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/NaN)
     *
     */
    public static readonly NaN = new FixedPointNumber(0n, 0n, NaN);

    /**
     * The negative Infinity value.
     *
     * @remarks {@link fractionalDigits} and {@link scaledValue} not meaningful.
     *
     * @see [Number.NEGATIVE_INFINITY](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/NEGATIVE_INFINITY)
     */
    public static readonly NEGATIVE_INFINITY = new FixedPointNumber(
        0n,
        0n,
        Number.NEGATIVE_INFINITY
    );

    /**
     * Represents the one constant.
     */
    public static readonly ONE = FixedPointNumber.of(1n);

    /**
     * The positive Infinite value.
     *
     * @remarks {@link fractionalDigits} and {@link scaledValue} not meaningful.
     *
     * @see [Number.POSITIVE_INFINITY](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/POSITIVE_INFINITY)
     */
    public static readonly POSITIVE_INFINITY = new FixedPointNumber(
        0n,
        0n,
        Number.POSITIVE_INFINITY
    );

    /**
     * Regular expression pattern for matching integers expressed as base 10 strings.
     */
    private static readonly REGEX_INTEGER: RegExp = /^[-+]?\d+$/;

    /**
     * Regular expression for matching numeric values expressed as base 10 strings.
     */
    private static readonly REGEX_NUMBER =
        /(^[-+]?\d+(\.\d+)?)$|(^[-+]?\.\d+)$/;

    /**
     * Regular expression pattern for matching natural numbers expressed as base 10 strings.
     */
    private static readonly REGEX_NATURAL: RegExp = /^\d+$/;

    /**
     * Represents the zero constant.
     */
    public static readonly ZERO = new FixedPointNumber(0n, 0n, 0);

    /**
     * Edge Flag denotes the {@link NaN} or {@link NEGATIVE_INFINITY} or {@link POSITIVE_INFINITY} value.
     *
     * @remarks If `ef` is not zero, {@link fractionalDigits} and {@link scaledValue} are not meaningful.
     */
    protected readonly edgeFlag: number;

    /**
     * Fractional Digits or decimal places.
     *
     * @see [bignumber.js precision](https://mikemcl.github.io/bignumber.js/#sd)
     */
    public readonly fractionalDigits: bigint;

    /**
     * Scaled Value = value * 10 ^ {@link fractionalDigits}.
     */
    public readonly scaledValue: bigint;

    /**
     * Returns the integer part of this FixedPointNumber value.
     *
     * @return {bigint} the integer part of this FixedPointNumber value.
     *
     * @throws {InvalidOperation} If the value is not finite.
     */
    get bi(): bigint {
        if (this.isFinite()) {
            return (
                this.scaledValue /
                FixedPointNumber.BASE ** this.fractionalDigits
            );
        }
        throw new InvalidOperation(
            'FixedPointNumber.bi',
            'not finite value cannot cast to big integer',
            { this: this.toString() }
        );
    }

    /**
     * Returns the array of bytes representing the *Normalization Form Canonical Composition*
     * [Unicode Equivalence](https://en.wikipedia.org/wiki/Unicode_equivalence)
     * of this value expressed in decimal base.
     */
    get bytes(): Uint8Array {
        return Txt.of(this.toString()).bytes;
    }

    /**
     * Return this value approximated as {@link number}.
     */
    get n(): number {
        if (this.isNaN()) return Number.NaN;
        if (this.isNegativeInfinite()) return Number.NEGATIVE_INFINITY;
        if (this.isPositiveInfinite()) return Number.POSITIVE_INFINITY;
        if (this.isZero()) return 0;
        return Number(this.scaledValue) * 10 ** -Number(this.fractionalDigits);
    }

    /**
     * Returns the new Fixed-Point Number (FixedPointNumber) instance having
     *
     * @param {bigint} fd - Number of Fractional Digits (or decimal places).
     * @param {bigint} sv - Scaled Value.
     * @param {number} [ef=0] - Edge Flag.
     */
    protected constructor(fd: bigint, sv: bigint, ef: number = 0) {
        this.fractionalDigits = fd;
        this.edgeFlag = ef;
        this.scaledValue = sv;
    }

    /**
     * Returns a FixedPointNumber whose value is the absolute value, i.e. the magnitude, of the value of this FixedPointNumber.
     *
     * @return {FixedPointNumber} the absolute value of this FixedPointNumber.
     *
     * @see [bignumber.js absoluteValue](https://mikemcl.github.io/bignumber.js/#abs)
     */
    public abs(): FixedPointNumber {
        if (this.isNaN()) return FixedPointNumber.NaN;
        if (this.isNegativeInfinite())
            return FixedPointNumber.POSITIVE_INFINITY;
        return new FixedPointNumber(
            this.fractionalDigits,
            this.scaledValue < 0n ? -this.scaledValue : this.scaledValue,
            this.edgeFlag
        );
    }

    /**
     * Compares this instance with `that` FixedPointNumber instance.
     * * Returns 0 if this is equal to `that` FixedPointNumber, including infinite with equal sign;
     * * Returns -1, if this is -Infinite or less than `that` FixedPointNumber;,
     * * Returns 1 if this is +Infinite or greater than `that` FixedPointNumber.
     *
     * @param {FixedPointNumber} that - The instance to compare with this instance.
     * @return {number} Returns -1, 0, or 1 if this instance is less than, equal to, or greater
     * than the specified instance, respectively.
     * @throw InvalidOperation If this or `that` FixedPointNumber is {@link NaN}.
     *
     * @see [bignumber.js comparedTo](https://mikemcl.github.io/bignumber.js/#cmp)
     */
    public compareTo(that: FixedPointNumber): number {
        if (this.isNaN() || that.isNaN())
            throw new InvalidOperation(
                'FixedPointNumber.compareTo',
                'compare between NaN',
                {
                    this: `${this}`,
                    that: `${that}`
                }
            );
        if (this.isNegativeInfinite())
            return that.isNegativeInfinite() ? 0 : -1;
        if (this.isPositiveInfinite()) return that.isPositiveInfinite() ? 0 : 1;
        if (that.isNegativeInfinite()) return 1;
        if (that.isPositiveInfinite()) return -1;
        const fd = this.maxFractionalDigits(that, this.fractionalDigits); // Max common fractional decimals.
        const delta = this.dp(fd).scaledValue - that.dp(fd).scaledValue;
        return delta < 0n ? -1 : delta === 0n ? 0 : 1;
    }

    /**
     * Compares this instance with `that` FixedPointNumber instance.
     * * **Returns `null` if either instance is NaN;**
     * * Returns 0 if this is equal to `that` FixedPointNumber, including infinite with equal sign;
     * * Returns -1, if this is -Infinite or less than `that` FixedPointNumber;,
     * * Returns 1 if this is +Infinite or greater than `that` FixedPointNumber.
     *
     * @param {FixedPointNumber} that - The instance to compare with this instance.
     * @return {null | number} A null if either instance is NaN;
     * -1, 0, or 1 if this instance is less than, equal to, or greater
     * than the specified instance, respectively.
     *
     * @remarks This method uses internally {@link compareTo} wrapping the {@link InvalidOperation} exception
     * when comparing between {@link NaN} values to behave according the
     * [[bignumber.js comparedTo](https://mikemcl.github.io/bignumber.js/#cmp)] rules.
     */
    public comparedTo(that: FixedPointNumber): null | number {
        try {
            return this.compareTo(that);
        } catch {
            return null;
        }
    }

    /**
     * Returns a FixedPointNumber whose value is the value of this FixedPointNumber divided by `that` FixedPointNumber.
     *
     * Limit cases
     * * 0 / 0 = NaN
     * * NaN / ±n = NaN
     * * ±Infinity / ±Infinity = NaN
     * * +n / NaN = NaN
     * * +n / ±Infinity = 0
     * * -n / 0 = -Infinity
     * * +n / 0 = +Infinity
     *
     * @param {FixedPointNumber} that - The fixed-point number to divide by.
     * @return {FixedPointNumber} The result of the division.
     *
     * @remarks The precision is the greater of the precision of the two operands.
     *
     * @see [bignumber.js dividedBy](https://mikemcl.github.io/bignumber.js/#div)
     */
    public div(that: FixedPointNumber): FixedPointNumber {
        if (this.isNaN() || that.isNaN()) return FixedPointNumber.NaN;
        if (this.isNegativeInfinite()) {
            if (that.isInfinite()) return FixedPointNumber.NaN;
            if (that.isPositive()) return FixedPointNumber.NEGATIVE_INFINITY;
            return FixedPointNumber.POSITIVE_INFINITY;
        }
        if (this.isPositiveInfinite()) {
            if (that.isInfinite()) return FixedPointNumber.NaN;
            if (that.isPositive()) return FixedPointNumber.POSITIVE_INFINITY;
            return FixedPointNumber.NEGATIVE_INFINITY;
        }
        if (that.isInfinite()) return FixedPointNumber.ZERO;
        if (that.isZero()) {
            if (this.isZero()) return FixedPointNumber.NaN;
            if (this.isNegative()) return FixedPointNumber.NEGATIVE_INFINITY;
            return FixedPointNumber.POSITIVE_INFINITY;
        }
        const fd = this.maxFractionalDigits(that, this.fractionalDigits); // Max common fractional decimals.
        return new FixedPointNumber(
            fd,
            FixedPointNumber.div(
                fd,
                this.dp(fd).scaledValue,
                that.dp(fd).scaledValue
            )
        ).dp(this.fractionalDigits); // Minimize fractional decimals without precision loss.
    }

    /**
     * Divides the given dividend by the given divisor, adjusted by a factor based on fd.
     *
     * @param {bigint} fd - The factor determining the power of 10 to apply to the dividend.
     * @param {bigint} dividend - The number to be divided.
     * @param {bigint} divisor - The number by which to divide the dividend.
     *
     * @return {bigint} - The result of the division, adjusted by the given factor fd.
     */
    private static div(fd: bigint, dividend: bigint, divisor: bigint): bigint {
        return (FixedPointNumber.BASE ** fd * dividend) / divisor;
    }

    /**
     * Adjust the precision of the floating-point number by the specified
     * number of decimal places.
     *
     * @param decimalPlaces The number of decimal places to adjust to,
     *                      it must be a positive value.
     * @return {FixedPointNumber} A new FixedPointNumber instance with the adjusted precision.
     * @throws InvalidDataType if `decimalPlaces` is negative.
     */
    public dp(decimalPlaces: bigint | number): FixedPointNumber {
        const dp = BigInt(decimalPlaces);
        if (dp >= 0) {
            let fd = this.fractionalDigits;
            let sv = this.scaledValue;
            if (dp > fd) {
                // Scale up.
                sv *= FixedPointNumber.BASE ** (dp - fd);
                fd = dp;
            } else {
                // Scale down.
                while (fd > dp && sv % FixedPointNumber.BASE === 0n) {
                    fd--;
                    sv /= FixedPointNumber.BASE;
                }
            }
            return new FixedPointNumber(fd, sv, this.edgeFlag);
        }
        throw new InvalidDataType(
            'FixedPointNumber.scale',
            'negative `dp` arg',
            { dp: `${dp}` }
        );
    }

    /**
     * Returns `true `if the value of thisFPN is equal to the value of `that` FixedPointNumber, otherwise returns `false`.
     *
     * As with JavaScript, `NaN` does not equal `NaN`.
     *
     * @param {FixedPointNumber} that - The FixedPointNumber to compare against.
     * @return {boolean} `true` if the FixedPointNumber numbers are equal, otherwise `false`.
     *
     * @remarks This method uses {@link comparedTo} internally.
     *
     * @see [bigbumber.js isEqualTo](https://mikemcl.github.io/bignumber.js/#eq)
     */
    public eq(that: FixedPointNumber): boolean {
        return this.comparedTo(that) === 0;
    }

    /**
     * Returns `true` if the value of this FixedPointNumber is greater than `that` FixedPointNumber`, otherwise returns `false`.
     *
     * @param {FixedPointNumber} that The FixedPointNumber to compare against.
     * @return {boolean} `true` if this FixedPointNumber is greater than `that` FixedPointNumber, otherwise `false`.
     *
     * @remarks This method uses {@link comparedTo} internally.
     *
     * @see [bignummber.js isGreaterThan](https://mikemcl.github.io/bignumber.js/#gt)
     */
    public gt(that: FixedPointNumber): boolean {
        const cmp = this.comparedTo(that);
        return cmp !== null && cmp > 0;
    }

    /**
     * Returns `true` if the value of this FixedPointNumber is greater or equal than `that` FixedPointNumber`, otherwise returns `false`.
     *
     * @param {FixedPointNumber} that - The FixedPointNumber to compare against.
     * @return {boolean} `true` if this FixedPointNumber is greater or equal than `that` FixedPointNumber, otherwise `false`.
     *
     * @remarks This method uses {@link comparedTo} internally.
     *
     * @see [bignumber.js isGreaterThanOrEqualTo](https://mikemcl.github.io/bignumber.js/#gte)
     */
    public gte(that: FixedPointNumber): boolean {
        const cmp = this.comparedTo(that);
        return cmp !== null && cmp >= 0;
    }

    /**
     * Returns a fixed-point number whose value is the integer part of dividing the value of this fixed-point number
     * by `that` fixed point number.
     *
     * Limit cases
     * * 0 / 0 = NaN
     * * NaN / ±n = NaN
     * * ±Infinity / ±Infinity = NaN
     * * +n / NaN = NaN
     * * +n / ±Infinite = 0
     * * -n / 0 = -Infinite
     * * +n / 0 = +Infinite
     *
     * @param {FixedPointNumber} that - The fixed-point number to divide by.
     * @return {FixedPointNumber} The result of the division.
     *
     * @remarks The precision is the greater of the precision of the two operands.
     *
     * @see [bignumber.js dividedToIntegerBy](https://mikemcl.github.io/bignumber.js/#divInt)
     */
    public idiv(that: FixedPointNumber): FixedPointNumber {
        if (this.isNaN() || that.isNaN()) return FixedPointNumber.NaN;
        if (this.isNegativeInfinite()) {
            if (that.isInfinite()) return FixedPointNumber.NaN;
            if (that.isPositive()) return FixedPointNumber.NEGATIVE_INFINITY;
            return FixedPointNumber.POSITIVE_INFINITY;
        }
        if (this.isPositiveInfinite()) {
            if (that.isInfinite()) return FixedPointNumber.NaN;
            if (that.isPositive()) return FixedPointNumber.POSITIVE_INFINITY;
            return FixedPointNumber.NEGATIVE_INFINITY;
        }
        if (that.isInfinite()) return FixedPointNumber.ZERO;
        if (that.isZero()) {
            if (this.isZero()) return FixedPointNumber.NaN;
            if (this.isNegative()) return FixedPointNumber.NEGATIVE_INFINITY;
            return FixedPointNumber.POSITIVE_INFINITY;
        }
        const fd = this.maxFractionalDigits(that, this.fractionalDigits); // Max common fractional decimals.
        return new FixedPointNumber(
            fd,
            FixedPointNumber.idiv(
                fd,
                this.dp(fd).scaledValue,
                that.dp(fd).scaledValue
            )
        ).dp(this.fractionalDigits); // Minimize fractional decimals without precision loss.
    }

    /**
     * Performs integer division on two big integers and scales the result by a factor of 10 raised to the power of fd.
     *
     * @param {bigint} fd - The power to which 10 is raised to scale the result.
     * @param {bigint} dividend - The number to be divided.
     * @param {bigint} divisor - The number by which dividend is divided.
     * @return {bigint} - The scaled result of the integer division.
     */
    private static idiv(fd: bigint, dividend: bigint, divisor: bigint): bigint {
        return (dividend / divisor) * FixedPointNumber.BASE ** fd;
    }

    /**
     * Returns `true `if the value of thisFPN is equal to the value of `that` FixedPointNumber, otherwise returns `false`.
     *
     * As with JavaScript, `NaN` does not equal `NaN`.
     *
     * @param {FixedPointNumber} that - The FixedPointNumber to compare against.
     * @return {boolean} `true` if the FixedPointNumber numbers are equal, otherwise `false`.
     *
     * @remarks This method uses {@link eq} internally.
     */
    public isEqual(that: FixedPointNumber): boolean {
        return this.eq(that);
    }

    /**
     * Returns `true` if the value of this FixedPointNumber is a finite number, otherwise returns `false`.
     *
     * The only possible non-finite values of a FixedPointNumber are {@link NaN}, {@link NEGATIVE_INFINITY} and {@link POSITIVE_INFINITY}.
     *
     * @return `true` if the value of this FixedPointNumber is a finite number, otherwise returns `false`.
     *
     * @see [bignumber.js isFinite](https://mikemcl.github.io/bignumber.js/#isF)
     */
    public isFinite(): boolean {
        return this.edgeFlag === 0;
    }

    /**
     * Return `true` if the value of this FixedPointNumber is {@link NEGATIVE_INFINITY} or {@link POSITIVE_INFINITY},
     * otherwise returns false.
     *
     * @return true` if the value of this FixedPointNumber is {@link NEGATIVE_INFINITY} or {@link POSITIVE_INFINITY},
     */
    public isInfinite(): boolean {
        return this.isNegativeInfinite() || this.isPositiveInfinite();
    }

    /**
     * Returns `true` if the value of this FixedPointNumber is an integer,
     * otherwise returns `false`.
     *
     * @return `true` if the value of this FixedPointNumber is an integer.
     *
     * @see [bignumber.js isInteger](https://mikemcl.github.io/bignumber.js/#isInt)
     */
    public isInteger(): boolean {
        if (this.isFinite()) {
            return (
                this.scaledValue %
                    FixedPointNumber.BASE ** this.fractionalDigits ===
                0n
            );
        }
        return false;
    }

    /**
     * Checks if a given string expression is an integer in base 10 notation,
     * considering `-` for negative and `+` optional for positive values.
     *
     * @param {string} exp - The string expression to be tested.
     *
     * @return {boolean} `true` if the expression is an integer,
     * `false` otherwise.
     */
    public static isIntegerExpression(exp: string): boolean {
        return this.REGEX_INTEGER.test(exp);
    }

    /**
     *  Returns `true` if the value of this FixedPointNumber is `NaN`, otherwise returns `false`.
     *
     *  @return `true` if the value of this FixedPointNumber is `NaN`, otherwise returns `false`.
     *
     *  @see [bignumber.js isNaN](https://mikemcl.github.io/bignumber.js/#isNaN)
     */
    public isNaN(): boolean {
        return Number.isNaN(this.edgeFlag);
    }

    /**
     * Checks if a given string expression is a natural (unsigned positive integer)
     * number in base 10 notation.
     *
     * @param {string} exp - The string expression to be tested.
     *
     * @return {boolean} `true` if the expression is a natural number,
     * `false` otherwise.
     */
    public static isNaturalExpression(exp: string): boolean {
        return this.REGEX_NATURAL.test(exp);
    }

    /**
     * Returns `true` if the sign of this FixedPointNumber is negative, otherwise returns `false`.
     *
     * @return `true` if the sign of this FixedPointNumber is negative, otherwise returns `false`.
     *
     * @see [bignumber.js isNegative](https://mikemcl.github.io/bignumber.js/#isNeg)
     */
    public isNegative(): boolean {
        return (
            (this.isFinite() && this.scaledValue < 0n) ||
            this.isNegativeInfinite()
        );
    }

    /**
     * Returns `true` if this FixedPointNumber value is {@link NEGATIVE_INFINITY}, otherwise returns `false`.
     */
    public isNegativeInfinite(): boolean {
        return this.edgeFlag === Number.NEGATIVE_INFINITY;
    }

    /**
     * Checks if a given string expression is a number in base 10 notation,
     * considering `-` for negative and `+` optional for positive values.
     *
     * The method returns `true` for the following cases.
     * - Whole numbers:
     *   - Positive whole numbers, optionally signed: 1, +2, 3, ...
     *   - Negative whole numbers: -1, -2, -3, ...
     * - Decimal numbers:
     *   - Positive decimal numbers, optionally signed: 1.0, +2.5, 3.14, ...
     *   - Negative decimal numbers: -1.0, -2.5, -3.14, ...
     *   - Decimal numbers without whole part:
     *     - Positive decimal numbers, optionally signed: .1, +.5, .75, ...
     *     - Negative decimal numbers: -.1, -.5, -.75, ...
     *
     * @param exp - The string expression to be checked.
     *
     * @return `true` is `exp` represents a number, otherwise `false`.
     */
    public static isNumberExpression(exp: string): boolean {
        return FixedPointNumber.REGEX_NUMBER.test(exp);
    }

    /**
     * Returns `true` if the sign of this FixedPointNumber is positive, otherwise returns `false`.
     *
     * @return `true` if the sign of this FixedPointNumber is positive, otherwise returns `false`.
     *
     * @see [bignumber.js isPositive](https://mikemcl.github.io/bignumber.js/#isPos)
     */
    public isPositive(): boolean {
        return (
            (this.isFinite() && this.scaledValue >= 0n) ||
            this.isPositiveInfinite()
        );
    }

    /**
     * Returns `true` if this FixedPointNumber value is {@link POSITIVE_INFINITY}, otherwise returns `false`.
     *
     * @return `true` if this FixedPointNumber value is {@link POSITIVE_INFINITY}, otherwise returns `false`.
     */
    public isPositiveInfinite(): boolean {
        return this.edgeFlag === Number.POSITIVE_INFINITY;
    }

    /**
     * Returns `true` if the value of this FixedPointNumber is zero or minus zero, otherwise returns `false`.
     *
     * @return `true` if the value of this FixedPointNumber is zero or minus zero, otherwise returns `false`.
     *
     * [see bignumber.js isZero](https://mikemcl.github.io/bignumber.js/#isZ)
     */
    public isZero(): boolean {
        return this.isFinite() && this.scaledValue === 0n;
    }

    /**
     * Returns `true` if the value of this FixedPointNumber is less than the value of `that` FixedPointNumber, otherwise returns `false`.
     *
     * @param {FixedPointNumber} that - The FixedPointNumber to compare against.
     *
     * @return {boolean} `true` if the value of this FixedPointNumber is less than the value of `that` FixedPointNumber, otherwise returns `false`.
     *
     * @remarks This method uses {@link comparedTo} internally.
     *
     * @see [bignumber.js isLessThan](https://mikemcl.github.io/bignumber.js/#lt)
     */
    public lt(that: FixedPointNumber): boolean {
        const cmp = this.comparedTo(that);
        return cmp !== null && cmp < 0;
    }

    /**
     * Returns `true` if the value of this FixedPointNumber is less than or equal to the value of `that` FixedPointNumber,
     * otherwise returns `false`.
     *
     * @param {FixedPointNumber} that - The FixedPointNumber to compare against.
     * @return {boolean} `true` if the value of this FixedPointNumber is less than or equal to the value of `that` FixedPointNumber,
     * otherwise returns `false`.
     *
     * @remarks This method uses {@link comparedTo} internally.
     *
     * @see [bignumber.js isLessThanOrEqualTo](https://mikemcl.github.io/bignumber.js/#lte)
     */
    public lte(that: FixedPointNumber): boolean {
        const cmp = this.comparedTo(that);
        return cmp !== null && cmp <= 0;
    }

    /**
     * Return the maximum between the fixed decimal value of this object and `that` one.
     * If the maximum fixed digits value is less than `minFixedDigits`, return `minFixedDigits`.
     *
     * @param {FixedPointNumber} that to evaluate if `that` has the maximum fixed digits value.
     * @param {bigint} minFixedDigits Min value of returned value.
     *
     * @return the greater fixed digits value among `this`, `that` and `minFixedDigits`.
     */
    private maxFractionalDigits(
        that: FixedPointNumber,
        minFixedDigits: bigint
    ): bigint {
        const fd =
            this.fractionalDigits < that.fractionalDigits
                ? that.fractionalDigits
                : this.fractionalDigits;
        return fd > minFixedDigits ? fd : minFixedDigits;
    }

    /**
     * Returns a FixedPointNumber whose value is the value of this FixedPointNumber minus `that` FixedPointNumber.
     *
     * Limit cases
     * * NaN - ±n = NaN
     * * ±n - NaN = NaN
     * * -Infinity - -Infinity = NaN
     * * -Infinity - +n = -Infinity
     * * +Infinity - +Infinity = NaN
     * * +Infinity - +n = +Infinity
     *
     * @param {FixedPointNumber} that - The fixed-point number to subtract.
     * @return {FixedPointNumber} The result of the subtraction. The return value is always exact and unrounded.
     *
     * @remarks The precision is the greater of the precision of the two operands.
     *
     * @see [bignumber.js minus](https://mikemcl.github.io/bignumber.js/#minus)
     */
    public minus(that: FixedPointNumber): FixedPointNumber {
        if (this.isNaN() || that.isNaN()) return FixedPointNumber.NaN;
        if (this.isNegativeInfinite())
            return that.isNegativeInfinite()
                ? FixedPointNumber.NaN
                : FixedPointNumber.NEGATIVE_INFINITY;
        if (this.isPositiveInfinite())
            return that.isPositiveInfinite()
                ? FixedPointNumber.NaN
                : FixedPointNumber.POSITIVE_INFINITY;
        const fd = this.maxFractionalDigits(that, this.fractionalDigits); // Max common fractional decimals.
        return new FixedPointNumber(
            fd,
            this.dp(fd).scaledValue - that.dp(fd).scaledValue
        ).dp(this.fractionalDigits); // Minimize fractional decimals without precision loss.
    }

    /**
     * Returns a FixedPointNumber whose value is the value of this FixedPointNumber modulo `that` FixedPointNumber,
     * i.e. the integer remainder of dividing this FixedPointNumber by `that`.
     *
     * Limit cases
     * * NaN % ±n = NaN
     * * ±n % NaN = NaN
     * * ±Infinity % n = NaN
     * * n % ±Infinity = NaN
     *
     * @param that {FixedPointNumber} - The fixed-point number to divide by.
     * @return {FixedPointNumber} the integer remainder of dividing this FixedPointNumber by `that`.
     *
     * @remarks The precision is the greater of the precision of the two operands.
     *
     * @see [bignumber.js modulo](https://mikemcl.github.io/bignumber.js/#mod)
     */
    public modulo(that: FixedPointNumber): FixedPointNumber {
        if (this.isNaN() || that.isNaN()) return FixedPointNumber.NaN;
        if (this.isInfinite() || that.isInfinite()) return FixedPointNumber.NaN;
        if (that.isZero()) return FixedPointNumber.NaN;
        const fd = this.maxFractionalDigits(that, this.fractionalDigits); // Max common fractional decimals.
        let modulo = this.abs().dp(fd).scaledValue;
        const divisor = that.abs().dp(fd).scaledValue;
        while (modulo >= divisor) {
            modulo -= divisor;
        }
        return new FixedPointNumber(fd, modulo).dp(this.fractionalDigits); // Minimize fractional decimals without precision loss.
    }

    /**
     * Multiplies two big integer values and divides by a factor of ten raised to a specified power.
     *
     * @param {bigint} multiplicand - The first number to be multiplied.
     * @param {bigint} multiplicator - The second number to be multiplied.
     * @param {bigint} fd - The power of ten by which the product is to be divided.
     *
     * @return {bigint} The result of the multiplication divided by ten raised to the specified power.
     */
    private static mul(
        multiplicand: bigint,
        multiplicator: bigint,
        fd: bigint
    ): bigint {
        return (multiplicand * multiplicator) / FixedPointNumber.BASE ** fd;
    }

    /**
     * Returns a new instance of FixedPointNumber whose value is the value of this FixedPointNumber value
     * negated, i.e. multiplied by -1.
     *
     * @see [bignumber.js negated](https://mikemcl.github.io/bignumber.js/#neg)
     */
    public negated(): FixedPointNumber {
        if (this.isNegativeInfinite())
            return FixedPointNumber.POSITIVE_INFINITY;
        if (this.isPositiveInfinite())
            return FixedPointNumber.NEGATIVE_INFINITY;
        return new FixedPointNumber(
            this.fractionalDigits,
            -this.scaledValue,
            this.edgeFlag
        );
    }

    /**
     * Constructs a new instance of FixedPointNumber (Fixed Point Number) parsing the
     * `exp` numeric expression in base 10 and representing the value with the
     * precision of `decimalPlaces` fractional decimal digits.
     *
     * @param {bigint|number|string} exp - The value to represent.
     * It can be a bigint, number, or string representation of the number.
     * @param {bigint} [decimalPlaces=this.DEFAULT_FRACTIONAL_DECIMALS] - The
     * number of fractional decimal digits to be used to represent the value.
     *
     * @return {FixedPointNumber} A new instance of FixedPointNumber with the given parameters.
     *
     * @throws {InvalidDataType} If `exp` is not a numeric expression.
     */
    public static of(
        exp: bigint | number | string | FixedPointNumber,
        decimalPlaces: bigint = this.DEFAULT_FRACTIONAL_DECIMALS
    ): FixedPointNumber {
        try {
            if (exp instanceof FixedPointNumber) {
                return new FixedPointNumber(
                    exp.fractionalDigits,
                    exp.scaledValue,
                    exp.edgeFlag
                );
            }
            if (Number.isNaN(exp))
                return new FixedPointNumber(decimalPlaces, 0n, Number.NaN);
            if (exp === Number.NEGATIVE_INFINITY)
                return new FixedPointNumber(
                    decimalPlaces,
                    -1n,
                    Number.NEGATIVE_INFINITY
                );
            if (exp === Number.POSITIVE_INFINITY)
                return new FixedPointNumber(
                    decimalPlaces,
                    1n,
                    Number.POSITIVE_INFINITY
                );
            return new FixedPointNumber(
                decimalPlaces,
                this.txtToSV(exp.toString(), decimalPlaces)
            );
        } catch (e) {
            throw new InvalidDataType(
                'FixedPointNumber.of',
                'not a number',
                { exp },
                e
            );
        }
    }

    /**
     * Returns a FixedPointNumber whose value is the value of this FixedPointNumber plus `that` FixedPointNumber.
     *
     * Limit cases
     * * NaN + ±n = NaN
     * * ±n + NaN = NaN
     * * -Infinity + -Infinity = -Infinity
     * * -Infinity + +Infinity = NaN
     * * +Infinity + -Infinity = NaN
     * * +Infinity + +Infinity = +Infinity
     *
     * @param {FixedPointNumber} that - The fixed-point number to add to the current number.
     * @return {FixedPointNumber} The result of the addition. The return value is always exact and unrounded.
     *
     * @remarks The precision is the greater of the precision of the two operands.
     *
     * @see [bignumber.js plus](https://mikemcl.github.io/bignumber.js/#plus)
     */
    public plus(that: FixedPointNumber): FixedPointNumber {
        if (this.isNaN() || that.isNaN()) return FixedPointNumber.NaN;
        if (this.isNegativeInfinite())
            return that.isPositiveInfinite()
                ? FixedPointNumber.NaN
                : FixedPointNumber.NEGATIVE_INFINITY;
        if (this.isPositiveInfinite())
            return that.isNegativeInfinite()
                ? FixedPointNumber.NaN
                : FixedPointNumber.POSITIVE_INFINITY;
        const fd = this.maxFractionalDigits(that, this.fractionalDigits); // Max common fractional decimals.
        return new FixedPointNumber(
            fd,
            this.dp(fd).scaledValue + that.dp(fd).scaledValue
        ).dp(this.fractionalDigits); // Minimize fractional decimals without precision loss.
    }

    /**
     * Returns a FixedPointNumber whose value is the value of this FixedPointNumber raised to the power of `that` FixedPointNumber.
     *
     * This method implements the
     * [Exponentiation by Squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring)
     * algorithm.
     *
     * Limit cases
     * * NaN ^ e = NaN
     * * b ^ NaN = NaN
     * * b ^ -Infinite = 0
     * * b ^ 0 = 1
     * * b ^ +Infinite = +Infinite
     * * ±Infinite ^ -e = 0
     * * ±Infinite ^ +e = +Infinite
     *
     * @param {FixedPointNumber} that - The exponent as a fixed-point number.
     * truncated to its integer component because **Exponentiation by Squaring** is not valid for rational exponents.
     * @return {FixedPointNumber} - The result of raising this fixed-point number to the power of the given exponent.
     *
     * @see [bignumber.js exponentiatedBy](https://mikemcl.github.io/bignumber.js/#pow)
     */
    public pow(that: FixedPointNumber): FixedPointNumber {
        // Limit cases
        if (this.isNaN() || that.isNaN()) return FixedPointNumber.NaN;
        if (this.isInfinite())
            return that.isZero()
                ? FixedPointNumber.ONE
                : that.isNegative()
                  ? FixedPointNumber.ZERO
                  : FixedPointNumber.POSITIVE_INFINITY;
        if (that.isNegativeInfinite()) return FixedPointNumber.ZERO;
        if (that.isPositiveInfinite()) {
            return FixedPointNumber.POSITIVE_INFINITY;
        }
        if (that.isZero()) return FixedPointNumber.ONE;
        // Exponentiation by squaring works for natural exponent value.
        let exponent = that.abs().bi;
        let base = FixedPointNumber.of(this);
        let result = FixedPointNumber.ONE;
        while (exponent > 0n) {
            // If the exponent is odd, multiply the result by the current base.
            if (exponent % 2n === 1n) {
                result = result.times(base);
            }
            // Square the base and halve the exponent.
            base = base.times(base);
            exponent = exponent / 2n;
        }
        // If exponent is negative, convert the problem to positive exponent.
        return that.isNegative() ? FixedPointNumber.ONE.div(result) : result;
    }

    /**
     * Computes the square root of a given positive bigint value using a fixed-point iteration method.
     *
     * @param {bigint} value - The positive bigint value for which the square root is to be calculated.
     * @param {bigint} fd - The iteration factor determinant.
     * @return {bigint} The calculated square root of the input bigint value.
     *
     * @throws {RangeError} If the input value is negative.
     */
    private static sqr(value: bigint, fd: bigint): bigint {
        if (value < 0n) {
            throw new RangeError(`Value must be positive`);
        }
        const sf = fd * FixedPointNumber.BASE; // Scale Factor.
        let iteration = 0;
        let actualResult = value;
        let storedResult = 0n;
        while (actualResult !== storedResult && iteration < sf) {
            storedResult = actualResult;
            actualResult =
                (actualResult + FixedPointNumber.div(fd, value, actualResult)) /
                2n;
            iteration++;
        }
        return actualResult;
    }

    /**
     * Returns a FixedPointNumber whose value is the square root of the value of this FixedPointNumber
     *
     * Limit cases
     * * NaN = NaN
     * * +Infinite = +Infinite
     * * -n = NaN
     *
     * @return {FixedPointNumber} The square root of the number.
     *
     * @see [bignumber.js sqrt](https://mikemcl.github.io/bignumber.js/#sqrt)
     */
    public sqrt(): FixedPointNumber {
        if (this.isNaN()) return FixedPointNumber.NaN;
        if (this.isNegativeInfinite()) return FixedPointNumber.NaN;
        if (this.isPositiveInfinite())
            return FixedPointNumber.POSITIVE_INFINITY;
        try {
            return new FixedPointNumber(
                this.fractionalDigits,
                FixedPointNumber.sqr(this.scaledValue, this.fractionalDigits)
            );
        } catch {
            return FixedPointNumber.NaN;
        }
    }

    /**
     * Returns a FixedPointNumber whose value is the value of this FixedPointNumber multiplied by `that` FixedPointNumber.
     *
     * Limits cases
     * * NaN * n = NaN
     * * n * NaN = NaN
     * * -Infinite * -n = +Infinite
     * * -Infinite * +n = -Infinite
     * * +Infinite * -n = -Infinite
     * * +Infinite * +n = +Infinite
     *
     * @param {FixedPointNumber} that - The fixed-point number to multiply with this number.
     * @return {FixedPointNumber} a FixedPointNumber whose value is the value of this FixedPointNumber multiplied by `that` FixedPointNumber.
     *
     * @remarks The precision is the greater of the precision of the two operands.
     *
     * @see [bignumber.js multipliedBy](https://mikemcl.github.io/bignumber.js/#times)
     */
    public times(that: FixedPointNumber): FixedPointNumber {
        if (this.isNaN() || that.isNaN()) return FixedPointNumber.NaN;
        if (this.isNegativeInfinite())
            return that.isNegative()
                ? FixedPointNumber.POSITIVE_INFINITY
                : FixedPointNumber.NEGATIVE_INFINITY;
        if (this.isPositiveInfinite())
            return that.isNegative()
                ? FixedPointNumber.NEGATIVE_INFINITY
                : FixedPointNumber.POSITIVE_INFINITY;
        const fd =
            this.fractionalDigits > that.fractionalDigits
                ? this.fractionalDigits
                : that.fractionalDigits; // Max common fractional decimals.
        return new FixedPointNumber(
            fd,
            FixedPointNumber.mul(
                this.dp(fd).scaledValue,
                that.dp(fd).scaledValue,
                fd
            )
        ).dp(this.fractionalDigits); // Minimize fractional decimals without precision loss.
    }

    /**
     * Converts the fixed-point number to its string representation.
     *
     * @param {string} [decimalSeparator='.'] - The character to use as the decimal separator in the string representation. Default is '.'.
     * @return {string} A string representation of the fixed-point number.
     */
    public toString(decimalSeparator = '.'): string {
        if (this.edgeFlag === 0) {
            const sign = this.scaledValue < 0n ? '-' : '';
            const digits =
                this.scaledValue < 0n
                    ? (-this.scaledValue).toString()
                    : this.scaledValue.toString();
            const padded = digits.padStart(Number(this.fractionalDigits), '0');
            const decimals =
                this.fractionalDigits > 0
                    ? padded.slice(Number(-this.fractionalDigits))
                    : '';
            const integers = padded.slice(0, padded.length - decimals.length);
            const integersShow = integers.length < 1 ? '0' : integers;
            const decimalsShow = FixedPointNumber.trimEnd(decimals);
            return (
                sign +
                integersShow +
                (decimalsShow.length > 0 ? decimalSeparator + decimalsShow : '')
            );
        }
        return this.edgeFlag.toString();
    }

    /**
     * Trims the specified trailing substring from the end of the input string recursively.
     *
     * @param {string} str - The input string to be trimmed.
     * @param {string} [sub='0'] - The substring to be removed from the end of the input string. Defaults to '0' if not provided.
     * @return {string} The trimmed string with the specified trailing substring removed.
     */
    private static trimEnd(str: string, sub: string = '0'): string {
        // Check if the input string ends with the trailing substring
        if (str.endsWith(sub)) {
            // Remove the trailing substring recursively.
            return FixedPointNumber.trimEnd(
                str.substring(0, str.length - sub.length),
                sub
            );
        }
        return str;
    }

    /**
     * Converts a string expression of a number into a scaled value.
     *
     * @param {string} exp - The string expression of the number to be converted.
     * @param {bigint} fd - The scale factor to be used for conversion.
     * @param {string} [decimalSeparator='.'] - The character used as the decimal separator in the string expression.
     * @return {bigint} - The converted scaled value as a bigint.
     */
    private static txtToSV(
        exp: string,
        fd: bigint,
        decimalSeparator = '.'
    ): bigint {
        const fc = exp.charAt(0); // First Character.
        let sign = 1n;
        if (fc === '-') {
            sign = -1n;
            exp = exp.substring(1);
        } else if (fc === '+') {
            exp = exp.substring(1);
        }
        const sf = FixedPointNumber.BASE ** fd; // Scale Factor.
        const di = exp.lastIndexOf(decimalSeparator); // Decimal Index.
        if (di < 0) {
            return sign * sf * BigInt(exp); // Signed Integer.
        }
        const ie = exp.substring(0, di); // Integer Expression.
        const fe = exp.substring(di + 1); // Fractional Expression.
        return (
            sign * sf * BigInt(ie) + // Integer part
            (sign * (sf * BigInt(fe))) / BigInt(10 ** fe.length) // Fractional part.
        );
    }
}

export { FixedPointNumber };

Выполнить команду


Для локальной разработки. Не используйте в интернете!