From 371102c12acee96176d5413a7fda5bdc83d86be5 Mon Sep 17 00:00:00 2001 From: Luca Nicola Debiasi <63785793+lucanicoladebiasi@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:41:08 +0100 Subject: [PATCH] 1421 complete the fixedpointnumber scale down algorithm (#1432) * feat: 1421 FixedPointNumber auto-scale implementation... * feat: 1421 FixedPointNumber auto-scale implementation... * feat: 1421 FixedPointNumber auto-scale implementation... * feat: 1421 FixedPointNumber auto-scale implementation... * feat: 1421 FixedPointNumber auto-scale implemented * feat: 1421 FixedPointNumber auto-scale implemented --- docs/diagrams/architecture/vcdm.md | 3 +- packages/core/src/vcdm/FixedPointNumber.ts | 237 ++++++++++++------ packages/core/src/vcdm/currency/VET.ts | 2 +- packages/core/src/vcdm/currency/VTHO.ts | 2 +- .../tests/vcdm/FixedPointNumber.unit.test.ts | 145 +++++------ 5 files changed, 244 insertions(+), 145 deletions(-) diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 7bd345f06..7be37c715 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -65,7 +65,8 @@ classDiagram +FixedPointNumber value } class FixedPointNumber { - +bigint sv + +bigint fractionalDigits + +bigint scaledValue +FixedPointNumber NaN$ +FixedPointNumber NEGATIVE_INFINITY$ +FixedPointNumber POSITIVE_INFINITY$ diff --git a/packages/core/src/vcdm/FixedPointNumber.ts b/packages/core/src/vcdm/FixedPointNumber.ts index b9e277115..24f06d31b 100644 --- a/packages/core/src/vcdm/FixedPointNumber.ts +++ b/packages/core/src/vcdm/FixedPointNumber.ts @@ -2,7 +2,15 @@ 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 { + /** + * Base of value notation. + */ + private static readonly BASE = 10n; + /** * The default number of decimal places to use for fixed-point math. * @@ -16,7 +24,7 @@ class FixedPointNumber implements VeChainDataModel { /** * Not a Number. * - * @remarks {@link fd} and {@link sv} not meaningful. + * @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) * @@ -26,7 +34,7 @@ class FixedPointNumber implements VeChainDataModel { /** * The negative Infinity value. * - * @remarks {@link fd} and {@link sv} not meaningful. + * @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) */ @@ -39,7 +47,7 @@ class FixedPointNumber implements VeChainDataModel { /** * The positive Infinite value. * - * @remarks {@link fd} and {@link sv} not meaningful. + * @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) */ @@ -73,19 +81,21 @@ class FixedPointNumber implements VeChainDataModel { /** * Edge Flag denotes the {@link NaN} or {@link NEGATIVE_INFINITY} or {@link POSITIVE_INFINITY} value. * - * @remarks If `ef` is not zero, {@link fd} and {@link sv} are not meaningful. + * @remarks If `ef` is not zero, {@link fractionalDigits} and {@link scaledValue} are not meaningful. */ - protected readonly ef: number; + protected readonly edgeFlag: number; /** * Fractional Digits or decimal places. + * + * @see [bignumber.js precision](https://mikemcl.github.io/bignumber.js/#sd) */ - protected readonly fd: bigint; + public readonly fractionalDigits: bigint; /** - * Scaled Value = value * 10 ^ {@link fd}. + * Scaled Value = value * 10 ^ {@link fractionalDigits}. */ - public readonly sv: bigint; + public readonly scaledValue: bigint; /** * Returns the integer part of this FixedPointNumber value. @@ -96,7 +106,10 @@ class FixedPointNumber implements VeChainDataModel { */ get bi(): bigint { if (this.isFinite()) { - return this.sv / 10n ** this.fd; + return ( + this.scaledValue / + FixedPointNumber.BASE ** this.fractionalDigits + ); } throw new InvalidOperation( 'FixedPointNumber.bi', @@ -122,7 +135,7 @@ class FixedPointNumber implements VeChainDataModel { if (this.isNegativeInfinite()) return Number.NEGATIVE_INFINITY; if (this.isPositiveInfinite()) return Number.POSITIVE_INFINITY; if (this.isZero()) return 0; - return Number(this.sv) * 10 ** -Number(this.fd); + return Number(this.scaledValue) * 10 ** -Number(this.fractionalDigits); } /** @@ -133,9 +146,9 @@ class FixedPointNumber implements VeChainDataModel { * @param {number} [ef=0] - Edge Flag. */ protected constructor(fd: bigint, sv: bigint, ef: number = 0) { - this.fd = fd; - this.ef = ef; - this.sv = sv; + this.fractionalDigits = fd; + this.edgeFlag = ef; + this.scaledValue = sv; } /** @@ -150,9 +163,9 @@ class FixedPointNumber implements VeChainDataModel { if (this.isNegativeInfinite()) return FixedPointNumber.POSITIVE_INFINITY; return new FixedPointNumber( - this.fd, - this.sv < 0n ? -this.sv : this.sv, - this.ef + this.fractionalDigits, + this.scaledValue < 0n ? -this.scaledValue : this.scaledValue, + this.edgeFlag ); } @@ -184,8 +197,8 @@ class FixedPointNumber implements VeChainDataModel { if (this.isPositiveInfinite()) return that.isPositiveInfinite() ? 0 : 1; if (that.isNegativeInfinite()) return 1; if (that.isPositiveInfinite()) return -1; - const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - const delta = this.dp(fd).sv - that.dp(fd).sv; + 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; } @@ -254,11 +267,15 @@ class FixedPointNumber implements VeChainDataModel { : this.isNegative() ? FixedPointNumber.NEGATIVE_INFINITY : FixedPointNumber.POSITIVE_INFINITY; - const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + const fd = this.maxFractionalDigits(that, this.fractionalDigits); // Max common fractional decimals. return new FixedPointNumber( fd, - FixedPointNumber.div(fd, this.dp(fd).sv, that.dp(fd).sv) - ); + FixedPointNumber.div( + fd, + this.dp(fd).scaledValue, + that.dp(fd).scaledValue + ) + ).dp(this.fractionalDigits); // Minimize fractional decimals without precision loss. } /** @@ -271,24 +288,40 @@ class FixedPointNumber implements VeChainDataModel { * @return {bigint} - The result of the division, adjusted by the given factor fd. */ private static div(fd: bigint, dividend: bigint, divisor: bigint): bigint { - return (10n ** fd * dividend) / divisor; + return (FixedPointNumber.BASE ** fd * dividend) / divisor; } /** - * Adjusts the precision of the floating-point number by the specified + * Adjust the precision of the floating-point number by the specified * number of decimal places. * - * @param {bigint | number} decimalPlaces - The number of decimal places to adjust to. + * @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 fp = BigInt(decimalPlaces); - const dd = fp - this.fd; // Fractional Decimals Difference. - if (dd < 0) { - return new FixedPointNumber(fp, this.sv / 10n ** -dd); - } else { - return new FixedPointNumber(fp, this.sv * 10n ** dd); + const dp = BigInt(decimalPlaces); + if (dp >= 0) { + let fd = this.fractionalDigits; + let sv = this.scaledValue; + if (dp > fd) { + 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}` } + ); } /** @@ -310,7 +343,7 @@ class FixedPointNumber implements VeChainDataModel { /** * Returns `true` if the value of this FixedPointNumber is greater than `that` FixedPointNumber`, otherwise returns `false`. * - * @param {FixedPointNumber} - that The FixedPointNumber to compare against. + * @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. @@ -378,11 +411,15 @@ class FixedPointNumber implements VeChainDataModel { : this.isNegative() ? FixedPointNumber.NEGATIVE_INFINITY : FixedPointNumber.POSITIVE_INFINITY; - const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + const fd = this.maxFractionalDigits(that, this.fractionalDigits); // Max common fractional decimals. return new FixedPointNumber( fd, - FixedPointNumber.idiv(fd, this.dp(fd).sv, that.dp(fd).sv) - ); + FixedPointNumber.idiv( + fd, + this.dp(fd).scaledValue, + that.dp(fd).scaledValue + ) + ).dp(this.fractionalDigits); // Minimize fractional decimals without precision loss. } /** @@ -394,7 +431,7 @@ class FixedPointNumber implements VeChainDataModel { * @return {bigint} - The scaled result of the integer division. */ private static idiv(fd: bigint, dividend: bigint, divisor: bigint): bigint { - return (dividend / divisor) * 10n ** fd; + return (dividend / divisor) * FixedPointNumber.BASE ** fd; } /** @@ -421,14 +458,14 @@ class FixedPointNumber implements VeChainDataModel { * @see [bignumber.js isFinite](https://mikemcl.github.io/bignumber.js/#isF) */ public isFinite(): boolean { - return this.ef === 0; + return this.edgeFlag === 0; } /** - * Return `true` if the value of this FixedPointNumber is {@link NEGATIVE_INFINITY} and {@link POSITIVE_INFINITY}, + * 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} and {@link POSITIVE_INFINITY}, + * @return true` if the value of this FixedPointNumber is {@link NEGATIVE_INFINITY} or {@link POSITIVE_INFINITY}, */ public isInfinite(): boolean { return this.isNegativeInfinite() || this.isPositiveInfinite(); @@ -444,7 +481,11 @@ class FixedPointNumber implements VeChainDataModel { */ public isInteger(): boolean { if (this.isFinite()) { - return this.sv % 10n ** this.fd === 0n; + return ( + this.scaledValue % + FixedPointNumber.BASE ** this.fractionalDigits === + 0n + ); } return false; } @@ -470,7 +511,7 @@ class FixedPointNumber implements VeChainDataModel { * @see [bignumber.js isNaN](https://mikemcl.github.io/bignumber.js/#isNaN) */ public isNaN(): boolean { - return Number.isNaN(this.ef); + return Number.isNaN(this.edgeFlag); } /** @@ -494,14 +535,17 @@ class FixedPointNumber implements VeChainDataModel { * @see [bignumber.js isNegative](https://mikemcl.github.io/bignumber.js/#isNeg) */ public isNegative(): boolean { - return (this.isFinite() && this.sv < 0n) || this.isNegativeInfinite(); + 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.ef === Number.NEGATIVE_INFINITY; + return this.edgeFlag === Number.NEGATIVE_INFINITY; } /** @@ -535,7 +579,10 @@ class FixedPointNumber implements VeChainDataModel { * @see [bignumber.js isPositive](https://mikemcl.github.io/bignumber.js/#isPos) */ public isPositive(): boolean { - return (this.isFinite() && this.sv >= 0n) || this.isPositiveInfinite(); + return ( + (this.isFinite() && this.scaledValue >= 0n) || + this.isPositiveInfinite() + ); } /** @@ -544,7 +591,7 @@ class FixedPointNumber implements VeChainDataModel { * @return `true` if this FixedPointNumber value is {@link POSITIVE_INFINITY}, otherwise returns `false`. */ public isPositiveInfinite(): boolean { - return this.ef === Number.POSITIVE_INFINITY; + return this.edgeFlag === Number.POSITIVE_INFINITY; } /** @@ -555,7 +602,7 @@ class FixedPointNumber implements VeChainDataModel { * [see bignumber.js isZero](https://mikemcl.github.io/bignumber.js/#isZ) */ public isZero(): boolean { - return this.isFinite() && this.sv === 0n; + return this.isFinite() && this.scaledValue === 0n; } /** @@ -591,6 +638,26 @@ class FixedPointNumber implements VeChainDataModel { 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, {@link FixedPointNumber.DEFAULT_FRACTIONAL_DECIMALS} by default. + * + * @return the greater fixed digits value among `this`, `that` and `minFixedDigits`. + */ + private maxFractionalDigits( + that: FixedPointNumber, + minFixedDigits: bigint = FixedPointNumber.DEFAULT_FRACTIONAL_DECIMALS + ): 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. * @@ -619,8 +686,11 @@ class FixedPointNumber implements VeChainDataModel { return that.isPositiveInfinite() ? FixedPointNumber.NaN : FixedPointNumber.POSITIVE_INFINITY; - const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - return new FixedPointNumber(fd, this.dp(fd).sv - that.dp(fd).sv); + 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. } /** @@ -644,13 +714,13 @@ class FixedPointNumber implements VeChainDataModel { 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.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - let modulo = this.abs().dp(fd).sv; - const divisor = that.abs().dp(fd).sv; + 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); + return new FixedPointNumber(fd, modulo).dp(this.fractionalDigits); // Minimize fractional decimals without precision loss. } /** @@ -667,7 +737,7 @@ class FixedPointNumber implements VeChainDataModel { multiplicator: bigint, fd: bigint ): bigint { - return (multiplicand * multiplicator) / 10n ** fd; + return (multiplicand * multiplicator) / FixedPointNumber.BASE ** fd; } /** @@ -681,7 +751,11 @@ class FixedPointNumber implements VeChainDataModel { return FixedPointNumber.POSITIVE_INFINITY; if (this.isPositiveInfinite()) return FixedPointNumber.NEGATIVE_INFINITY; - return new FixedPointNumber(this.fd, -this.sv, this.ef); + return new FixedPointNumber( + this.fractionalDigits, + -this.scaledValue, + this.edgeFlag + ); } /** @@ -759,8 +833,11 @@ class FixedPointNumber implements VeChainDataModel { return that.isNegativeInfinite() ? FixedPointNumber.NaN : FixedPointNumber.POSITIVE_INFINITY; - const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - return new FixedPointNumber(fd, this.dp(fd).sv + that.dp(fd).sv); + 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. } /** @@ -798,11 +875,15 @@ class FixedPointNumber implements VeChainDataModel { if (that.isNegativeInfinite()) return FixedPointNumber.ZERO; if (that.isPositiveInfinite()) return FixedPointNumber.POSITIVE_INFINITY; - const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + const fd = this.maxFractionalDigits(that, this.fractionalDigits); // Max common fractional decimals. return new FixedPointNumber( fd, - FixedPointNumber.pow(fd, this.dp(fd).sv, that.dp(fd).sv) - ); + FixedPointNumber.pow( + fd, + this.dp(fd).scaledValue, + that.dp(fd).scaledValue + ) + ).dp(this.fractionalDigits); // Minimize fractional decimals without precision loss. } /** @@ -814,7 +895,7 @@ class FixedPointNumber implements VeChainDataModel { * @return {bigint} The result of base raised to the power of exponent, scaled by the scale factor. */ private static pow(fd: bigint, base: bigint, exponent: bigint): bigint { - const sf = 10n ** fd; // Scale factor. + const sf = FixedPointNumber.BASE ** fd; // Scale factor. if (exponent < 0n) { return FixedPointNumber.pow( fd, @@ -848,7 +929,7 @@ class FixedPointNumber implements VeChainDataModel { if (value < 0n) { throw new RangeError(`Value must be positive`); } - const sf = fd * 10n; // Scale Factor. + const sf = fd * FixedPointNumber.BASE; // Scale Factor. let iteration = 0; let actualResult = value; let storedResult = 0n; @@ -881,8 +962,8 @@ class FixedPointNumber implements VeChainDataModel { return FixedPointNumber.POSITIVE_INFINITY; try { return new FixedPointNumber( - this.fd, - FixedPointNumber.sqr(this.sv, this.fd) + this.fractionalDigits, + FixedPointNumber.sqr(this.scaledValue, this.fractionalDigits) ); // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { @@ -918,11 +999,18 @@ class FixedPointNumber implements VeChainDataModel { return that.isNegative() ? FixedPointNumber.NEGATIVE_INFINITY : FixedPointNumber.POSITIVE_INFINITY; - const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + const fd = + this.fractionalDigits > that.fractionalDigits + ? this.fractionalDigits + : that.fractionalDigits; // Max common fractional decimals. return new FixedPointNumber( fd, - FixedPointNumber.mul(this.dp(fd).sv, that.dp(fd).sv, fd) - ); + FixedPointNumber.mul( + this.dp(fd).scaledValue, + that.dp(fd).scaledValue, + fd + ) + ).dp(this.fractionalDigits); // Minimize fractional decimals without precision loss. } /** @@ -932,12 +1020,17 @@ class FixedPointNumber implements VeChainDataModel { * @return {string} A string representation of the fixed-point number. */ public toString(decimalSeparator = '.'): string { - if (this.ef === 0) { - const sign = this.sv < 0n ? '-' : ''; + if (this.edgeFlag === 0) { + const sign = this.scaledValue < 0n ? '-' : ''; const digits = - this.sv < 0n ? (-this.sv).toString() : this.sv.toString(); - const padded = digits.padStart(Number(this.fd), '0'); - const decimals = this.fd > 0 ? padded.slice(Number(-this.fd)) : ''; + 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); @@ -947,7 +1040,7 @@ class FixedPointNumber implements VeChainDataModel { (decimalsShow.length > 0 ? decimalSeparator + decimalsShow : '') ); } - return this.ef.toString(); + return this.edgeFlag.toString(); } /** @@ -990,7 +1083,7 @@ class FixedPointNumber implements VeChainDataModel { } else if (fc === '+') { exp = exp.substring(1); } - const sf = 10n ** fd; // Scale Factor. + const sf = FixedPointNumber.BASE ** fd; // Scale Factor. const di = exp.lastIndexOf(decimalSeparator); // Decimal Index. if (di < 0) { return sign * sf * BigInt(exp); // Signed Integer. diff --git a/packages/core/src/vcdm/currency/VET.ts b/packages/core/src/vcdm/currency/VET.ts index cd7fe068d..b4541bad0 100644 --- a/packages/core/src/vcdm/currency/VET.ts +++ b/packages/core/src/vcdm/currency/VET.ts @@ -29,7 +29,7 @@ class VET extends Coin { * * @type {bigint} */ - public readonly wei: bigint = this.value.dp(VET.WEI_FD).sv; + public readonly wei: bigint = this.value.dp(VET.WEI_FD).scaledValue; /** * Create a new instance with the given `value`. diff --git a/packages/core/src/vcdm/currency/VTHO.ts b/packages/core/src/vcdm/currency/VTHO.ts index 5702f032d..462bd6620 100644 --- a/packages/core/src/vcdm/currency/VTHO.ts +++ b/packages/core/src/vcdm/currency/VTHO.ts @@ -30,7 +30,7 @@ class VTHO extends Coin { * * @type {bigint} */ - public readonly wei: bigint = this.value.dp(VTHO.WEI_FD).sv; + public readonly wei: bigint = this.value.dp(VTHO.WEI_FD).scaledValue; /** * Create a new instance with the given `value`. diff --git a/packages/core/tests/vcdm/FixedPointNumber.unit.test.ts b/packages/core/tests/vcdm/FixedPointNumber.unit.test.ts index 466ea061f..00f042fb7 100644 --- a/packages/core/tests/vcdm/FixedPointNumber.unit.test.ts +++ b/packages/core/tests/vcdm/FixedPointNumber.unit.test.ts @@ -582,17 +582,17 @@ describe('FixedPointNumber class tests', () => { test('scale down', () => { const fd = 5n; const n = 123.45; - expect(FixedPointNumber.of(n).dp(fd)).toEqual( - FixedPointNumber.of(n, fd) - ); + const expected = FixedPointNumber.of(n); + const actual = FixedPointNumber.of(n).dp(fd); + expect(actual.isEqual(expected)).toBe(true); }); test('scale up', () => { const fd = 25n; const n = 123.45; - expect(FixedPointNumber.of(n).dp(fd)).toEqual( - FixedPointNumber.of(n, fd) - ); + const expected = FixedPointNumber.of(n); + const actual = FixedPointNumber.of(n).dp(fd); + expect(actual.isEqual(expected)).toBe(true); }); }); @@ -672,10 +672,10 @@ describe('FixedPointNumber class tests', () => { }); test('n / NaN = NaN', () => { - const lr = 123.45; - const r = NaN; - const actual = FixedPointNumber.of(lr).div(FixedPointNumber.of(r)); - const expected = BigNumber(lr).div(BigNumber(r)); + const x = FixedPointNumber.of(123.45); + const y = FixedPointNumber.of(NaN); + const actual = x.div(y); + const expected = BigNumber(x.n).div(BigNumber(y.n)); expect(actual.toString()).toBe(expected.toString()); }); @@ -712,41 +712,46 @@ describe('FixedPointNumber class tests', () => { }); test('x / y = periodic', () => { - const lr = -1; - const r = 3; - const actual = FixedPointNumber.of(lr).div(FixedPointNumber.of(r)); - const expected = BigNumber(lr).div(BigNumber(r)); - expect(actual.toString()).toBe(expected.toString()); + const x = FixedPointNumber.of(-1); + const y = FixedPointNumber.of(3); + const actual = x.div(y); + const expected = BigNumber(x.n).div(BigNumber(y.n)); + const dp = -5; // BigNumber default precision diverges after 15 digits. + expect(actual.toString().slice(0, dp)).toBe( + expected.toString().slice(0, dp) + ); }); test('x / y = real', () => { - const lr = 355; - const r = 113; - const actual = FixedPointNumber.of(lr).div(FixedPointNumber.of(r)); - const expected = BigNumber(lr).div(BigNumber(r)); - const dp = 15; // BigNumber default precision diverges after 15 digits. - expect(actual.n.toFixed(dp)).toBe(expected.toNumber().toFixed(dp)); + const x = FixedPointNumber.of(355); + const y = FixedPointNumber.of(113); + const actual = x.div(y); + const expected = BigNumber(x.n).div(BigNumber(y.n)); + const dp = -5; // BigNumber default precision diverges after 15 digits. + expect(actual.toString().slice(0, dp)).toBe( + expected.toString().slice(0, dp) + ); }); test('x / y = integer', () => { - const lr = 355; - const r = -5; - const actual = FixedPointNumber.of(lr).div(FixedPointNumber.of(r)); - const expected = BigNumber(lr).div(BigNumber(r)); + const x = FixedPointNumber.of(355); + const y = FixedPointNumber.of(-5); + const actual = x.div(y); + const expected = BigNumber(x.n).div(BigNumber(y.n)); expect(actual.n).toBe(expected.toNumber()); }); test('x / 1 = x scale test', () => { - const l = 123.45; - const r = 1; - const actualUp = FixedPointNumber.of(l, 7n).div( - FixedPointNumber.of(r, 5n) + const x = 123.45; + const y = 1; + const actualUp = FixedPointNumber.of(x, 7n).div( + FixedPointNumber.of(y, 5n) ); - const actualDn = FixedPointNumber.of(l, 5n).div( - FixedPointNumber.of(r, 7n) + const actualDn = FixedPointNumber.of(x, 5n).div( + FixedPointNumber.of(y, 7n) ); expect(actualUp.isEqual(actualDn)).toBe(true); - expect(actualUp.isEqual(FixedPointNumber.of(l))).toBe(true); + expect(actualUp.isEqual(FixedPointNumber.of(x))).toBe(true); }); }); @@ -882,17 +887,17 @@ describe('FixedPointNumber class tests', () => { }); test('x / 1 = x scale test', () => { - const l = 123.45; - const r = 1; - const actualUp = FixedPointNumber.of(l, 7n).idiv( - FixedPointNumber.of(r, 5n) + const x = 123.45; + const y = 1; + const actualUp = FixedPointNumber.of(x, 7n).idiv( + FixedPointNumber.of(y, 5n) ); - const actualDn = FixedPointNumber.of(l, 5n).idiv( - FixedPointNumber.of(r, 7n) + const actualDn = FixedPointNumber.of(x, 5n).idiv( + FixedPointNumber.of(y, 7n) ); - const expected = BigNumber(l).idiv(BigNumber(r)); + const expected = BigNumber(x).idiv(BigNumber(y)); expect(actualUp.isEqual(actualDn)).toBe(true); - expect(actualDn.n).toBe(expected.toNumber()); + expect(actualDn.toString()).toBe(expected.toString()); }); }); @@ -1944,23 +1949,19 @@ describe('FixedPointNumber class tests', () => { }); test('l - r -> >0', () => { - const fd = 13; const l = 123.45; const r = 23.45678; const actual = FixedPointNumber.of(l).minus(FixedPointNumber.of(r)); const expected = BigNumber(l).minus(BigNumber(r)); - expect(actual.n.toFixed(fd)).toBe(expected.toNumber().toFixed(fd)); - expect(actual.n).toBe(l - r); + expect(actual.toString()).toBe(expected.toString()); }); test('l - r -> <0', () => { - const fd = 13; const l = 123.45; const r = -1234.5678; const actual = FixedPointNumber.of(l).minus(FixedPointNumber.of(r)); const expected = BigNumber(l).minus(BigNumber(r)); - expect(actual.n.toFixed(fd)).toBe(expected.toNumber().toFixed(fd)); - expect(actual.n).toBe(l - r); + expect(actual.toString()).toBe(expected.toString()); }); }); @@ -2071,11 +2072,7 @@ describe('FixedPointNumber class tests', () => { FixedPointNumber.of(r) ); const expected = BigNumber(l).modulo(BigNumber(r)); - expect(actual).toEqual(FixedPointNumber.of(expected.toNumber())); - expect( - FixedPointNumber.of(l).modulo(FixedPointNumber.of(-r)) - ).toEqual(actual); - expect(actual.isZero()).toBe(true); + expect(actual.toString()).toBe(expected.toString()); }); test('n % ±1 -> 0 - scale test', () => { @@ -2402,11 +2399,15 @@ describe('FixedPointNumber class tests', () => { test('±b ^ 0 = 1', () => { const b = 123.45; const e = 0; - const actual = FixedPointNumber.of(b).pow(FixedPointNumber.of(e)); - expect(actual).toEqual(FixedPointNumber.of(1)); - expect(FixedPointNumber.of(-b).pow(FixedPointNumber.of(e))).toEqual( - actual + const expected = FixedPointNumber.of(1); + const actualFromNegative = FixedPointNumber.of(-b).pow( + FixedPointNumber.of(e) + ); + const actualFromPositive = FixedPointNumber.of(b).pow( + FixedPointNumber.of(e) ); + expect(actualFromNegative.isEqual(expected)).toBe(true); + expect(actualFromPositive.isEqual(expected)).toBe(true); }); }); @@ -2568,25 +2569,29 @@ describe('FixedPointNumber class tests', () => { describe('toString methods tests', () => { test('< 1', () => { - const n = FixedPointNumber.of(0.0001); - console.log(n.toString()); - console.log(n); + const expected = 0.0001; + const actual = FixedPointNumber.of(expected); + expect(actual.toString()).toEqual(expected.toString()); }); test('> 1', () => { - const n = FixedPointNumber.of(123.456); - console.log(n.toString()); + const expected = 123.456; + const actual = FixedPointNumber.of(123.456); + expect(actual.toString()).toEqual(expected.toString()); }); test('NaN', () => { - const r = FixedPointNumber.of(Number.NaN); - console.log(r.toString()); - }); - test('Negative infinite', () => { - const r = FixedPointNumber.of(-Infinity); - console.log(r.toString()); - }); - test('Positive infinite', () => { - const r = FixedPointNumber.of(Infinity); - console.log(r.toString()); + const expected = Number.NaN; + const actual = FixedPointNumber.of(expected); + expect(actual.toString()).toEqual(expected.toString()); + }); + test('-Infinity', () => { + const expected = -Infinity; + const actual = FixedPointNumber.of(-Infinity); + expect(actual.toString()).toEqual(expected.toString()); + }); + test('+Infinity', () => { + const expected = -Infinity; + const actual = FixedPointNumber.of(expected); + expect(actual.toString()).toEqual(expected.toString()); }); }); });