From 804b9c70cb12fe7af8ccb193a2cfc04c2935ddb5 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Thu, 8 Aug 2024 10:38:04 +0100 Subject: [PATCH 01/48] feat: 1110 remove Quantity class and add HexInt class --- docs/diagrams/architecture/vcdm.md | 8 +- packages/core/src/utils/hex/_Hex.ts | 4 +- packages/core/src/vcdm/Hex.ts | 168 +++++++++++++----- packages/core/src/vcdm/HexInt.ts | 53 ++++++ packages/core/src/vcdm/Quantity.ts | 65 ------- packages/core/src/vcdm/ThorId.ts | 2 +- packages/core/src/vcdm/index.ts | 2 +- packages/core/tests/vcdm/Hex.unit.test.ts | 70 +++++++- .../vechain-provider/vechain-provider.ts | 4 +- .../utils/formatter/blocks/formatter.ts | 12 +- .../utils/formatter/logs/formatter.ts | 4 +- .../utils/formatter/transactions/formatter.ts | 22 +-- .../methods/eth_syncing/eth_syncing.ts | 4 +- .../tests/provider/providers/fixture.ts | 10 +- .../eth_blockNumber.testnet.test.ts | 4 +- .../methods/eth_getBalance/fixture.ts | 10 +- .../methods/eth_getBlockByNumber/fixture.ts | 10 +- .../methods/eth_getBlockReceipts/fixture.ts | 4 +- 18 files changed, 286 insertions(+), 170 deletions(-) create mode 100644 packages/core/src/vcdm/HexInt.ts delete mode 100644 packages/core/src/vcdm/Quantity.ts diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 90e5c5fa6..531b6d57a 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -18,11 +18,11 @@ classDiagram +Hex of(bigint|number|string|Uint8Array exp)$ +Hex random(number bytes)$ } + class HexInt { + +HexInt of(bigint|number|string|Uint8Array|Hex exp)$ + } class Keccak256 class Keystore - class Quantity { - +Quantity of(bigint|number|string|Uint8Array|Hex exp)$ - } class Revision class Sha256 class String @@ -50,8 +50,8 @@ classDiagram Hex <|-- BloomFilter Hex <|-- HDNode Hex <|-- Hash + Hex <|-- HexInt Hex <|-- Keystore - Hex <|-- Quantity Hex <|-- Revision Hex <|-- ThorId String <|-- Hex diff --git a/packages/core/src/utils/hex/_Hex.ts b/packages/core/src/utils/hex/_Hex.ts index 01a8789e4..4bd1dff89 100644 --- a/packages/core/src/utils/hex/_Hex.ts +++ b/packages/core/src/utils/hex/_Hex.ts @@ -1,6 +1,6 @@ import * as n_utils from '@noble/curves/abstract/utils'; import { InvalidDataType } from '@vechain/sdk-errors'; -import { Quantity } from '../../vcdm'; +import { HexInt } from '../../vcdm'; import { randomBytes } from '@noble/hashes/utils'; import { type HexString } from './types'; @@ -436,7 +436,7 @@ const _Quantity = { */ of(n: HexRepresentable): string { // return `${PREFIX}${trim(_Hex.of(n))}`; - return Quantity.of(n).toString(); + return HexInt.of(n).toString(); } }; diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index 662d7f21f..e9ef46445 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -4,10 +4,31 @@ import { InvalidCastType, InvalidDataType } from '@vechain/sdk-errors'; import { type VeChainDataModel } from './VeChainDataModel'; /** - * Represents a hexadecimal value. + * Represents a hexadecimal value expressed as + * * `-` sign if the value is negative, + * * `0x` hexadecimal notation tag, + * * a not empty string of hexadecimal digits from `0` to `9` and from `a` to `f`. * @implements {VeChainDataModel} + * @description This hexadecimal notation is coherent with the decimal notation: + * * the sign is only expressed for negative values, and it is always the first symbol, + * * the `0x` tags the string as an hexaecimal expression, + * * hexadecimal digits follow. */ class Hex extends String implements VeChainDataModel { + /** + * Negative multiplier of the {@link hex} absolute value. + * + * @type {number} + */ + protected static readonly NEGATIVE: number = -1; + + /** + * Positive multiplier of the {@link hex} absolute value. + * + * @type {number} + */ + protected static readonly POSITIVE: number = 1; + /** * The radix used for representing numbers base 16 in a positional numeral notation system. * @@ -15,39 +36,49 @@ class Hex extends String implements VeChainDataModel { */ protected static readonly RADIX: number = 16; - private static readonly REGEX_HEX: RegExp = /^(0x)?[0-9a-f]*$/i; + /** + * Regular expression for matching hexadecimal strings. + * + * @type {RegExp} + */ + private static readonly REGEX_HEX: RegExp = /^-?(0x)?[0-9a-f]*$/i; - protected static readonly REGEX_PREFIX: RegExp = /^0x/i; + /** + * Regular expression pattern to match a prefix indicating hexadecimal number. + * + * @type {RegExp} + */ + protected static readonly REGEX_PREFIX: RegExp = /^-?0x/i; + /** + * Returns the hexadecimal digits expressing this absolute value, sign and `0x` prefix omitted. + */ public readonly hex: string; /** - * Creates a new instance of this class representing the `exp` hexadecimal expression. - * - * @param {string} exp - The hexadecimal expression. - * @param {function} normalize - A function used to normalize the hexadecimal expression. Defaults to converting it to lowercase. + * Represents the sign multiplier of a given number: + * * {@link NEGATIVE} `-1` if negative, + * * {@link POSITIVE} `1` if positive. + */ + public readonly sign; + + /** + * Creates a new instance of this class to represent the value + * built multiplying `sign` for the absolute value expressed by the hexadecimal `digits`. * - * @throws {InvalidDataType} - Thrown when the exp parameter is not a valid hexadecimal expression. + * @param {number} sign - The sign of the value. + * @param {string} digits - The digits of the absolute value in hexadecimal base. + * @param {function} [normalize] - The function used to normalize the digits. Defaults to converting digits to lowercase. */ protected constructor( - exp: string, - normalize: (exp: string) => string = (exp) => exp.toLowerCase() + sign: number, + digits: string, + normalize: (digits: string) => string = (digits) => digits.toLowerCase() ) { - let value = exp; - if (Hex.REGEX_PREFIX.test(value)) { - value = value.slice(2); - } - if (Hex.isValid(value)) { - value = normalize(value); - super('0x' + value); - this.hex = value; - } else { - throw new InvalidDataType( - 'Hex.constructor', - 'not an hexadecimal expression', - { value } - ); - } + const normalizedDigits = normalize(digits); + super((sign < 0 ? '-0x' : '0x') + normalizedDigits); + this.hex = normalizedDigits; + this.sign = sign; } /** @@ -56,7 +87,7 @@ class Hex extends String implements VeChainDataModel { * @returns {bigint} The value of `bi` as a `BigInt`. */ get bi(): bigint { - return nc_utils.hexToNumber(this.hex); + return BigInt(this.sign) * nc_utils.hexToNumber(this.hex); } /** @@ -72,11 +103,13 @@ class Hex extends String implements VeChainDataModel { * Retrieves the value of n. * * @return {number} The value of n. + * * @throws {InvalidCastType} Throws an error if this instance doesn't represent * an [IEEE 754 double precision 64 bits floating point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format). */ get n(): number { if (this.isNumber()) { + // The sign is part of the IEEE 754 representation hence no need to consider `this.sign` property. return new DataView(this.bytes.buffer).getFloat64(0); } throw new InvalidCastType( @@ -92,27 +125,33 @@ class Hex extends String implements VeChainDataModel { * @returns {Hex} - The aligned hexadecimal string. */ public alignToBytes(): Hex { - return this.hex.length % 2 === 0 ? this : new Hex('0' + this.hex); + return this.hex.length % 2 === 0 + ? this + : new Hex(this.sign, '0' + this.hex); } /** * Compares the current Hex object with another Hex object. * * @param {Hex} that - The Hex object to compare with. + * * @return {number} - Returns a negative number if the current Hex object is less than the given Hex object, * zero if they are equal, or a positive number if the current Hex object is greater than the given Hex object. */ compareTo(that: Hex): number { - const digits = Math.max(this.hex.length, that.hex.length); - const thisBytes = this.fit(digits).bytes; - const thatBytes = that.fit(digits).bytes; - let i = 0; - let compareByte = 0; - while (compareByte === 0 && i < thisBytes.length) { - compareByte = thisBytes[i] - thatBytes[i]; - i++; + if (this.sign === that.sign) { + const digits = Math.max(this.hex.length, that.hex.length); + const thisBytes = this.fit(digits).bytes; + const thatBytes = that.fit(digits).bytes; + let i = 0; + let compareByte = 0; + while (compareByte === 0 && i < thisBytes.length) { + compareByte = thisBytes[i] - thatBytes[i]; + i++; + } + return compareByte; } - return compareByte; + return this.sign - that.sign; } /** @@ -132,7 +171,7 @@ class Hex extends String implements VeChainDataModel { cue++; } if (this.hex.length - cue === digits) { - return new Hex(this.hex.slice(cue)); + return new Hex(this.sign, this.hex.slice(cue)); } throw new InvalidDataType( 'Hex.fit', @@ -142,7 +181,10 @@ class Hex extends String implements VeChainDataModel { } if (digits > this.hex.length) { // Pad. - return new Hex('0'.repeat(digits - this.hex.length) + this.hex); + return new Hex( + this.sign, + '0'.repeat(digits - this.hex.length) + this.hex + ); } return this; } @@ -195,29 +237,61 @@ class Hex extends String implements VeChainDataModel { * Create a Hex instance from a bigint, number, string, or Uint8Array. * * @param {bigint | number | string | Uint8Array} exp - The input value to convert to a Hex instance: - * * bigint encoded as hexadecimal expression of the bytes representing its absolute value; - * * number encoded as [IEEE 754 double precision 64 bits floating point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format); - * * string parsed as a hexadecimal expression, optionally prefixed with `0x`; - * * Uint8Array encoded as hexadecimal expression of the bytes represented in the provided expression; + * * bigint, converted to a signed hexadecimal expression of its absolute value; + * * number, encoded as [IEEE 754 double precision 64 bits floating point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format); + * * string, parsed as a hexadecimal expression, optionally signed `-`, optionally tagged with `0x`; + * * Uint8Array, encoded as hexadecimal expression of the bytes represented in the provided expression; + * * @returns {Hex} - A Hex instance representing the input value. + * + * @throws {InvalidDataType} if the given `exp` can't be represented as a hexadecimal expression. */ public static of(exp: bigint | number | string | Uint8Array): Hex { try { if (exp instanceof Uint8Array) { - return new Hex(nc_utils.bytesToHex(exp)); + return new Hex(this.POSITIVE, nc_utils.bytesToHex(exp)); } else if (typeof exp === 'bigint') { - return new Hex(nc_utils.numberToHexUnpadded(exp)); + if (exp < 0n) { + return new Hex( + this.NEGATIVE, + nc_utils.numberToHexUnpadded(-1n * exp) + ); + } + return new Hex( + this.POSITIVE, + nc_utils.numberToHexUnpadded(exp) + ); } else if (typeof exp === 'number') { const dataView = new DataView(new ArrayBuffer(16)); dataView.setFloat64(0, exp); - return Hex.of(new Uint8Array(dataView.buffer)); + return new Hex( + exp < 0 ? this.NEGATIVE : this.POSITIVE, + nc_utils.bytesToHex(new Uint8Array(dataView.buffer)) + ); + } + if (this.isValid(exp)) { + if (exp.startsWith('-')) { + return new Hex( + this.NEGATIVE, + this.REGEX_PREFIX.test(exp) + ? exp.slice(3) + : exp.slice(1) + ); + } + return new Hex( + this.POSITIVE, + this.REGEX_PREFIX.test(exp) ? exp.slice(2) : exp + ); } - return new Hex(exp); + throw new InvalidDataType('Hex.of', 'not an hexadecimal string', { + exp + }); } catch (e) { throw new InvalidDataType( 'Hex.of', 'not an hexadecimal expression', - { exp }, + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + { exp: `${exp}` }, // Needed to serialize bigint values. e ); } diff --git a/packages/core/src/vcdm/HexInt.ts b/packages/core/src/vcdm/HexInt.ts new file mode 100644 index 000000000..ce835dd2c --- /dev/null +++ b/packages/core/src/vcdm/HexInt.ts @@ -0,0 +1,53 @@ +import { Hex } from './Hex'; +import { InvalidDataType } from '@vechain/sdk-errors'; + +class HexInt extends Hex { + protected constructor(hex: Hex) { + let cue = 0; + while (cue < hex.hex.length && hex.hex.at(cue) === '0') { + cue++; + } + super( + hex.sign, + cue === hex.hex.length ? '0' : hex.hex.slice(cue, hex.hex.length) + ); + } + + public override get n(): number { + const bi = this.bi; + if (Number.MIN_SAFE_INTEGER <= bi && bi <= Number.MAX_VALUE) { + return Number(bi); + } + throw new InvalidDataType('HexInt.of', 'not in the safe number range', { + bi: `${bi}`, + hex: this.toString() + }); + } + + public static of(exp: bigint | number | string | Uint8Array | Hex): HexInt { + try { + if (exp instanceof Hex) { + return new HexInt(exp); + } + if (typeof exp === 'number') { + if (Number.isInteger(exp)) { + return new HexInt(Hex.of(BigInt(exp))); + } + throw new InvalidDataType('HexInt.of', 'not an integer', { + exp + }); + } + return new HexInt(Hex.of(exp)); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + throw new InvalidDataType( + 'HexInt.of', + 'not an hexadecimal integer expression', + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + { exp: `${exp}`, e } // Needed to serialize bigint values. + ); + } + } +} + +export { HexInt }; diff --git a/packages/core/src/vcdm/Quantity.ts b/packages/core/src/vcdm/Quantity.ts deleted file mode 100644 index 987bf9cc9..000000000 --- a/packages/core/src/vcdm/Quantity.ts +++ /dev/null @@ -1,65 +0,0 @@ -import * as nc_utils from '@noble/curves/abstract/utils'; -import { Hex } from './Hex'; -import { InvalidDataType } from '@vechain/sdk-errors'; - -/** - * A class representing a quantity in hexadecimal format. - * @extends Hex - * @experimental - */ -class Quantity extends Hex { - /** - * Constructs a new Quantity object with the given hexadecimal expression. - * - * @param {string} exp - The hexadecimal expression to use. - * @protected - */ - protected constructor(exp: string) { - let value = exp; - if (Hex.REGEX_PREFIX.test(value)) { - value = value.slice(2); - } - if (Hex.isValid(value)) { - let cue = 0; - while (cue < value.length && value.at(cue) === '0') { - cue++; - } - if (cue === value.length) { - super('0'); - } else { - super(value.slice(cue)); - } - } else { - throw new InvalidDataType( - 'Quantity.constructor', - 'not an hexadecimal expression', - { value } - ); - } - } - - /** - * Creates a new Quantity object from the given input. - * - * @param {bigint | number | string | Hex | Uint8Array} exp - The input value to create Quantity from. - * @returns {Quantity} A new Quantity object. - * @experimental - */ - public static of( - exp: bigint | number | string | Hex | Uint8Array - ): Quantity { - if (exp instanceof Hex) { - return new Quantity(exp.hex); - } - if (exp instanceof Uint8Array) { - return new Quantity(nc_utils.bytesToHex(exp)); - } else if (typeof exp === 'bigint') { - return new Quantity(nc_utils.numberToHexUnpadded(exp)); - } else if (typeof exp === 'number') { - return new Quantity(nc_utils.numberToHexUnpadded(exp)); - } - return new Hex(exp); - } -} - -export { Quantity }; diff --git a/packages/core/src/vcdm/ThorId.ts b/packages/core/src/vcdm/ThorId.ts index 61a5f33cd..a7c919761 100644 --- a/packages/core/src/vcdm/ThorId.ts +++ b/packages/core/src/vcdm/ThorId.ts @@ -23,7 +23,7 @@ class ThorId extends Hex { */ protected constructor(hex: Hex) { if (ThorId.isValid(hex.hex)) { - super(hex.hex); + super(Hex.POSITIVE, hex.hex); } else { throw new InvalidDataType( 'ThorId.constructor', diff --git a/packages/core/src/vcdm/index.ts b/packages/core/src/vcdm/index.ts index 9b0f79121..87b10bf02 100644 --- a/packages/core/src/vcdm/index.ts +++ b/packages/core/src/vcdm/index.ts @@ -1,5 +1,5 @@ export * from './Hex'; -export * from './Quantity'; +export * from './HexInt'; export * from './ThorId'; export * from './Txt'; export * from './VeChainDataModel'; diff --git a/packages/core/tests/vcdm/Hex.unit.test.ts b/packages/core/tests/vcdm/Hex.unit.test.ts index 55bd5c2a9..858761adf 100644 --- a/packages/core/tests/vcdm/Hex.unit.test.ts +++ b/packages/core/tests/vcdm/Hex.unit.test.ts @@ -8,8 +8,14 @@ import { InvalidCastType, InvalidDataType } from '@vechain/sdk-errors'; */ describe('Hex class tests', () => { describe('VeChain Data Model tests', () => { - test('Return a bi value from a bi expression', () => { - const exp = BigInt(12357); + test('Return a bi value from a negative bi expression', () => { + const exp = -12357n; // From literal. + const hex = Hex.of(exp); + expect(hex.bi).toEqual(exp); + }); + + test('Return a bi value from a positive bi expression', () => { + const exp = BigInt(12357); // From constructor. const hex = Hex.of(exp); expect(hex.bi).toEqual(exp); }); @@ -18,6 +24,7 @@ describe('Hex class tests', () => { const exp = Txt.of('👋🌐'); const hex = Hex.of(exp.bytes); expect(hex.bytes).toEqual(exp.bytes); + expect(hex.sign).toBeGreaterThan(0); expect(Txt.of(hex.bytes).isEqual(exp)).toBeTruthy(); }); @@ -39,7 +46,7 @@ describe('Hex class tests', () => { expect(() => hex.n).toThrow(InvalidCastType); }); - test('compareTo metthod tests', () => { + test('compareTo method tests - same signs', () => { const hex1 = Hex.of('0x123'); const hex2 = Hex.of('0x456'); expect(hex1.compareTo(hex2)).toBeLessThan(0); @@ -47,6 +54,13 @@ describe('Hex class tests', () => { expect(hex1.compareTo(hex1)).toEqual(0); }); + test('compareTo method tests - opposite signs', () => { + const hex1 = Hex.of('-0x123'); + const hex2 = Hex.of('0x123'); + expect(hex1.compareTo(hex2)).toBeLessThan(0); + expect(hex2.compareTo(hex1)).toBeGreaterThan(0); + }); + test('isEqual method tests', () => { const coffe = Hex.of('0xc0ffe'); const COFFE = Hex.of('C0FFE'); @@ -57,12 +71,18 @@ describe('Hex class tests', () => { }); describe('Construction tests', () => { - test('Return an Hex instance if the passed argument is negative bigint', () => {}); + test('Return an Hex instance if the passed argument is negative bigint', () => { + const exp = -12357n; + const hex = Hex.of(exp); + expect(hex).toBeInstanceOf(Hex); + expect(hex.bi).toEqual(exp); + }); test('Return an Hex instance if the passed argument is positive bigint', () => { const exp = BigInt(12357); const hex = Hex.of(exp); expect(hex).toBeInstanceOf(Hex); + expect(hex.bi).toEqual(exp); }); test('Return an Hex instance if the passed argument is negative number', () => { @@ -93,6 +113,20 @@ describe('Hex class tests', () => { expect(hex.hex).toEqual(exp.toLowerCase()); // Normalized from is lower case. }); + test('Return an Hex instance if the passed argument is string - negative value with 0x prefix', () => { + const exp = '-0xc0fFeE'; + const hex = Hex.of(exp); + expect(hex).toBeInstanceOf(Hex); + expect(hex.toString()).toEqual(exp.toLowerCase()); // Normalized from is lower case. + }); + + test('Return an Hex instance if the passed argument is string - negative value without 0x prefix', () => { + const exp = '-C0c0a'; + const hex = Hex.of(exp); + expect(hex).toBeInstanceOf(Hex); + expect('-' + hex.hex).toEqual(exp.toLowerCase()); // Normalized from is lower case. + }); + test('Return an Hex instance if the passed argument is UInt8Array', () => { const exp = Uint8Array.of(0xc0, 0xff, 0xee); const hex = Hex.of(exp); @@ -169,12 +203,22 @@ describe('Hex class tests', () => { expect(Hex.isValid(exp)).toBeFalsy(); }); - test('Return true for valid hex with 0x prefix', () => { + test('Return true for valid negative hex with 0x prefix', () => { + const exp = '-0xBadBabe'; + expect(Hex.isValid(exp)).toBeTruthy(); + }); + + test('Return true for valid negative hex without 0x prefix', () => { + const exp = '-BadBabe'; + expect(Hex.isValid(exp)).toBeTruthy(); + }); + + test('Return true for valid positive hex with 0x prefix', () => { const exp = '0xBadBabe'; expect(Hex.isValid(exp)).toBeTruthy(); }); - test('Return true for valid hex without 0x prefix', () => { + test('Return true for valid positive hex without 0x prefix', () => { const exp = 'BadBabe'; expect(Hex.isValid(exp)).toBeTruthy(); }); @@ -186,12 +230,22 @@ describe('Hex class tests', () => { expect(Hex.isValid0x(exp)).toBeFalsy(); }); - test('Return false for valid hex without 0x prefix', () => { + test('Return false for valid negative hex without 0x prefix', () => { + const exp = '-BadBabe'; + expect(Hex.isValid0x(exp)).toBeFalsy(); + }); + + test('Return true for valid positive hex with 0x prefix', () => { + const exp = '-0xBadBabe'; + expect(Hex.isValid0x(exp)).toBeTruthy(); + }); + + test('Return false for valid positive hex without 0x prefix', () => { const exp = 'BadBabe'; expect(Hex.isValid0x(exp)).toBeFalsy(); }); - test('Return true for valid hex with 0x prefix', () => { + test('Return true for valid positive hex with 0x prefix', () => { const exp = '0xBadBabe'; expect(Hex.isValid0x(exp)).toBeTruthy(); }); diff --git a/packages/network/src/provider/providers/vechain-provider/vechain-provider.ts b/packages/network/src/provider/providers/vechain-provider/vechain-provider.ts index ba0f596ce..5fce1f572 100644 --- a/packages/network/src/provider/providers/vechain-provider/vechain-provider.ts +++ b/packages/network/src/provider/providers/vechain-provider/vechain-provider.ts @@ -18,7 +18,7 @@ import { type SubscriptionEvent, type SubscriptionManager } from './types'; -import { Quantity } from '@vechain/sdk-core'; +import { HexInt } from '@vechain/sdk-core'; import { type EventPoll, Poll, vnsUtils } from '../../../utils'; import { type CompressedBlockDetail, @@ -220,7 +220,7 @@ class VeChainProvider extends EventEmitter implements EIP1193ProviderMessage { const promises = Array.from( this.subscriptionManager.logSubscriptions.entries() ).map(async ([subscriptionId, subscriptionDetails]) => { - const currentBlock = Quantity.of( + const currentBlock = HexInt.of( this.subscriptionManager.currentBlockNumber ).toString(); // Construct filter options for the Ethereum logs query based on the subscription details diff --git a/packages/network/src/provider/utils/formatter/blocks/formatter.ts b/packages/network/src/provider/utils/formatter/blocks/formatter.ts index a79193721..22d15126a 100644 --- a/packages/network/src/provider/utils/formatter/blocks/formatter.ts +++ b/packages/network/src/provider/utils/formatter/blocks/formatter.ts @@ -1,5 +1,5 @@ import { type BlocksRPC } from './types'; -import { Quantity, Hex, ZERO_BYTES } from '@vechain/sdk-core'; +import { Hex, HexInt, ZERO_BYTES } from '@vechain/sdk-core'; import { transactionsFormatter } from '../transactions'; import { type CompressedBlockDetail, @@ -35,14 +35,14 @@ const formatToRPCStandard = ( // Supported fields converted to RPC standard hash: block.id, parentHash: block.parentID, - number: Quantity.of(block.number).toString(), - size: Quantity.of(block.size).toString(), + number: HexInt.of(block.number).toString(), + size: HexInt.of(block.size).toString(), stateRoot: block.stateRoot, receiptsRoot: block.receiptsRoot, transactionsRoot: block.txsRoot, - timestamp: Quantity.of(block.timestamp).toString(), - gasLimit: Quantity.of(block.gasLimit).toString(), - gasUsed: Quantity.of(block.gasUsed).toString(), + timestamp: HexInt.of(block.timestamp).toString(), + gasLimit: HexInt.of(block.gasLimit).toString(), + gasUsed: HexInt.of(block.gasUsed).toString(), transactions, miner: block.beneficiary, diff --git a/packages/network/src/provider/utils/formatter/logs/formatter.ts b/packages/network/src/provider/utils/formatter/logs/formatter.ts index 2ee96517e..f40843956 100644 --- a/packages/network/src/provider/utils/formatter/logs/formatter.ts +++ b/packages/network/src/provider/utils/formatter/logs/formatter.ts @@ -1,4 +1,4 @@ -import { Quantity } from '@vechain/sdk-core'; +import { HexInt } from '@vechain/sdk-core'; import { type EventCriteria, type EventLogs } from '../../../../thor-client'; import { type LogsRPC } from './types'; @@ -14,7 +14,7 @@ const formatToLogsRPC = (eventLogs: EventLogs[]): LogsRPC[] => { return { address: eventLog.address, blockHash: eventLog.meta.blockID, - blockNumber: Quantity.of(eventLog.meta.blockNumber).toString(), + blockNumber: HexInt.of(eventLog.meta.blockNumber).toString(), data: eventLog.data, logIndex: '0x0', // Always false for now diff --git a/packages/network/src/provider/utils/formatter/transactions/formatter.ts b/packages/network/src/provider/utils/formatter/transactions/formatter.ts index 52d9f31e7..549489d12 100644 --- a/packages/network/src/provider/utils/formatter/transactions/formatter.ts +++ b/packages/network/src/provider/utils/formatter/transactions/formatter.ts @@ -3,7 +3,7 @@ import { type TransactionReceiptRPC, type TransactionRPC } from './types'; -import { Hex, Quantity, ZERO_BYTES } from '@vechain/sdk-core'; +import { Hex, HexInt, ZERO_BYTES } from '@vechain/sdk-core'; import { getNumberOfLogsAheadOfTransactionIntoBlockExpanded, getTransactionIndexIntoBlock @@ -37,13 +37,13 @@ const _formatTransactionToRPC = ( return { // Supported fields blockHash, - blockNumber: Quantity.of(blockNumber).toString(), + blockNumber: HexInt.of(blockNumber).toString(), from: tx.origin, - gas: Quantity.of(tx.gas).toString(), + gas: HexInt.of(tx.gas).toString(), chainId, hash: tx.id, nonce: tx.nonce as string, - transactionIndex: Quantity.of(txIndex).toString(), + transactionIndex: HexInt.of(txIndex).toString(), /** * `input`, `to`, `value` are being referred to the first clause. @@ -54,7 +54,7 @@ const _formatTransactionToRPC = ( to: tx.clauses[0]?.to !== undefined ? tx.clauses[0].to : null, value: tx.clauses[0]?.value !== undefined - ? Quantity.of(tx.clauses[0].value).toString() + ? HexInt.of(tx.clauses[0].value).toString() : '', // Unsupported fields @@ -156,14 +156,14 @@ function formatTransactionReceiptToRPCStandard( output.events.forEach((event) => { logs.push({ blockHash: receipt.meta.blockID, - blockNumber: Quantity.of(receipt.meta.blockNumber).toString(), + blockNumber: HexInt.of(receipt.meta.blockNumber).toString(), transactionHash: receipt.meta.txID as string, address: event.address, topics: event.topics.map((topic) => topic), data: event.data, removed: false, - transactionIndex: Quantity.of(transactionIndex).toString(), - logIndex: Quantity.of(logIndex).toString() + transactionIndex: HexInt.of(transactionIndex).toString(), + logIndex: HexInt.of(logIndex).toString() }); logIndex++; }); @@ -171,18 +171,18 @@ function formatTransactionReceiptToRPCStandard( return { blockHash: receipt.meta.blockID, - blockNumber: Quantity.of(receipt.meta.blockNumber).toString(), + blockNumber: HexInt.of(receipt.meta.blockNumber).toString(), contractAddress: receipt.outputs.length > 0 ? receipt.outputs[0].contractAddress : null, from: transaction.origin, - gasUsed: Quantity.of(receipt.gasUsed).toString(), + gasUsed: HexInt.of(receipt.gasUsed).toString(), logs, status: receipt.reverted ? '0x0' : '0x1', to: transaction.clauses[0].to, transactionHash: receipt.meta.txID as string, - transactionIndex: Quantity.of(transactionIndex).toString(), + transactionIndex: HexInt.of(transactionIndex).toString(), // Incompatible fields logsBloom: Hex.of(ZERO_BYTES(256)).toString(), diff --git a/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_syncing/eth_syncing.ts b/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_syncing/eth_syncing.ts index 2e53e10d4..0e224bdb8 100644 --- a/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_syncing/eth_syncing.ts +++ b/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_syncing/eth_syncing.ts @@ -1,5 +1,5 @@ import { JSONRPCInternalError, stringifyData } from '@vechain/sdk-errors'; -import { Quantity } from '@vechain/sdk-core'; +import { HexInt } from '@vechain/sdk-core'; import { type CompressedBlockDetail, type ThorClient @@ -40,7 +40,7 @@ const ethSyncing = async ( // Get the highest block number const highestBlockNumber: string | null = genesisBlock !== null - ? Quantity.of( + ? HexInt.of( Math.floor((Date.now() - genesisBlock.timestamp) / 10000) ).toString() : null; diff --git a/packages/network/tests/provider/providers/fixture.ts b/packages/network/tests/provider/providers/fixture.ts index aee36f219..1ba5d0c49 100644 --- a/packages/network/tests/provider/providers/fixture.ts +++ b/packages/network/tests/provider/providers/fixture.ts @@ -1,4 +1,4 @@ -import { Quantity, unitsUtils } from '@vechain/sdk-core'; +import { HexInt, unitsUtils } from '@vechain/sdk-core'; import { zeroBlock } from '../rpc-mapper/methods/eth_getBlockByNumber/fixture'; import { validTransactionDetailTestnet, @@ -18,7 +18,7 @@ const providerMethodsTestCasesTestnet = [ description: "Should be able to call eth_getBlockByNumber with '0x0' as the block number", method: 'eth_getBlockByNumber', - params: [Quantity.of(0).toString(), false], + params: [HexInt.of(0).toString(), false], expected: zeroBlock }, { @@ -44,7 +44,7 @@ const providerMethodsTestCasesSolo = [ 'Should be able to call eth_getBalance of an address with balance more than 0 VET', method: 'eth_getBalance', params: [THOR_SOLO_ACCOUNTS[0].address, 'latest'], - expected: Quantity.of(unitsUtils.parseVET('500000000')).toString() + expected: HexInt.of(unitsUtils.parseVET('500000000')).toString() }, { description: 'Should be able to call eth_getCode of a smart contract', @@ -59,8 +59,8 @@ const logsInput = { '0x0000000000000000000000000000456e65726779', '0x0000000000000000000000000000456e65726779' ], - fromBlock: Quantity.of(0).toString(), - toBlock: Quantity.of(100000).toString(), + fromBlock: HexInt.of(0).toString(), + toBlock: HexInt.of(100000).toString(), topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0x0000000000000000000000005034aa590125b64023a0262112b98d72e3c8e40e', diff --git a/packages/network/tests/provider/rpc-mapper/methods/eth_blockNumber/eth_blockNumber.testnet.test.ts b/packages/network/tests/provider/rpc-mapper/methods/eth_blockNumber/eth_blockNumber.testnet.test.ts index 926a48b77..8d68a3b1c 100644 --- a/packages/network/tests/provider/rpc-mapper/methods/eth_blockNumber/eth_blockNumber.testnet.test.ts +++ b/packages/network/tests/provider/rpc-mapper/methods/eth_blockNumber/eth_blockNumber.testnet.test.ts @@ -5,7 +5,7 @@ import { TESTNET_URL, ThorClient } from '../../../../../src'; -import { Quantity } from '@vechain/sdk-core'; +import { HexInt } from '@vechain/sdk-core'; /** * RPC Mapper integration tests for 'eth_blockNumber' method on Testnet Network @@ -50,7 +50,7 @@ describe('RPC Mapper - eth_blockNumber method tests', () => { expect(rpcCallUpdatedLatestBlockNumber).not.toBe('0x0'); expect(rpcCallUpdatedLatestBlockNumber).toBe( - Quantity.of(Number(rpcCallLatestBlockNumber) + 1).toString() + HexInt.of(Number(rpcCallLatestBlockNumber) + 1).toString() ); }, 20000); }); diff --git a/packages/network/tests/provider/rpc-mapper/methods/eth_getBalance/fixture.ts b/packages/network/tests/provider/rpc-mapper/methods/eth_getBalance/fixture.ts index 5c297172c..826e2643b 100644 --- a/packages/network/tests/provider/rpc-mapper/methods/eth_getBalance/fixture.ts +++ b/packages/network/tests/provider/rpc-mapper/methods/eth_getBalance/fixture.ts @@ -1,4 +1,4 @@ -import { Hex, Quantity, ZERO_BYTES, unitsUtils } from '@vechain/sdk-core'; +import { Hex, HexInt, ZERO_BYTES, unitsUtils } from '@vechain/sdk-core'; import { JSONRPCInternalError, JSONRPCInvalidParams @@ -12,19 +12,19 @@ const ethGetBalanceTestCases = [ { description: 'Should return correct balance of the test account', params: [THOR_SOLO_ACCOUNTS[0].address, 'latest'], - expected: Quantity.of(unitsUtils.parseVET('500000000')).toString() + expected: HexInt.of(unitsUtils.parseVET('500000000')).toString() }, { description: 'Should return correct balance of the test account', params: [THOR_SOLO_ACCOUNTS[0].address, 'best'], - expected: Quantity.of(unitsUtils.parseVET('500000000')).toString() + expected: HexInt.of(unitsUtils.parseVET('500000000')).toString() }, { description: 'Should return correct balance of the test account before seeding', params: [ THOR_SOLO_ACCOUNTS[0].address, - Quantity.of(0).toString() // 0 is the genesis block + HexInt.of(0).toString() // 0 is the genesis block ], expected: '0x0' // Expected balance is 0 }, @@ -34,7 +34,7 @@ const ethGetBalanceTestCases = [ params: [ // Zero address Hex.of(ZERO_BYTES(20)).toString(), - Quantity.of(1).toString() + HexInt.of(1).toString() ], expected: '0x0' }, diff --git a/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockByNumber/fixture.ts b/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockByNumber/fixture.ts index 261a3d84e..1aa2a31a9 100644 --- a/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockByNumber/fixture.ts +++ b/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockByNumber/fixture.ts @@ -1,4 +1,4 @@ -import { Quantity } from '@vechain/sdk-core'; +import { HexInt } from '@vechain/sdk-core'; import { blockWithTransactionsExpanded, blockWithTransactionsNotExpanded @@ -46,24 +46,24 @@ const ethGetBlockByNumberTestCases = [ // NOTE: hydrate true or false is the same, Because genesis block doesn't have any transactions on testnet { description: "Should get block by number '0x0'", - params: [Quantity.of(0).toString(), false], + params: [HexInt.of(0).toString(), false], expected: zeroBlock }, { description: "Should get block by number '0x0' with transaction details", - params: [Quantity.of(0).toString(), true], + params: [HexInt.of(0).toString(), true], expected: zeroBlock }, { description: 'Should get block which has transactions with details', - params: [Quantity.of(17529453).toString(), true], + params: [HexInt.of(17529453).toString(), true], expected: blockWithTransactionsExpanded }, { description: 'Should get block which has transactions with transaction NOT expanded', - params: [Quantity.of(17529453).toString(), false], + params: [HexInt.of(17529453).toString(), false], expected: blockWithTransactionsNotExpanded } ]; diff --git a/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockReceipts/fixture.ts b/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockReceipts/fixture.ts index a127c80f1..9efc39b4c 100644 --- a/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockReceipts/fixture.ts +++ b/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockReceipts/fixture.ts @@ -1,4 +1,4 @@ -import { Quantity } from '@vechain/sdk-core'; +import { HexInt } from '@vechain/sdk-core'; /** * Fixtures for the `eth_getBlockReceipts` RPC method. @@ -50,7 +50,7 @@ const blockReceiptsFixture = [ ] }, { - blockNumber: Quantity.of(17529453).toString(), + blockNumber: HexInt.of(17529453).toString(), expected: [ { blockHash: From 9424bc126d33d2c8daafb8a2ee8c7c13f63a4664 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Thu, 8 Aug 2024 15:59:25 +0100 Subject: [PATCH 02/48] feat: 1110 remove Quantity class and add HexInt class --- docs/diagrams/architecture/vcdm.md | 18 ++++-- packages/core/src/hash/blake2b256.ts | 3 +- packages/core/src/vcdm/Hex.ts | 4 +- packages/core/tests/bloom/bloom.unit.test.ts | 10 +-- .../core/tests/hash/blake2b256.unit.test.ts | 13 ++-- .../core/tests/keystore/keystore.unit.test.ts | 6 +- packages/core/tests/mnemonic/fixture.ts | 8 +-- .../core/tests/mnemonic/mnemonic.unit.test.ts | 4 +- packages/core/tests/rlp/helpers.unit.test.ts | 2 +- packages/core/tests/rlp/rlp.fixture.ts | 64 +++++++++---------- packages/core/tests/rlp/rlp.unit.test.ts | 32 +++++----- packages/core/tests/transaction/fixture.ts | 24 +++---- .../transaction-handler.unit.test.ts | 29 +++++---- .../transaction/transaction.unit.test.ts | 20 +++--- packages/core/tests/vcdm/Hex.unit.test.ts | 12 +++- 15 files changed, 133 insertions(+), 116 deletions(-) diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 531b6d57a..133d01f9a 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -21,6 +21,9 @@ classDiagram class HexInt { +HexInt of(bigint|number|string|Uint8Array|Hex exp)$ } + class HexUInt { + +HexUInt of(bigint|number|string|Uint8Array|HexInt exp)$ + } class Keccak256 class Keystore class Revision @@ -47,16 +50,17 @@ classDiagram Hash <|-- Blake2b256 Hash <|-- Keccak256 Hash <|-- Sha256 - Hex <|-- BloomFilter - Hex <|-- HDNode - Hex <|-- Hash Hex <|-- HexInt - Hex <|-- Keystore - Hex <|-- Revision - Hex <|-- ThorId + HexUInt <|-- Address + HexUInt <|-- BloomFilter + HexUInt <|-- HDNode + HexInt <|-- HexUInt + HexUInt <|-- Hash + HexUInt <|-- ThorId + HexUInt <|-- Keystore + HexUInt <|-- Revision String <|-- Hex String <|-- Txt - ThorId <|-- Address VeChainDataModel <|.. Hex VeChainDataModel <|.. Txt ``` diff --git a/packages/core/src/hash/blake2b256.ts b/packages/core/src/hash/blake2b256.ts index d0084e05f..54ea08af6 100644 --- a/packages/core/src/hash/blake2b256.ts +++ b/packages/core/src/hash/blake2b256.ts @@ -1,7 +1,6 @@ import { Hex } from '../vcdm/Hex'; import { Txt } from '../vcdm'; import { blake2b } from '@noble/hashes/blake2b'; -import { hexToBytes } from '@noble/hashes/utils'; import { type ReturnType } from './types'; import { InvalidDataType } from '@vechain/sdk-errors'; @@ -133,7 +132,7 @@ function blake2b256OfHex( } try { - const hash = blake2b256OfArray(hexToBytes(Hex.of(hex).hex)); + const hash = blake2b256OfArray(Hex.of(hex).bytes); return returnType === 'hex' ? Hex.of(hash).toString() : hash; } catch (e) { throw new InvalidDataType( diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index e9ef46445..db21887c8 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -11,8 +11,9 @@ import { type VeChainDataModel } from './VeChainDataModel'; * @implements {VeChainDataModel} * @description This hexadecimal notation is coherent with the decimal notation: * * the sign is only expressed for negative values, and it is always the first symbol, - * * the `0x` tags the string as an hexaecimal expression, + * * the `0x` tags the string as a hexadecimal expression, * * hexadecimal digits follow. + * * An empty content results is no digits. */ class Hex extends String implements VeChainDataModel { /** @@ -38,6 +39,7 @@ class Hex extends String implements VeChainDataModel { /** * Regular expression for matching hexadecimal strings. + * An empty input is represented as a empty digits. * * @type {RegExp} */ diff --git a/packages/core/tests/bloom/bloom.unit.test.ts b/packages/core/tests/bloom/bloom.unit.test.ts index 3ca5316e4..2eb2bd129 100644 --- a/packages/core/tests/bloom/bloom.unit.test.ts +++ b/packages/core/tests/bloom/bloom.unit.test.ts @@ -220,7 +220,7 @@ describe('Bloom Filter', () => { // Generate the bloom filter const filter = generator.generate(bitsPerKey, k); - expect(Hex.of(filter.bits).hex).toBe('96b298d634155950'); + expect(Hex.of(filter.bits).toString()).toBe('0x96b298d634155950'); expect(filter.bits.byteLength).toBe(8); // 64 bits expect(filter.k).toBe(k); @@ -248,7 +248,7 @@ describe('Bloom Filter', () => { expect(filter.bits.byteLength).toBe(8); expect(filter.k).toBe(k); - expect(Hex.of(filter.bits).hex).toBe('0000000000000000'); // 16 groups of 4 bits, all 0 -> 64 bits + expect(Hex.of(filter.bits).toString()).toBe('0x0000000000000000'); // 16 groups of 4 bits, all 0 -> 64 bits }); /** @@ -273,8 +273,8 @@ describe('Bloom Filter', () => { // Generate the bloom filter const filter = generator.generate(bitsPerKey, k); - expect(Hex.of(filter.bits).hex).toBe( - 'a4d641159d68d829345f86f40d50676cf042f6265072075a94' + expect(Hex.of(filter.bits).toString()).toBe( + '0xa4d641159d68d829345f86f40d50676cf042f6265072075a94' ); expect(filter.bits.byteLength).toBe(25); expect(filter.k).toBe(k); @@ -313,7 +313,7 @@ describe('Bloom Filter', () => { const filter = generator.generate(bitsPerKey, k); - expect(Hex.of(filter.bits).hex).toBe('1190199325088200'); + expect(Hex.of(filter.bits).toString()).toBe('0x1190199325088200'); // Validate the generated filter keys.forEach((key) => { diff --git a/packages/core/tests/hash/blake2b256.unit.test.ts b/packages/core/tests/hash/blake2b256.unit.test.ts index 7414b41fa..59f1f9a55 100644 --- a/packages/core/tests/hash/blake2b256.unit.test.ts +++ b/packages/core/tests/hash/blake2b256.unit.test.ts @@ -2,7 +2,6 @@ // import * as ThorDevKit from 'thor-devkit'; import { Txt, blake2b256, blake2b256OfHex, type ReturnType } from '../../src'; import { describe, expect, test } from '@jest/globals'; -import { hexToBytes } from '@noble/hashes/utils'; import { BLAKE2B256_CONTENT, BLAKE2B256_CONTENT_HASH, @@ -30,7 +29,7 @@ describe('blake2b256', () => { test('blake2b256 - valid content - string', () => { const content = Txt.of(BLAKE2B256_CONTENT).bytes; - const expected = hexToBytes(Hex.of(BLAKE2B256_CONTENT_HASH).hex); + const expected = Hex.of(BLAKE2B256_CONTENT_HASH).bytes; let actual: string | Uint8Array; actual = blake2b256(content); expect(actual).toEqual(expected); @@ -41,7 +40,7 @@ describe('blake2b256', () => { }); test('blake2b256 - valid content - Uint8Array', () => { - const expected = hexToBytes(Hex.of(BLAKE2B256_CONTENT_HASH).hex); + const expected = Hex.of(BLAKE2B256_CONTENT_HASH).bytes; let actual: string | Uint8Array; actual = blake2b256(BLAKE2B256_CONTENT); expect(actual).toEqual(expected); @@ -52,7 +51,7 @@ describe('blake2b256', () => { }); test('blake2b256 - valid no content - Uint8Array', () => { - const expected = hexToBytes(Hex.of(BLAKE2B256_NO_CONTENT_HASH).hex); + const expected = Hex.of(BLAKE2B256_NO_CONTENT_HASH).bytes; let actual: string | Uint8Array; actual = blake2b256(BLAKE2B256_NO_CONTENT); expect(actual).toEqual(expected); @@ -63,7 +62,7 @@ describe('blake2b256', () => { }); test('blake2b256 - valid zero content - string', () => { - const expected = hexToBytes(Hex.of(BLAKE2B256_NO_CONTENT_HASH).hex); + const expected = Hex.of(BLAKE2B256_NO_CONTENT_HASH).bytes; let actual: string | Uint8Array; actual = blake2b256(''); expect(actual).toEqual(expected); @@ -105,7 +104,7 @@ describe('blake2b256', () => { }); test('blake2b256OfHex - valid - content', () => { - const expected = hexToBytes(Hex.of(BLAKE2B256_CONTENT_HASH).hex); + const expected = Hex.of(BLAKE2B256_CONTENT_HASH).bytes; let actual = blake2b256OfHex(content); expect(actual).toEqual(expected); actual = blake2b256OfHex(content, 'hex'); @@ -113,7 +112,7 @@ describe('blake2b256', () => { }); test('blake2b256OfHex - valid - no content', () => { - const expected = hexToBytes(Hex.of(BLAKE2B256_NO_CONTENT_HASH).hex); + const expected = Hex.of(BLAKE2B256_NO_CONTENT_HASH).bytes; let actual = blake2b256OfHex(zeroContent); expect(actual).toEqual(expected); actual = blake2b256OfHex(zeroContent, 'hex'); diff --git a/packages/core/tests/keystore/keystore.unit.test.ts b/packages/core/tests/keystore/keystore.unit.test.ts index 4044b5e8e..31b252527 100644 --- a/packages/core/tests/keystore/keystore.unit.test.ts +++ b/packages/core/tests/keystore/keystore.unit.test.ts @@ -78,9 +78,9 @@ import { encryptionPassword ); - // Verify private key (slice(2) is used to remove 0x prefix) - expect(decryptedKeystore.privateKey.slice(2)).toEqual( - Hex.of(privateKey).hex + // Verify private key + expect(decryptedKeystore.privateKey).toEqual( + Hex.of(privateKey).toString() ); }); diff --git a/packages/core/tests/mnemonic/fixture.ts b/packages/core/tests/mnemonic/fixture.ts index af15f2582..7e297b190 100644 --- a/packages/core/tests/mnemonic/fixture.ts +++ b/packages/core/tests/mnemonic/fixture.ts @@ -27,28 +27,28 @@ const derivationPaths = [ testName: 'Derive private key', derivationPath: undefined, resultingPrivateKey: - '27196338e7d0b5e7bf1be1c0327c53a244a18ef0b102976980e341500f492425', + '0x27196338e7d0b5e7bf1be1c0327c53a244a18ef0b102976980e341500f492425', resultingAddress: '0x339Fb3C438606519E2C75bbf531fb43a0F449A70' }, { testName: 'Derive private key with standard derivation path', derivationPath: 'm/0', resultingPrivateKey: - '27196338e7d0b5e7bf1be1c0327c53a244a18ef0b102976980e341500f492425', + '0x27196338e7d0b5e7bf1be1c0327c53a244a18ef0b102976980e341500f492425', resultingAddress: '0x339Fb3C438606519E2C75bbf531fb43a0F449A70' }, { testName: 'Derive private key with custom derivation path', derivationPath: 'm/0/1', resultingPrivateKey: - 'fbbd4e92d4ee4ca2e985648599abb4e95b0886b4e0390b7bfc365283a7befc86', + '0xfbbd4e92d4ee4ca2e985648599abb4e95b0886b4e0390b7bfc365283a7befc86', resultingAddress: '0x43e60f60C89333121236226B7ADC884DC2a8847a' }, { testName: 'Derive private key with custom deep derivation path', derivationPath: 'm/0/1/4/2/4/3', resultingPrivateKey: - '66962cecff67bea483935c87fd33c6b6a524f06cc46430fa9591350bbd9f4999', + '0x66962cecff67bea483935c87fd33c6b6a524f06cc46430fa9591350bbd9f4999', resultingAddress: '0x0b41c56e19c5151122568873a039fEa090937Fe2' } ]; diff --git a/packages/core/tests/mnemonic/mnemonic.unit.test.ts b/packages/core/tests/mnemonic/mnemonic.unit.test.ts index 1f14b268c..9495bc511 100644 --- a/packages/core/tests/mnemonic/mnemonic.unit.test.ts +++ b/packages/core/tests/mnemonic/mnemonic.unit.test.ts @@ -70,7 +70,7 @@ describe('mnemonic', () => { words, path.derivationPath ) - ).hex + ).toString() ).toEqual(path.resultingPrivateKey); }); }); @@ -89,7 +89,7 @@ describe('mnemonic', () => { words, path.derivationPath ) - ).hex + ).toString() ).toEqual(path.resultingPrivateKey); }); }); diff --git a/packages/core/tests/rlp/helpers.unit.test.ts b/packages/core/tests/rlp/helpers.unit.test.ts index 51855689b..06734a33d 100644 --- a/packages/core/tests/rlp/helpers.unit.test.ts +++ b/packages/core/tests/rlp/helpers.unit.test.ts @@ -41,7 +41,7 @@ describe('encodeBigIntToBuffer', () => { test('encodeBigIntToBuffer', () => { const bi = 123456789012345678901n; // or any BigInt you want to test with const buffer = encodeBigIntToBuffer(bi, 9, 'encodeBigIntToBuffer'); - expect(Hex.of(buffer).hex).toBe('06b14e9f812f366c35'); + expect(Hex.of(buffer).toString()).toBe('0x06b14e9f812f366c35'); }); }); diff --git a/packages/core/tests/rlp/rlp.fixture.ts b/packages/core/tests/rlp/rlp.fixture.ts index 66b9658d0..e20937090 100644 --- a/packages/core/tests/rlp/rlp.fixture.ts +++ b/packages/core/tests/rlp/rlp.fixture.ts @@ -4,21 +4,21 @@ import { RLP_CODER, type RLPProfile, type RLPValidObject } from '../../src'; const encodeTestCases = [ { input: [1, 2, 3, [4, 5]], - expected: 'c6010203c20405', + expected: '0xc6010203c20405', description: 'array with multiple and nested items' }, - { input: 42, expected: '2a', description: 'single value' }, - { input: [], expected: 'c0', description: 'empty array' } + { input: 42, expected: '0x2a', description: 'single value' }, + { input: [], expected: '0xc0', description: 'empty array' } ]; /* Simple RLP_CODER decode */ const decodeTestCases = [ { - input: '2a', + input: '0x2a', expected: Buffer.from([42]), description: 'single value' }, - { input: 'c0', expected: [], description: 'empty array' } + { input: '0xc0', expected: [], description: 'empty array' } ]; /* NumericKind encode */ @@ -26,55 +26,55 @@ const numericKindEncodeTestCases = [ { kind: new RLP_CODER.NumericKind(8), data: '0x0', - expected: '', + expected: '0x', description: 'zero hex string' }, { kind: new RLP_CODER.NumericKind(8), data: '0x123', - expected: '0123', + expected: '0x0123', description: 'hex string' }, { kind: new RLP_CODER.NumericKind(8), data: '0', - expected: '', + expected: '0x', description: 'zero number string' }, { kind: new RLP_CODER.NumericKind(8), data: '100', - expected: '64', + expected: '0x64', description: 'number string' }, { kind: new RLP_CODER.NumericKind(8), data: 0, - expected: '', + expected: '0x', description: 'zero number' }, { kind: new RLP_CODER.NumericKind(8), data: 0x123, - expected: '0123', + expected: '0x0123', description: 'number in hex format' }, { kind: new RLP_CODER.NumericKind(8), data: 12524, - expected: '30ec', + expected: '0x30ec', description: 'number non hex format' }, { kind: new RLP_CODER.NumericKind(1), data: '5', - expected: '05', + expected: '0x05', description: 'number in hex format' }, { kind: new RLP_CODER.NumericKind(1), data: 255, - expected: 'ff', + expected: '0xff', description: 'number non hex format' } ]; @@ -171,7 +171,7 @@ const hexBlobKindEncodeTestCases = [ { kind: new RLP_CODER.HexBlobKind(), data: '0x010203', - expected: '010203', + expected: '0x010203', description: 'hex string' } ]; @@ -199,7 +199,7 @@ const fixedHexBlobKindEncodeTestCases = [ { kind: new RLP_CODER.FixedHexBlobKind(1), data: '0x01', - expected: '01', + expected: '0x01', description: 'hex string' } ]; @@ -296,13 +296,13 @@ const compactFixedHexBlobKindEncodeTestCases = [ kind: new RLP_CODER.CompactFixedHexBlobKind(4), data: '0x00112233', description: 'buffer with data', - expected: '112233' + expected: '0x112233' }, { kind: new RLP_CODER.CompactFixedHexBlobKind(1), data: '0x00', description: 'buffer with data', - expected: '' + expected: '0x' } ]; @@ -648,19 +648,19 @@ const encodeNumericProfileTestCases = [ { profile: numericProfile, data: numericData, - expected: 'd10102cec60304c3c20708c60506c3c2800a', + expected: '0xd10102cec60304c3c20708c60506c3c2800a', description: 'encode numeric profile' }, { profile: numericProfileWithMaxBytes, data: numericDataWithMaxBytes, - expected: 'c90102c6c20304c20506', + expected: '0xc90102c6c20304c20506', description: 'encode numeric profile with max bytes' }, { profile: numericProfileWithMaxBytes, data: numericDataWithString, - expected: 'c90102c6c20304c20506', + expected: '0xc90102c6c20304c20506', description: 'encode numeric profile with string' } ]; @@ -669,7 +669,7 @@ const encodeBufferProfileTestCases = [ { profile: bufferProfile, data: bufferData, - expected: 'c88301020383040506', + expected: '0xc88301020383040506', description: 'encode buffer profile' } ]; @@ -678,7 +678,7 @@ const encodeHexBlobProfileTestCases = [ { profile: hexBlobProfile, data: hexBlobData, - expected: 'c88301020383040506', + expected: '0xc88301020383040506', description: 'encode hex blob profile' } ]; @@ -687,7 +687,7 @@ const encodeFixedHexBlobProfileTestCases = [ { profile: fixedHexBlobProfile, data: fixedHexBlobData, - expected: 'c50183010203', + expected: '0xc50183010203', description: 'encode fixed hex blob profile' } ]; @@ -696,31 +696,31 @@ const encodeOptionalFixedHexBlobProfileTestCases = [ { profile: optionalFixedHexBlobProfile, data: optionalFixedHexBlobData, - expected: 'c50183010203', + expected: '0xc50183010203', description: 'encode nullable fixed hex blob profile' }, { profile: optionalFixedHexBlobProfile, data: optionalFixedHexBlobDataNull, - expected: 'c28080', + expected: '0xc28080', description: 'encode nullable fixed hex blob profile with null' }, { profile: optionalFixedHexBlobProfile, data: optionalFixedHexBlobDataMixed, - expected: 'c20180', + expected: '0xc20180', description: 'encode nullable fixed hex blob profile with mixed data' }, { profile: optionalFixedHexBlobProfile, data: optionalFixedHexBlobDataMixed2, - expected: 'c58083010203', + expected: '0xc58083010203', description: 'encode nullable fixed hex blob profile with mixed data 2' }, { profile: optionalFixedHexBlobProfile, data: optionalFixedHexBlobDataMixed3, - expected: 'c28080', + expected: '0xc28080', description: 'encode nullable fixed hex blob profile with mixed data 3' } ]; @@ -729,7 +729,7 @@ const encodeCompactFixedHexBlobProfileTestCases = [ { profile: compactFixedHexBlobProfile, data: compactFixedHexBlobData, - expected: 'c50183010203', + expected: '0xc50183010203', description: 'encode compact fixed hex blob profile' } ]; @@ -738,14 +738,14 @@ const encodeMixedKindProfileTestCases = [ { profile: mixedKindProfile1, data: mixedKindData1, - expected: 'd17b8412345678cac4118204d2c41282162e', + expected: '0xd17b8412345678cac4118204d2c41282162e', description: 'encode mixed kind profile' }, { profile: mixedKindProfile2, data: mixedKindData2, expected: - 'f8530184aabbccdd20f840df947567d83b7b8d80addcb281a71d54fc7b3364ffed82271086000000606060df947567d83b7b8d80addcb281a71d54fc7b3364ffed824e208600000060606081808252088083bc614e', + '0xf8530184aabbccdd20f840df947567d83b7b8d80addcb281a71d54fc7b3364ffed82271086000000606060df947567d83b7b8d80addcb281a71d54fc7b3364ffed824e208600000060606081808252088083bc614e', description: 'encode mixed kind profile with transaction like data' } ]; diff --git a/packages/core/tests/rlp/rlp.unit.test.ts b/packages/core/tests/rlp/rlp.unit.test.ts index f71a1abfc..700ed3c19 100644 --- a/packages/core/tests/rlp/rlp.unit.test.ts +++ b/packages/core/tests/rlp/rlp.unit.test.ts @@ -45,7 +45,9 @@ describe('RLP', () => { describe('encode', () => { encodeTestCases.forEach(({ input, expected, description }) => { test(description, () => { - expect(Hex.of(RLP_CODER.encode(input)).hex).toEqual(expected); + expect(Hex.of(RLP_CODER.encode(input)).toString()).toEqual( + expected + ); }); }); }); @@ -54,9 +56,9 @@ describe('RLP', () => { describe('decode', () => { decodeTestCases.forEach(({ input, expected, description }) => { test(description, () => { - expect(RLP_CODER.decode(Buffer.from(input, 'hex'))).toEqual( - expected - ); + expect( + RLP_CODER.decode(Buffer.from(Hex.of(input).bytes)) + ).toEqual(expected); }); }); }); @@ -89,7 +91,7 @@ describe('RLP', () => { ({ kind, data, expected, description }) => { test(description, () => { expect( - Hex.of(kind.data(data, '').encode()).hex + Hex.of(kind.data(data, '').encode()).toString() ).toEqual(expected); }); } @@ -140,7 +142,7 @@ describe('RLP', () => { ({ kind, data, expected, description }) => { test(description, () => { expect( - Hex.of(kind.data(data, '').encode()).hex + Hex.of(kind.data(data, '').encode()).toString() ).toEqual(expected); }); } @@ -192,7 +194,7 @@ describe('RLP', () => { ({ kind, data, expected, description }) => { test(description, () => { expect( - Hex.of(kind.data(data, '').encode()).hex + Hex.of(kind.data(data, '').encode()).toString() ).toEqual(expected); }); } @@ -245,7 +247,7 @@ describe('RLP', () => { ({ kind, data, expected, description }) => { test(description, () => { expect( - Hex.of(kind.data(data, '').encode()).hex + Hex.of(kind.data(data, '').encode()).toString() ).toEqual(expected); }); } @@ -279,7 +281,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); @@ -294,7 +296,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); @@ -309,7 +311,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); @@ -323,7 +325,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); @@ -338,7 +340,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); @@ -353,7 +355,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); @@ -368,7 +370,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); diff --git a/packages/core/tests/transaction/fixture.ts b/packages/core/tests/transaction/fixture.ts index a2b099d9f..129ca0cdd 100644 --- a/packages/core/tests/transaction/fixture.ts +++ b/packages/core/tests/transaction/fixture.ts @@ -1,4 +1,4 @@ -import { addressUtils, secp256k1, type TransactionBody } from '../../src'; +import { addressUtils, Hex, secp256k1, type TransactionBody } from '../../src'; /** * Simple correct transaction body fixture @@ -33,7 +33,7 @@ const transactions = { { body: _correctTransactionBody, signatureHashExpected: - '2a1c25ce0d66f45276a5f308b99bf410e2fc7d5b6ea37a49f2ab9f1da9446478', + '0x2a1c25ce0d66f45276a5f308b99bf410e2fc7d5b6ea37a49f2ab9f1da9446478', signedTransactionIdExpected: '0xda90eaea52980bc4bb8d40cb2ff84d78433b3b4a6e7d50b75736c5e3e77b71ec', encodedUnsignedExpected: Buffer.from( @@ -57,7 +57,7 @@ const transactions = { } }, signatureHashExpected: - '005fb0b47dfd16b7f2f61bb17df791242bc37ed1fffe9b05fa55fb0fe069f9a3', + '0x005fb0b47dfd16b7f2f61bb17df791242bc37ed1fffe9b05fa55fb0fe069f9a3', encodedUnsignedExpected: Buffer.from( 'f8550184aabbccdd20f840df947567d83b7b8d80addcb281a71d54fc7b3364ffed82271086000000606060df947567d83b7b8d80addcb281a71d54fc7b3364ffed824e208600000060606081808252088083bc614ec101', 'hex' @@ -79,7 +79,7 @@ const transactions = { } }, signatureHashExpected: - 'd6e8f162e3e08585ee8fcf81868e5bd57a59966fef218528339766ee2587726c', + '0xd6e8f162e3e08585ee8fcf81868e5bd57a59966fef218528339766ee2587726c', encodedUnsignedExpected: Buffer.from( 'f8610184aabbccdd20f840df947567d83b7b8d80addcb281a71d54fc7b3364ffed82271086000000606060df947567d83b7b8d80addcb281a71d54fc7b3364ffed824e208600000060606081808252088083bc614ecd01853078303030853078303030', 'hex' @@ -97,28 +97,24 @@ const transactions = { /** * Simple private key fixture */ -const _signerPrivateKey = Buffer.from( - '7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a', - 'hex' -); +const _signerPrivateKey = + '7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a'; const signer = { privateKey: _signerPrivateKey, address: addressUtils.fromPublicKey( - Buffer.from(secp256k1.derivePublicKey(_signerPrivateKey)) + secp256k1.derivePublicKey(Hex.of(_signerPrivateKey).bytes) ) }; /** * Simple private key of transaction delegator fixture */ -const _delegatorPrivateKey = Buffer.from( - '40de805e918403683fb9a6081c3fba072cdc5c88232c62a9509165122488dab7', - 'hex' -); +const _delegatorPrivateKey = + '40de805e918403683fb9a6081c3fba072cdc5c88232c62a9509165122488dab7'; const delegator = { privateKey: _delegatorPrivateKey, address: addressUtils.fromPublicKey( - Buffer.from(secp256k1.derivePublicKey(_delegatorPrivateKey)) + secp256k1.derivePublicKey(Hex.of(_delegatorPrivateKey).bytes) ) }; diff --git a/packages/core/tests/transaction/transaction-handler.unit.test.ts b/packages/core/tests/transaction/transaction-handler.unit.test.ts index b2a269ef9..4d223c376 100644 --- a/packages/core/tests/transaction/transaction-handler.unit.test.ts +++ b/packages/core/tests/transaction/transaction-handler.unit.test.ts @@ -30,15 +30,15 @@ describe('Transaction handler', () => { transactions.undelegated.forEach((transaction) => { const signedTransaction = TransactionHandler.sign( transaction.body, - signer.privateKey + Buffer.from(Hex.of(signer.privateKey).bytes) ); // Sign a non-delegated transaction with delegator expect(() => TransactionHandler.signWithDelegator( transaction.body, - signer.privateKey, - delegator.privateKey + Buffer.from(Hex.of(signer.privateKey).bytes), + Buffer.from(Hex.of(delegator.privateKey).bytes) ) ).toThrowError(NotDelegatedTransaction); @@ -68,19 +68,22 @@ describe('Transaction handler', () => { transactions.delegated.forEach((transaction) => { const signedTransaction = TransactionHandler.signWithDelegator( transaction.body, - signer.privateKey, - delegator.privateKey + Buffer.from(Hex.of(signer.privateKey).bytes), + Buffer.from(Hex.of(delegator.privateKey).bytes) ); // Sign normally a delegated transaction expect(() => - TransactionHandler.sign(transaction.body, signer.privateKey) + TransactionHandler.sign( + transaction.body, + Buffer.from(Hex.of(signer.privateKey).bytes) + ) ).toThrowError(InvalidTransactionField); expect(() => TransactionHandler.sign( transaction.body, - delegator.privateKey + Buffer.from(Hex.of(delegator.privateKey).bytes) ) ).toThrowError(InvalidTransactionField); @@ -118,14 +121,14 @@ describe('Transaction handler', () => { TransactionHandler.signWithDelegator( transactions.delegated[0].body, Buffer.from('INVALID', 'hex'), - delegator.privateKey + Buffer.from(Hex.of(delegator.privateKey).bytes) ); }).toThrowError(InvalidSecp256k1PrivateKey); expect(() => { TransactionHandler.signWithDelegator( transactions.delegated[0].body, - signer.privateKey, + Buffer.from(Hex.of(signer.privateKey).bytes), Buffer.from('INVALID', 'hex') ); }).toThrowError(InvalidSecp256k1PrivateKey); @@ -151,7 +154,7 @@ describe('Transaction handler', () => { transactions.undelegated.forEach((transaction) => { // Unsigned transaction const decodedUnsigned = TransactionHandler.decode( - transaction.encodedUnsignedExpected, + Buffer.from(transaction.encodedUnsignedExpected), false ); @@ -182,7 +185,7 @@ describe('Transaction handler', () => { // Signed transaction const decodedSigned = TransactionHandler.decode( - transaction.encodedSignedExpected, + Buffer.from(transaction.encodedSignedExpected), true ); @@ -242,8 +245,8 @@ describe('Transaction handler', () => { const encodedSignedDelegated = TransactionHandler.signWithDelegator( transactions.delegated[0].body, - signer.privateKey, - delegator.privateKey + Buffer.from(Hex.of(signer.privateKey).bytes), + Buffer.from(Hex.of(delegator.privateKey).bytes) ); // Signed transaction diff --git a/packages/core/tests/transaction/transaction.unit.test.ts b/packages/core/tests/transaction/transaction.unit.test.ts index c6d32b92b..9e124ccf5 100644 --- a/packages/core/tests/transaction/transaction.unit.test.ts +++ b/packages/core/tests/transaction/transaction.unit.test.ts @@ -31,7 +31,7 @@ describe('Transaction', () => { expect(unsignedTransaction.isSigned).toEqual(false); expect(unsignedTransaction.isDelegated).toEqual(false); expect( - Hex.of(unsignedTransaction.getSignatureHash()).hex + Hex.of(unsignedTransaction.getSignatureHash()).toString() ).toEqual(transaction.signatureHashExpected); // Get id from unsigned transaction (should throw error) @@ -51,7 +51,9 @@ describe('Transaction', () => { // Encoding expect(unsignedTransaction.encoded).toEqual( - transaction.encodedUnsignedExpected + Buffer.from( + Hex.of(transaction.encodedUnsignedExpected).bytes + ) ); // Intrinsic gas @@ -72,7 +74,7 @@ describe('Transaction', () => { // Init unsigned transaction from body const signedTransaction = TransactionHandler.sign( transaction.body, - signer.privateKey + Buffer.from(Hex.of(signer.privateKey).bytes) ); // Checks on signature @@ -80,7 +82,7 @@ describe('Transaction', () => { expect(signedTransaction.isSigned).toEqual(true); expect(signedTransaction.isDelegated).toEqual(false); expect( - Hex.of(signedTransaction.getSignatureHash()).hex + Hex.of(signedTransaction.getSignatureHash()).toString() ).toEqual(transaction.signatureHashExpected); // Checks on origin, id and delegator @@ -96,7 +98,7 @@ describe('Transaction', () => { // Encoding expect(signedTransaction.encoded).toEqual( - transaction.encodedSignedExpected + Buffer.from(Hex.of(transaction.encodedSignedExpected).bytes) ); }); }); @@ -119,7 +121,7 @@ describe('Transaction', () => { expect(unsignedTransaction.isSigned).toEqual(false); expect(unsignedTransaction.isDelegated).toEqual(true); expect( - Hex.of(unsignedTransaction.getSignatureHash()).hex + Hex.of(unsignedTransaction.getSignatureHash()).toString() ).toEqual(transaction.signatureHashExpected); // Get id from unsigned transaction (should throw error) @@ -154,8 +156,8 @@ describe('Transaction', () => { transactions.delegated.forEach((transaction) => { const signedTransaction = TransactionHandler.signWithDelegator( transaction.body, - signer.privateKey, - delegator.privateKey + Buffer.from(Hex.of(signer.privateKey).bytes), + Buffer.from(Hex.of(delegator.privateKey).bytes) ); // Checks on signature @@ -163,7 +165,7 @@ describe('Transaction', () => { expect(signedTransaction.isSigned).toEqual(true); expect(signedTransaction.isDelegated).toEqual(true); expect( - Hex.of(signedTransaction.getSignatureHash()).hex + Hex.of(signedTransaction.getSignatureHash()).toString() ).toEqual(transaction.signatureHashExpected); // Checks on origin, id and delegator diff --git a/packages/core/tests/vcdm/Hex.unit.test.ts b/packages/core/tests/vcdm/Hex.unit.test.ts index 858761adf..dd48300d1 100644 --- a/packages/core/tests/vcdm/Hex.unit.test.ts +++ b/packages/core/tests/vcdm/Hex.unit.test.ts @@ -1,5 +1,5 @@ -import { describe, expect, test } from '@jest/globals'; import { Hex, Txt } from '../../src'; +import { describe, expect, test } from '@jest/globals'; import { InvalidCastType, InvalidDataType } from '@vechain/sdk-errors'; /** @@ -198,6 +198,11 @@ describe('Hex class tests', () => { }); describe('isValid method tests', () => { + test('Return true for empty input', () => { + const exp = ''; + expect(Hex.isValid(exp)).toBeTruthy(); + }); + test('Return false for invalid hex', () => { const exp = 'G00dB0y'; expect(Hex.isValid(exp)).toBeFalsy(); @@ -225,6 +230,11 @@ describe('Hex class tests', () => { }); describe('isValid0x method tests', () => { + test('Return true for empty input', () => { + const exp = '0x'; + expect(Hex.isValid(exp)).toBeTruthy(); + }); + test('Return false for invalid hex', () => { const exp = '0xG00dB0y'; expect(Hex.isValid0x(exp)).toBeFalsy(); From 52b6358396332250298a1f713046796809f71cac Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Thu, 8 Aug 2024 17:49:20 +0100 Subject: [PATCH 03/48] feat: 1110 HexInt class documented --- packages/core/src/vcdm/Hex.ts | 6 ++- packages/core/src/vcdm/HexInt.ts | 46 ++++++++++++++++++- .../methods/eth_subscribe/eth_subscribe.ts | 1 - .../vechain-private-key-signer.ts | 16 +++---- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index db21887c8..c13ed26ba 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -8,12 +8,14 @@ import { type VeChainDataModel } from './VeChainDataModel'; * * `-` sign if the value is negative, * * `0x` hexadecimal notation tag, * * a not empty string of hexadecimal digits from `0` to `9` and from `a` to `f`. - * @implements {VeChainDataModel} + * * @description This hexadecimal notation is coherent with the decimal notation: * * the sign is only expressed for negative values, and it is always the first symbol, * * the `0x` tags the string as a hexadecimal expression, * * hexadecimal digits follow. * * An empty content results is no digits. + * + * @implements {VeChainDataModel} */ class Hex extends String implements VeChainDataModel { /** @@ -238,7 +240,7 @@ class Hex extends String implements VeChainDataModel { /** * Create a Hex instance from a bigint, number, string, or Uint8Array. * - * @param {bigint | number | string | Uint8Array} exp - The input value to convert to a Hex instance: + * @param {bigint | number | string | Uint8Array} exp - The value to convert to a Hex instance: * * bigint, converted to a signed hexadecimal expression of its absolute value; * * number, encoded as [IEEE 754 double precision 64 bits floating point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format); * * string, parsed as a hexadecimal expression, optionally signed `-`, optionally tagged with `0x`; diff --git a/packages/core/src/vcdm/HexInt.ts b/packages/core/src/vcdm/HexInt.ts index ce835dd2c..3f6e24191 100644 --- a/packages/core/src/vcdm/HexInt.ts +++ b/packages/core/src/vcdm/HexInt.ts @@ -1,7 +1,23 @@ import { Hex } from './Hex'; import { InvalidDataType } from '@vechain/sdk-errors'; +/** + * Represents a hexadecimal signed integer value. + * + * @description + * Any non-meaningful zero digits are removed from the hexadecimal expression of this instance. + * + * @remark This class makes equal instances created from the same value as number or as bigint. + * + * @extends {Hex} + */ class HexInt extends Hex { + /** + * Creates a new instance of this class to represent the `hex` value, + * not meaningful zero digits are remved from the hexadecimal representation. + * + * @param {Hex} hex - The Hex object to be used for constructing a new instance. + */ protected constructor(hex: Hex) { let cue = 0; while (cue < hex.hex.length && hex.hex.at(cue) === '0') { @@ -13,6 +29,16 @@ class HexInt extends Hex { ); } + /** + * Retrieves the value of n cast from this instance interpreted as the hexadecimal expression of a bigint value. + * + * @return {number} The value of n. + * + * @throws {InvalidDataType} If n is not within the safe number range, if the number representation of this + * instance results approximated. + * + * @remark This class makes equal instances created from the same value as number or as bigint. + */ public override get n(): number { const bi = this.bi; if (Number.MIN_SAFE_INTEGER <= bi && bi <= Number.MAX_VALUE) { @@ -24,6 +50,25 @@ class HexInt extends Hex { }); } + /** + * Create a HexInt instance from a bigint, number, string, Uint8Array, or {@link Hex}. + * + * @param {bigint | number | string | Uint8Array | Hex} exp - The value to convert to a HexInt instance: + * * bigint is converted to its hexadecimal expression, + * * number is cast to a bigint then converted to its hexadecimal expression, + * it throws {@link InvalidDataType} if not an integer value, + * * string is parsed as a bigint hexadecimal expression, + * * Uint8Array is interpreted as the sequence of bytes expressing a bigint value, + * then concerted to its hexadecimal expression, + * * {@link Hex} is interpreted as expressing a bigint value. + * + * @returns {HexInt} - The converted HexInt object. + * + * @throws {InvalidDataType} - If the value is not a valid hexadecimal integer expression, + * if `exp` is a not integer number. + * + * @remark This class makes equal instances created from the same value as number or as bigint. + */ public static of(exp: bigint | number | string | Uint8Array | Hex): HexInt { try { if (exp instanceof Hex) { @@ -39,7 +84,6 @@ class HexInt extends Hex { } return new HexInt(Hex.of(exp)); } catch (e) { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions throw new InvalidDataType( 'HexInt.of', 'not an hexadecimal integer expression', diff --git a/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_subscribe/eth_subscribe.ts b/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_subscribe/eth_subscribe.ts index 911fbc111..49919cbc0 100644 --- a/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_subscribe/eth_subscribe.ts +++ b/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_subscribe/eth_subscribe.ts @@ -102,7 +102,6 @@ const ethSubscribe = async ( provider.startSubscriptionsPolling(); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access const subscriptionId: string = Hex.random(16).hex; if (params.includes(SUBSCRIPTION_TYPE.NEW_HEADS)) { diff --git a/packages/network/src/signer/signers/vechain-private-key-signer/vechain-private-key-signer.ts b/packages/network/src/signer/signers/vechain-private-key-signer/vechain-private-key-signer.ts index dd2f27611..d343daac6 100644 --- a/packages/network/src/signer/signers/vechain-private-key-signer/vechain-private-key-signer.ts +++ b/packages/network/src/signer/signers/vechain-private-key-signer/vechain-private-key-signer.ts @@ -184,15 +184,13 @@ class VeChainPrivateKeySigner extends VeChainAbstractSigner { ): Promise { return await new Promise((resolve, reject) => { try { - const hash = n_utils.hexToBytes( - Hex.of( - vechain_sdk_core_ethers.TypedDataEncoder.hash( - domain, - types, - value - ) - ).hex - ); + const hash = Hex.of( + vechain_sdk_core_ethers.TypedDataEncoder.hash( + domain, + types, + value + ) + ).bytes; const sign = secp256k1.sign( hash, new Uint8Array(this.privateKey) From 0825dddf570610dcfe63da93d2f0c45f6320dc9a Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Thu, 8 Aug 2024 19:07:05 +0100 Subject: [PATCH 04/48] feat: 1110 HexInt class tested --- packages/core/src/vcdm/Hex.ts | 3 + packages/core/src/vcdm/HexInt.ts | 3 +- packages/core/tests/vcdm/Hex.unit.test.ts | 16 ++++ packages/core/tests/vcdm/HexInt.unit.test.ts | 77 ++++++++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 packages/core/tests/vcdm/HexInt.unit.test.ts diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index c13ed26ba..b5a66d20b 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -56,6 +56,8 @@ class Hex extends String implements VeChainDataModel { /** * Returns the hexadecimal digits expressing this absolute value, sign and `0x` prefix omitted. + + * @remark An empty content results in an empty string returned. */ public readonly hex: string; @@ -287,6 +289,7 @@ class Hex extends String implements VeChainDataModel { this.REGEX_PREFIX.test(exp) ? exp.slice(2) : exp ); } + // noinspection ExceptionCaughtLocallyJS throw new InvalidDataType('Hex.of', 'not an hexadecimal string', { exp }); diff --git a/packages/core/src/vcdm/HexInt.ts b/packages/core/src/vcdm/HexInt.ts index 3f6e24191..29f1edf6c 100644 --- a/packages/core/src/vcdm/HexInt.ts +++ b/packages/core/src/vcdm/HexInt.ts @@ -41,7 +41,7 @@ class HexInt extends Hex { */ public override get n(): number { const bi = this.bi; - if (Number.MIN_SAFE_INTEGER <= bi && bi <= Number.MAX_VALUE) { + if (Number.MIN_SAFE_INTEGER <= bi && bi <= Number.MAX_SAFE_INTEGER) { return Number(bi); } throw new InvalidDataType('HexInt.of', 'not in the safe number range', { @@ -78,6 +78,7 @@ class HexInt extends Hex { if (Number.isInteger(exp)) { return new HexInt(Hex.of(BigInt(exp))); } + // noinspection ExceptionCaughtLocallyJS throw new InvalidDataType('HexInt.of', 'not an integer', { exp }); diff --git a/packages/core/tests/vcdm/Hex.unit.test.ts b/packages/core/tests/vcdm/Hex.unit.test.ts index dd48300d1..903e8c810 100644 --- a/packages/core/tests/vcdm/Hex.unit.test.ts +++ b/packages/core/tests/vcdm/Hex.unit.test.ts @@ -127,6 +127,22 @@ describe('Hex class tests', () => { expect('-' + hex.hex).toEqual(exp.toLowerCase()); // Normalized from is lower case. }); + test('Return an Hex instance if the passed argument is an empty string with 0x prefix', () => { + const exp = '0x'; + const hex = Hex.of(exp); + expect(hex).toBeInstanceOf(Hex); + expect(hex.toString()).toEqual(exp); + expect(hex.hex.length).toEqual(0); + }); + + test('Return an Hex instance if the passed argument is an empty string without 0x prefix', () => { + const exp = ''; + const hex = Hex.of(exp); + expect(hex).toBeInstanceOf(Hex); + expect(hex.toString()).toEqual('0x'); + expect(hex.hex.length).toEqual(0); + }); + test('Return an Hex instance if the passed argument is UInt8Array', () => { const exp = Uint8Array.of(0xc0, 0xff, 0xee); const hex = Hex.of(exp); diff --git a/packages/core/tests/vcdm/HexInt.unit.test.ts b/packages/core/tests/vcdm/HexInt.unit.test.ts new file mode 100644 index 000000000..623493cac --- /dev/null +++ b/packages/core/tests/vcdm/HexInt.unit.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, test } from '@jest/globals'; +import { Hex, HexInt } from '../../src'; +import { InvalidDataType } from '@vechain/sdk-errors'; + +/** + * Test Txt class. + * @group unit/vcdm + */ +describe('HexInt class tests', () => { + describe('VeChain Data Model tests', () => { + test('Return equals values for bi and n properties from bigint value', () => { + const exp = -789514n; // Safe integer + const hex = HexInt.of(exp); + expect(hex.bi).toEqual(exp); + expect(hex.n).toEqual(Number(exp)); + expect(hex.toString()).toEqual('-0xc0c0a'); + }); + + test('Return equals values for bi and n properties from number value', () => { + const exp = 12648430; // Safe integer. + const hex = HexInt.of(exp); + expect(hex.n).toEqual(exp); + expect(hex.bi).toEqual(BigInt(exp)); + expect(hex.toString()).toEqual('0xc0ffee'); + }); + + test('Throw an exception if this integer is beyond safe integer range - underflow', () => { + const largeBigInt = BigInt(Number.MIN_SAFE_INTEGER) - BigInt(10); + const hexIntInstance = HexInt.of(largeBigInt); + expect(() => hexIntInstance.n).toThrow(InvalidDataType); + }); + + test('Throw an exception if this integer is beyond safe integer range - overflow', () => { + const largeBigInt = BigInt(Number.MAX_SAFE_INTEGER) + BigInt(10); + const hexIntInstance = HexInt.of(largeBigInt); + expect(() => hexIntInstance.n).toThrow(InvalidDataType); + }); + }); + + describe('Construction tests', () => { + test('Return an HexInt instance if the passed argument is a bigint', () => { + const exp = 12357n; + const hex = HexInt.of(exp); + expect(hex).toBeInstanceOf(HexInt); + }); + + test('Return an HexInt instance if the passed argument is an integer number', () => { + const exp = 12357; + const hex = HexInt.of(exp); + expect(hex).toBeInstanceOf(HexInt); + }); + + test('Return an HexInt instance if the passed argument is an string hexadecimal expression', () => { + const exp = '-C0c0a'; // Safe integer -789514. + const hex = HexInt.of(exp); + expect(hex).toBeInstanceOf(HexInt); + }); + + test('Return an HexInt instance if the passed argument is an Hex', () => { + const exp = Hex.of(-789514n); + const hex = HexInt.of(exp); + expect(hex).toBeInstanceOf(HexInt); + expect(hex.isEqual(exp)).toBeTruthy(); + }); + + test('Return an HexInt instance if the passed argument is Uint8Array', () => { + const exp = Uint8Array.of(0xc0, 0xff, 0xee); // Safe integer 12648430. + const hex = HexInt.of(exp); + expect(hex).toBeInstanceOf(Hex); + }); + + test('Throw an exception if the passed argument is not an integer number', () => { + const exp = 123.57; + expect(() => HexInt.of(exp)).toThrow(InvalidDataType); + }); + }); +}); From bfe0202d7e26bbc09f8ce4a3042dbf63fdbd7455 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Fri, 9 Aug 2024 08:58:33 +0100 Subject: [PATCH 05/48] feat: 1110 HexUInt class provided --- docs/diagrams/architecture/vcdm.md | 1 + packages/core/src/vcdm/Hex.ts | 9 ++++ packages/core/src/vcdm/HexUInt.ts | 53 +++++++++++++++++++ packages/core/src/vcdm/index.ts | 1 + packages/core/tests/vcdm/Hex.unit.test.ts | 14 +++++ packages/core/tests/vcdm/HexUInt.unit.test.ts | 22 ++++++++ 6 files changed, 100 insertions(+) create mode 100644 packages/core/src/vcdm/HexUInt.ts create mode 100644 packages/core/tests/vcdm/HexUInt.unit.test.ts diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 133d01f9a..48ed5c6e8 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -10,6 +10,7 @@ classDiagram <> } class Hex { + +Hex abs +number sign +Hex alignToBytes() +Hex fit(number digits) diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index b5a66d20b..b74f353fb 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -87,6 +87,15 @@ class Hex extends String implements VeChainDataModel { this.sign = sign; } + /** + * Returns the absolute value of this Hex object. + * + * @return {Hex} A new Hex object representing the absolute value of this Hex. + */ + public get abs(): Hex { + return new Hex(Hex.POSITIVE, this.hex); + } + /** * Returns the value of `bi` as a `BigInt` type. * diff --git a/packages/core/src/vcdm/HexUInt.ts b/packages/core/src/vcdm/HexUInt.ts new file mode 100644 index 000000000..36323c365 --- /dev/null +++ b/packages/core/src/vcdm/HexUInt.ts @@ -0,0 +1,53 @@ +import { HexInt } from './HexInt'; +import { Hex } from './Hex'; +import { InvalidDataType } from '@vechain/sdk-errors'; + +/** + * Represents a hexadecimal unsigned integer. + * + * @extends HexInt + */ +class HexUInt extends HexInt { + /** + * Creates a new instance of this class to represent the absolute `hi` value. + * + * @param {HexInt} hi - The HexInt object representing the hexadecimal value. + * + * @throws {InvalidDataType} Throws an error if the sign of hi is not positive. + */ + protected constructor(hi: HexInt) { + if (hi.sign >= Hex.POSITIVE) { + super(hi); + } else { + throw new InvalidDataType('HexUInt.constructor', 'not positive', { + hi + }); + } + } + + /** + * Create a HexUInt instance from the given expression interprete as an unsigned integer. + * + * @param exp The expression to convert. It can be of type bigint, number, string, Uint8Array, or HexInt. + * + * @returns {HexUInt} The converted hexadecimal unsigned integer. + * + * @throws {InvalidDataType} If the given expression is not a valid hexadecimal positive integer expression. + */ + public static of( + exp: bigint | number | string | Uint8Array | HexInt + ): HexUInt { + try { + return new HexUInt(HexInt.of(exp)); + } catch (e) { + throw new InvalidDataType( + 'HexUInt.of', + 'not a hexadecimal positive integer expression', + { exp }, + e + ); + } + } +} + +export { HexUInt }; diff --git a/packages/core/src/vcdm/index.ts b/packages/core/src/vcdm/index.ts index 87b10bf02..e477be9a7 100644 --- a/packages/core/src/vcdm/index.ts +++ b/packages/core/src/vcdm/index.ts @@ -1,5 +1,6 @@ export * from './Hex'; export * from './HexInt'; +export * from './HexUInt'; export * from './ThorId'; export * from './Txt'; export * from './VeChainDataModel'; diff --git a/packages/core/tests/vcdm/Hex.unit.test.ts b/packages/core/tests/vcdm/Hex.unit.test.ts index 903e8c810..3d03b468b 100644 --- a/packages/core/tests/vcdm/Hex.unit.test.ts +++ b/packages/core/tests/vcdm/Hex.unit.test.ts @@ -156,6 +156,20 @@ describe('Hex class tests', () => { }); }); + describe('abs property test', () => { + test('Return a positive Hex instance for a negative argument', () => { + const exp = '-0xA01fe'; + const hex = Hex.of(exp); + expect(hex.abs.isEqual(Hex.of(exp.slice(1)))).toBeTruthy(); + }); + + test('Return a positive Hex instance for a positive argument', () => { + const exp = '0xA01fe'; + const hex = Hex.of(exp); + expect(hex.abs.isEqual(hex)).toBeTruthy(); + }); + }); + describe('alignToBytes method tests', () => { test('Return bytes aligned from aligned expression', () => { const hex = Hex.of('0xc0fee'); diff --git a/packages/core/tests/vcdm/HexUInt.unit.test.ts b/packages/core/tests/vcdm/HexUInt.unit.test.ts new file mode 100644 index 000000000..982f12fce --- /dev/null +++ b/packages/core/tests/vcdm/HexUInt.unit.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, test } from '@jest/globals'; +import { HexUInt } from '../../src'; +import { InvalidDataType } from '@vechain/sdk-errors'; + +/** + * Test Txt class. + * @group unit/vcdm + */ +describe('HexUInt class tests', () => { + describe('Construction tests', () => { + test('Return an HexUInt instance if the passed argument is positive', () => { + const exp = '0xcaffee'; + const hi = HexUInt.of(exp); + expect(hi).toBeInstanceOf(HexUInt); + }); + + test('Throw an error if the passed argument is negative', () => { + const exp = '-0xcaffee'; + expect(() => HexUInt.of(exp)).toThrow(InvalidDataType); + }); + }); +}); From b9c481e5aa98df07933946016372fc1a85313bf1 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Fri, 9 Aug 2024 11:59:14 +0100 Subject: [PATCH 06/48] feat: 1113 utils/hex removed --- packages/core/src/utils/hex/_Hex.ts | 443 ------------------ packages/core/src/utils/hex/index.ts | 2 - packages/core/src/utils/hex/types.d.ts | 1 - packages/core/src/utils/index.ts | 1 - .../core/tests/utils/hex/hex.unit.test.ts | 302 ------------ 5 files changed, 749 deletions(-) delete mode 100644 packages/core/src/utils/hex/_Hex.ts delete mode 100644 packages/core/src/utils/hex/index.ts delete mode 100644 packages/core/src/utils/hex/types.d.ts delete mode 100644 packages/core/tests/utils/hex/hex.unit.test.ts diff --git a/packages/core/src/utils/hex/_Hex.ts b/packages/core/src/utils/hex/_Hex.ts deleted file mode 100644 index 4bd1dff89..000000000 --- a/packages/core/src/utils/hex/_Hex.ts +++ /dev/null @@ -1,443 +0,0 @@ -import * as n_utils from '@noble/curves/abstract/utils'; -import { InvalidDataType } from '@vechain/sdk-errors'; -import { HexInt } from '../../vcdm'; -import { randomBytes } from '@noble/hashes/utils'; -import { type HexString } from './types'; - -/** - * The PREFIX constant represents the prefix string used in the code. - * The prefix is set to '0x', indicating that the following value is in hexadecimal format. - * - * @constant {string} - * @default '0x' - * @see {_Hex0x.of} - */ -const PREFIX: string = '0x'; - -/** - * The radix value used for hexadecimal numbers. - * - * @type {number} - */ -const RADIX: number = 16; - -/** - * Regular expression for matching a string in the format `/^0x[0-9a-f]*$/i;` - * - * @type {RegExp} - * @see _Hex0x.of - * @see HexString - */ -const REGEX_FOR_0X_PREFIX_HEX: RegExp = /^0x[0-9a-f]*$/i; - -/** - * Regular expression for matching a string in the format `/^(0x)?[0-9a-f]*$/i;` - * - * @type {RegExp} - * @see HexString - */ -const REGEX_FOR_OPTIONAL_0X_PREFIX_HEX: RegExp = /^(0x)?[0-9a-f]*$/i; - -/** - * Default length of thor id hex string. - * Thor id is a 64 characters long hexadecimal string. - * This is used to validate thor id strings (block ids, transaction ids, ...). - */ -const THOR_ID_LENGTH = 64; - -/** - * Represents the error messages used in the {@link _Hex} object. - * @enum {string} - */ -enum ErrorMessage { - /** - * Error message constant indicating that the provided arguments are not sufficient - * to fit the expected value. - * - * @type {string} - * @see _Hex.canon - */ - NOT_FIT = `Arg 'bytes' not enough to fit 'exp'.`, - - /** - * Error message constant for invalid hexadecimal expression - * not matching {@link REGEX_FOR_0X_PREFIX_HEX}. - * - * @type {string} - * @see _Hex.canon - */ - NOT_HEX = `Arg 'n' not an hexadecimal expression.`, - - /** - * String constant representing an error message when the argument 'n' is not an integer. - * - * @type {string} - * @see ofNumber - */ - NOT_INTEGER = `Arg 'n' not an integer.`, - - /** - * Variable representing an error message when the argument 'bytes' is not a valid length. - * - * @type {string} - * @see _Hex.canon - */ - NOT_LENGTH = `Arg 'bytes' not a length.`, - - /** - * String constant representing an error message when argument 'n' is not negative. - * - * @type {string} - * @see {ofBigInt} - * @see {ofNumber} - */ - NOT_POSITIVE = `Arg 'n' not negative.` -} - -/** - * Represents a value that can be represented in hexadecimal format. - * - * @typedef { bigint | Uint8Array | number | string } HexRepresentable - */ -type HexRepresentable = bigint | Uint8Array | number | string; - -/** - * Convert a bigint number to a padded hexadecimal representation long the specified number of bytes. - * - * @param {bigint} bi - The bigint number to be represented as hexadecimal string. - * @param {number} bytes - The number of bytes the resulting hexadecimal representation should be padded to. - * @returns {string} - The padded hexadecimal representation of the bigint number. - * @throws {InvalidDataType} - */ -function ofBigInt(bi: bigint, bytes: number): string { - if (bi < 0) { - throw new InvalidDataType( - 'Hex.ofBigInt()', - ErrorMessage.NOT_POSITIVE as string, - { - bi: bi.toString() - } - ); - } - - return pad(bi.toString(RADIX), bytes); -} - -/** - * Converts a hexadecimal string representing a number to a padded lowercase hexadecimal string. - * - * @param {HexString} n - The hexadecimal string representing the number. - * @param {number} [bytes=0] - The number of bytes the resulting hexadecimal string should be padded to. Defaults to 0. - * @returns {string} - The padded lowercase hexadecimal string. - * @throws {InvalidDataType} - */ -function ofHexString(n: HexString, bytes: number): string { - if (!_Hex0x.isValid(n)) - throw new InvalidDataType( - 'Hex.ofHexString()', - ErrorMessage.NOT_HEX as string, - { - n - } - ); - return pad(n.slice(2).toLowerCase(), bytes); -} - -/** - * Convert a number to a padded hexadecimal representation long the specified number of bytes. - * - * @param {number} n - The number to be represented as hexadecimal string. - * @param {number} bytes - The number of bytes the resulting hexadecimal representation should be padded to. - * @returns {string} The padded hexadecimal representation of the number. - * @throws {InvalidDataType} - */ -function ofNumber(n: number, bytes: number): string { - if (!Number.isInteger(n)) { - throw new InvalidDataType( - 'Hex.ofNumber()', - ErrorMessage.NOT_INTEGER as string, - { n } - ); - } - if (n < 0) { - throw new InvalidDataType( - 'Hex.ofNumber()', - ErrorMessage.NOT_POSITIVE as string, - { n } - ); - } - - return pad(n.toString(RADIX), bytes); -} - -/** - * Converts a string to its binary representation, - * then to its padded hexadecimal representation - * long the specified number of bytes. - * - * @param {string} txt - The input string to be converted. - * @param {number} bytes - The number of bytes the resulting hexadecimal representation should be padded to. - * @returns {string} - The padded hexadecimal representation of the number. - * - * @see {ofUint8Array} - */ -function ofString(txt: string, bytes: number): string { - return ofUint8Array(n_utils.utf8ToBytes(txt), bytes); -} - -/** - * Convert an Uint8Array to a padded hexadecimal representation long the specified number of bytes. - * - * Secure audit function. - * * [n_utils](https://github.com/paulmillr/noble-curves?tab=readme-ov-file#utils-useful-utilities) - * - * @param {Uint8Array} uint8Array - The Uint8Array to be represented as hexadecimal string. - * @param {number} [bytes=0] - The number of bytes the resulting hexadecimal representation should be padded to. - * @return {string} - The padded hexadecimal representation of the buffer. - */ -function ofUint8Array(uint8Array: Uint8Array, bytes: number): string { - return pad(n_utils.bytesToHex(uint8Array), bytes); -} - -/** - * Adds padding to a hexadecimal expression to ensure it represents the specified number of bytes. - * - * @param {string} exp - The hexadecimal expression to pad. - * @param {number} bytes - The number of bytes that the expression should occupy. - * @return {string} The padded hexadecimal expression. - */ -function pad(exp: string, bytes: number): string { - let result = exp; - if (result.length % 2 !== 0) { - result = '0' + result; - } - if (bytes > 0) { - const gap = bytes - result.length / 2; - if (gap > 0) { - return `${'00'.repeat(gap)}${result}`; - } - } - return result; -} - -/** - * Trims leading zeros from a string. - * - * @param {string} exp - The string to trim. - * @returns {string} - The trimmed string. - * @see _Quantity.of - */ -// function trim(exp: string): string { -// let i = 0; -// while (i < exp.length && exp.at(i) === '0') { -// i++; -// } -// return i === exp.length ? '0' : exp.slice(i); -// } - -/** - * Helper for encoding hexadecimal values prefixed with '0x'. - */ -const _Hex0x = { - /** - * Converts a given string expression to a canonical representation prefixed with `0x`, - * optionally specifying the number of bytes to include in the canonical form. - * - * @param {string} exp - The string expression to convert to canonical form. - * @param {number} [bytes] - The number of bytes to include in the canonical form. - * If not specified, all bytes will be included. - * @returns {string} The canonical representation of the given string expression. - * @throws {Error} if `exp` is not a valid hexadecimal expression, - * if `bytes` is not integer and greater or equal to zero. - */ - canon: function (exp: string, bytes?: number): string { - return `${PREFIX}${_Hex.canon(exp, bytes)}`; - }, - - /** - * Checks if the given expression is a valid Thor-based ID. - * Thor id is a 64 characters long hexadecimal string. - * It is used to identify a transaction id, a block id, etc. - * - * @param {string} exp - The expression to check. - * @param {boolean} is0xOptional - Do not check if `exp` is `0x` prefixed, `false` by default. - * @returns {boolean} - Returns true if the expression is a valid Thor ID, otherwise false. - */ - isThorId: function (exp: string, is0xOptional: boolean = false): boolean { - return ( - this.isValid(exp, is0xOptional) && - (is0xOptional - ? exp.length === THOR_ID_LENGTH - : exp.length === THOR_ID_LENGTH + 2) // +2 for '0x' - ); - }, - - /** - * Checks if the given expression is a valid hexadecimal expression - * - prefixed with `0x` (or optionally if `is0xOptional is `true`), - * - byte aligned if `isByteAligned` is `true`. - * - * @param {string} exp - The expression to be validated. - * @param {boolean} is0xOptional - Do not check if `exp` is `0x` prefixed, `false` by default. - * @param {boolean} isByteAliged - Check `exp` represents a full byte or an array of bytes, `false`, by default. - * @returns {boolean} - Whether the expression is valid or not. - */ - isValid: function ( - exp: string, - is0xOptional: boolean = false, - isByteAliged: boolean = false - ): boolean { - let predicate: boolean = is0xOptional - ? REGEX_FOR_OPTIONAL_0X_PREFIX_HEX.test(exp) - : REGEX_FOR_0X_PREFIX_HEX.test(exp); - if (isByteAliged && predicate) { - predicate = exp.length % 2 === 0; - } - return predicate; - }, - - /** - * Returns a hexadecimal representation from the given input data prefixed with `0x`. - * - * **Note:** this method calls {@link _Hex.of} to generate the hexadecimal representation of n, - * then it prefixes the result with `0x`. - * - * @param {HexRepresentable} n - The input data to be represented. - * @param {number} [bytes=0] - If not `0` by default, the hexadecimal representation encodes at least {number} bytes. - * @returns {Uint8Array} - The resulting hexadecimal representation, - * it is guaranteed to be even characters long. - * @see _Hex - * @see HexRepresentable - */ - of: function (n: HexRepresentable, bytes: number = 0): string { - return `${PREFIX}${_Hex.of(n, bytes)}`; - } -}; - -/** - * Helper for encoding hexadecimal values. - */ -const _Hex = { - /** - * Converts a given string expression to a canonical representation prefixed with `0x`, - * optionally specifying the number of bytes to include in the canonical form. - * - * @param {string} exp - The string expression to convert to canonical form. - * @param {number} [bytes] - The number of bytes to include in the canonical form. - * If not specified, all bytes will be included. - * @returns {string} The canonical representation of the given string expression. - * @throws {InvalidDataType} - */ - canon: function (exp: string, bytes?: number): string { - let result: string = ''; - if (REGEX_FOR_0X_PREFIX_HEX.test(exp)) { - result = exp.slice(2).toLowerCase(); - } else if (REGEX_FOR_OPTIONAL_0X_PREFIX_HEX.test(exp)) { - result = exp.toLowerCase(); - } else { - throw new InvalidDataType('Hex.canon()', ErrorMessage.NOT_HEX, { - exp - }); - } - if (typeof bytes !== 'undefined') { - if (!Number.isInteger(bytes) || bytes < 0) { - throw new InvalidDataType( - 'Hex.canon()', - ErrorMessage.NOT_LENGTH as string, - { - bytes - } - ); - } - - result = pad(result, bytes); - - if (result.length > bytes * 2) { - throw new InvalidDataType( - 'Hex.canon()', - ErrorMessage.NOT_FIT as string, - { - bytes - } - ); - } - } - return result; - }, - - /** - * Returns a hexadecimal representation from the given input data. - * This method calls - * * {@link ofBigInt} if `n` type is `bigint`; - * * {@link ofHexString} if `n` type is {@link HexString}`; - * * {@link ofNumber} if `n` type is `number`; - * * {@link ofString} if `n` type is `string`; - * * {@link ofUint8Array} if `n` is an instance of {@link Uint8Array}. - * - * **Note:** the returned string is not prefixed with `0x`, - * see {@link _Hex0x.of} to make a hexadecimal representation prefixed with `0x`. - * - * **Note:** [HexString](https://docs.ethers.org/v6/api/utils/#HexString) - * definition overlaps `string` TS completely as an alias. - * This function tests if the given input starts with `0x` - * and is positive to {@link _Hex0x.isValid} - * processing it as {@link HexString} type, - * else it considers the string as an array of bytes and - * returns its hexadecimal representation. - * To force a string to be considered an array of bytes despite it is - * a valid `0x` hexadecimal expression, convert it to {@link Uint8Array}. - * ``` - * Hex.of(buffer.toString('hex')) - * ``` - * - * @param {HexRepresentable} n - The input data to be represented. - * @param {number} [bytes=0] - If not `0` by default, the hexadecimal representation encodes at least {number} bytes. - * @returns {Uint8Array} - The resulting hexadecimal representation, - * it is guaranteed to be even characters long. - * @see HexRepresentable - */ - of: function (n: HexRepresentable, bytes: number = 0): string { - if (n instanceof Uint8Array) return ofUint8Array(n, bytes); - if (typeof n === 'bigint') return ofBigInt(n, bytes); - if (typeof n === `number`) return ofNumber(n, bytes); - if (_Hex0x.isValid(n)) return ofHexString(n, bytes); - return ofString(n, bytes); - }, - - /** - * Generates a random hexadecimal string of the specified number of bytes. - * The length of the string is twice the `bytes`. - * - * @param {number} bytes - The number of bytes for the random string. - * @return {string} - The generated random string. - */ - random: function (bytes: number): string { - return ofUint8Array(randomBytes(bytes), bytes); - } -}; - -/** - * Helper for encoding hexadecimal values as used to represent Ethereum quantities. - */ -const _Quantity = { - /** - * Returns a hexadecimal representation for the given input data - * - without any not meaningful `0` digit on the left side, - * - prefixed with `0x`, - * - hence returns `0x0` if `n` is zero. - * - * This function is a more efficient drop-in replacement of the function - * `toQuantity` in [math.ts](https://github.com/ethers-io/ethers.js/blob/main/src.ts/utils/maths.ts) - * of [The Ethers Project](https://github.com/ethers-io/ethers.js/tree/main) library. - * - * @param {HexRepresentable} n - The input data to be represented. - * @return The resulting hexadecimal representation, nibble aligned. - * @see HexRepresentable - */ - of(n: HexRepresentable): string { - // return `${PREFIX}${trim(_Hex.of(n))}`; - return HexInt.of(n).toString(); - } -}; - -export { _Hex, _Hex0x, _Quantity }; diff --git a/packages/core/src/utils/hex/index.ts b/packages/core/src/utils/hex/index.ts deleted file mode 100644 index c436800d7..000000000 --- a/packages/core/src/utils/hex/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './_Hex'; -export * from './types.d'; diff --git a/packages/core/src/utils/hex/types.d.ts b/packages/core/src/utils/hex/types.d.ts deleted file mode 100644 index 33ae05c17..000000000 --- a/packages/core/src/utils/hex/types.d.ts +++ /dev/null @@ -1 +0,0 @@ -export type HexString = string; diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 89163871f..a66953550 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -2,7 +2,6 @@ export * from './const'; export * from './bloom'; export * from './data'; export * from './hdnode'; -export * from './hex'; export * from './transaction'; export * from './units'; export * from './revision'; diff --git a/packages/core/tests/utils/hex/hex.unit.test.ts b/packages/core/tests/utils/hex/hex.unit.test.ts deleted file mode 100644 index dcb97efc9..000000000 --- a/packages/core/tests/utils/hex/hex.unit.test.ts +++ /dev/null @@ -1,302 +0,0 @@ -import { describe, expect, test } from '@jest/globals'; -import { _Hex, _Hex0x, _Quantity } from '../../../src'; -import { - invalidThorIDs, - prefixedAndUnprefixedStrings, - validThorIDs -} from '../data/fixture'; -import { InvalidDataType } from '@vechain/sdk-errors'; - -/** - * Text Hex representation from TS types prefixed with `0x`. - * @group unit/utils/hex - */ -describe('Hex0x', () => { - test('canon - pad to 64 characters by default', () => { - const result = _Hex0x.canon('1a', 32); - expect(result).toHaveLength(66); // 64 chars + '0x' - expect(result).toBe( - '0x000000000000000000000000000000000000000000000000000000000000001a' - ); - }); - - test('canon - pad to custom length', () => { - const result = _Hex0x.canon('1a', 64); - expect(result).toHaveLength(130); // 128 chars + '0x' - expect(result).toBe('0x' + '0'.repeat(126) + '1a'); - }); - - test('canon - with 0x', () => { - const result = _Hex0x.canon('0x1a', 32); - expect(result).toBe( - '0x000000000000000000000000000000000000000000000000000000000000001a' - ); - }); - - test('canon - return unchanged if it is already the correct length', () => { - const hex = '0x' + '1'.repeat(64); - const result = _Hex0x.canon(hex, 32); - expect(result).toBe(hex); - }); - - test('canon - return a string of just zeros if the input is empty', () => { - const result = _Hex0x.canon('', 32); - expect(result).toBe('0x' + '0'.repeat(64)); - }); - - test('canon - not hex', () => { - expect(() => _Hex0x.canon('defg', 1)).toThrowError(InvalidDataType); - }); - - test('canon - not fit', () => { - expect(() => _Hex0x.canon('001a', 1)).toThrowError(InvalidDataType); - }); - - test('canon - non-integer length', () => { - expect(() => _Hex0x.canon('1a', 31.5)).toThrowError(InvalidDataType); - }); - - test('canon - not positive length', () => { - expect(() => _Hex0x.canon('1a', -32)).toThrowError(InvalidDataType); - }); - - test('of bigint', () => { - const output: string = _Hex0x.of(BigInt(10)); - expect(output).toBe('0x0a'); - }); - - test('of bigint - padded', () => { - const output: string = _Hex0x.of(BigInt(10), 2); - expect(output).toBe('0x000a'); - }); - - test('of Buffer', () => { - const buffer: Uint8Array = Buffer.from(new Uint8Array(1)); - buffer[0] = 10; - const output: string = _Hex0x.of(buffer); - expect(output).toBe('0x0a'); - }); - - test('of Buffer - padded', () => { - const buffer: Uint8Array = Buffer.from(new Uint8Array(1)); - buffer[0] = 10; - const output: string = _Hex0x.of(buffer, 2); - expect(output).toBe('0x000a'); - }); - - test('of Uint8Array', () => { - const uint8Array: Uint8Array = new Uint8Array(1); - uint8Array[0] = 10; - const output: string = _Hex0x.of(uint8Array); - expect(output).toBe('0x0a'); - }); - - test('of Uint8Array - padded', () => { - const uint8Array: Uint8Array = new Uint8Array(1); - uint8Array[0] = 10; - const output: string = _Hex0x.of(uint8Array, 2); - expect(output).toBe('0x000a'); - }); - - test('of number', () => { - const output: string = _Hex0x.of(10 as number); - expect(output).toBe('0x0a'); - }); - - test('of number', () => { - const output: string = _Hex0x.of(10 as number, 2); - expect(output).toBe('0x000a'); - }); - - test('of string', () => { - const output: string = _Hex0x.of('a' as string); - expect(output).toBe('0x61'); - }); - - test('of string', () => { - const output: string = _Hex0x.of('a' as string, 2); - expect(output).toBe('0x0061'); - }); - - test('isThorId - true', () => { - validThorIDs.forEach((id) => { - expect(_Hex0x.isThorId(id.value, !id.checkPrefix)).toBe(true); - }); - }); - - test('isThorId - false', () => { - invalidThorIDs.forEach((id) => { - expect(_Hex0x.isThorId(id.value, !id.checkPrefix)).toBe(false); - }); - }); - - test('isValid - true', () => { - const output: boolean = _Hex0x.isValid('0x12ef'); - expect(output).toBe(true); - }); - - test('isValid - true - optional 0x', () => { - expect(_Hex0x.isValid('12ef', true)).toBe(true); - expect(_Hex0x.isValid('0x12ef', true)).toBe(true); - }); - - test('isValid - false - no 0x', () => { - const output: boolean = _Hex0x.isValid('12ef'); - expect(output).toBe(false); - }); - - test('isValid - false - no hex', () => { - const output: boolean = _Hex0x.isValid('12fg'); - expect(output).toBe(false); - }); - - test('isValid - false - no hex', () => { - const output: boolean = _Hex0x.isValid('12fg'); - expect(output).toBe(false); - }); - - test('isValid - false - byte aligned', () => { - const output: boolean = _Hex0x.isValid('12e', true, true); - expect(output).toBe(false); - }); - - test('isValid - true - byte aligned', () => { - const output: boolean = _Hex0x.isValid('12ef', true, true); - expect(output).toBe(true); - }); -}); - -/** - * Text Hex representation from TS types. - * @group unit/utils/hex - */ -describe('Hex', () => { - test('canon', () => { - prefixedAndUnprefixedStrings.forEach((prefixAndUnprefix) => { - expect(_Hex.canon(prefixAndUnprefix.prefixed)).toBe( - prefixAndUnprefix.unprefixed - ); - }); - }); - - test('of bigint', () => { - const output: string = _Hex.of(BigInt(10)); - expect(output).toBe('0a'); - - expect(() => { - _Hex.of(BigInt(-10), 0); - }).toThrow("Arg 'n' not negative."); - }); - - test('of bigint - padded', () => { - const output: string = _Hex.of(BigInt(10), 2); - expect(output).toBe('000a'); - }); - - test('of Buffer', () => { - const buffer: Buffer = Buffer.from(new Uint8Array(1)); - buffer[0] = 10; - const output: string = _Hex.of(buffer); - expect(output).toBe('0a'); - }); - - test('of Buffer - padded', () => { - const buffer: Buffer = Buffer.from(new Uint8Array(1)); - buffer[0] = 10; - const output: string = _Hex.of(buffer, 2); - expect(output).toBe('000a'); - }); - - test('of HexString', () => { - const output: string = _Hex.of('0x61' as string); - expect(output).toBe('61'); - }); - - test('of HexString - padded', () => { - const output: string = _Hex.of('0x61' as string, 2); - expect(output).toBe('0061'); - }); - - test('of number', () => { - const output: string = _Hex.of(10 as number); - expect(output).toBe('0a'); - - expect(() => { - _Hex.of(3.14 as number); - }).toThrow(`Arg 'n' not an integer.`); - - expect(() => { - _Hex.of(-10 as number); - }).toThrow("Arg 'n' not negative."); - }); - - test('of number - padded', () => { - const output: string = _Hex.of(10 as number, 2); - expect(output).toBe('000a'); - }); - - test('of string', () => { - const output: string = _Hex.of('a' as string); - expect(output).toBe('61'); - }); - - test('of string - padded', () => { - const output: string = _Hex.of('a' as string, 2); - expect(output).toBe('0061'); - }); - - test('of Uint8Array', () => { - const uint8Array: Uint8Array = new Uint8Array(1); - uint8Array[0] = 10; - const output: string = _Hex.of(uint8Array); - expect(output).toBe('0a'); - }); - - test('of Uint8Array - padded', () => { - const uint8Array: Uint8Array = new Uint8Array(1); - uint8Array[0] = 10; - const output: string = _Hex.of(uint8Array); - expect(output).toBe('0a'); - }); - - test('random - should return a string of the correct length', () => { - const size = 8; - const hex = _Hex.random(size / 2); - expect(hex).toHaveLength(size); - }); - - test('random - should only contain hexadecimal characters', () => { - const size = 8; - const hex = _Hex.random(size / 2); - // This regex matches strings that only contain characters 0-9 and a-f - expect(hex).toMatch(/^[0-9a-f]+$/); - }); - - test('random - should return different values on subsequent calls', () => { - const size = 8; - const hex1 = _Hex.random(size / 2); - const hex2 = _Hex.random(size / 2); - expect(hex1).not.toEqual(hex2); - }); -}); - -/** - * Text hexadecimal representation of Ethereum quantities. - * @group unit/utils/hex - */ -describe('Quantity', () => { - test('of zero', () => { - const output: string = _Quantity.of(0); - expect(output).toBe('0x0'); - }); - - test('of odd long hex expression', () => { - const output: string = _Quantity.of(256); - expect(output).toBe('0x100'); - }); - - test('of even long hex expression', () => { - const output: string = _Quantity.of(255); - expect(output).toBe('0xff'); - }); -}); From 43422b1b19788b797b232b39c5c89b678d050d71 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Fri, 9 Aug 2024 12:26:34 +0100 Subject: [PATCH 07/48] feat: 1113 utils/hex removed --- packages/core/src/vcdm/Hex.ts | 1 - packages/core/src/vcdm/HexInt.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index b74f353fb..cab765fd6 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -306,7 +306,6 @@ class Hex extends String implements VeChainDataModel { throw new InvalidDataType( 'Hex.of', 'not an hexadecimal expression', - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions { exp: `${exp}` }, // Needed to serialize bigint values. e ); diff --git a/packages/core/src/vcdm/HexInt.ts b/packages/core/src/vcdm/HexInt.ts index 29f1edf6c..1f5670fd6 100644 --- a/packages/core/src/vcdm/HexInt.ts +++ b/packages/core/src/vcdm/HexInt.ts @@ -88,7 +88,6 @@ class HexInt extends Hex { throw new InvalidDataType( 'HexInt.of', 'not an hexadecimal integer expression', - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions { exp: `${exp}`, e } // Needed to serialize bigint values. ); } From 30441c3750aa819c0ded0c84eb2a8cf9602e47cf Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Sat, 10 Aug 2024 10:40:00 +0100 Subject: [PATCH 08/48] feat: 1115 Sha256.ts class implemented --- docs/diagrams/architecture/vcdm.md | 43 +-- packages/core/src/hash/Sha256.ts | 49 +++ packages/core/src/hash/index.ts | 2 +- packages/core/src/hash/sha256.ts | 71 ---- packages/core/src/hdnode/hdnode.ts | 6 +- packages/core/src/vcdm/Currency.ts | 21 ++ packages/core/src/vcdm/Hash.ts | 7 + packages/core/src/vcdm/Hex.ts | 24 +- packages/core/src/vcdm/HexUInt.ts | 2 +- packages/core/src/vcdm/Txt.ts | 10 +- packages/core/src/vcdm/VeChainDataModel.ts | 2 + packages/core/src/vcdm/index.ts | 2 + packages/core/tests/hash/Sha256.unit.test.ts | 32 ++ packages/core/tests/hash/fixture.ts | 98 +++--- packages/core/tests/hash/hash.unit.test.ts | 324 +++++++++--------- packages/core/tests/vcdm/Hex.unit.test.ts | 8 +- packages/core/tests/vcdm/HexInt.unit.test.ts | 2 +- packages/core/tests/vcdm/HexUInt.unit.test.ts | 2 +- packages/core/tests/vcdm/Txt.unit.test.ts | 4 +- .../errors/src/available-errors/vcdm/vcdm.ts | 7 +- .../tests/available-errors/vcdm.unit.test.ts | 8 +- 21 files changed, 376 insertions(+), 348 deletions(-) create mode 100644 packages/core/src/hash/Sha256.ts delete mode 100644 packages/core/src/hash/sha256.ts create mode 100644 packages/core/src/vcdm/Currency.ts create mode 100644 packages/core/src/vcdm/Hash.ts create mode 100644 packages/core/tests/hash/Sha256.unit.test.ts diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 48ed5c6e8..d1576fb1b 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -1,13 +1,14 @@ ```mermaid classDiagram - class Account class Address - class Blake2b256 - class BloomFilter - class Contract - class HDNode - class Hash { + class Currency { <> + +bigint units + +bigint fraction + +string code + } + class Hash { + <> } class Hex { +Hex abs @@ -25,18 +26,12 @@ classDiagram class HexUInt { +HexUInt of(bigint|number|string|Uint8Array|HexInt exp)$ } - class Keccak256 - class Keystore - class Revision - class Sha256 - class String - class ThorId { - +ThorId of(bigint|number|string|Uint8Array|Hex exp)$ - } class Txt { +Txt of(bigint|number|string|Uint8Array exp)$ } - class String + class Sha256 { + +Sha256 of(bigint|number|string|Uint8Array|Hex exp)$ + } class VeChainDataModel{ <> +bigint bi @@ -46,22 +41,14 @@ classDiagram +boolean isEqual(~T~ that) +boolean isNumber() } - Address <|-- Account - Address <|-- Contract - Hash <|-- Blake2b256 - Hash <|-- Keccak256 + class Wei + Currency <|-- Wei Hash <|-- Sha256 Hex <|-- HexInt - HexUInt <|-- Address - HexUInt <|-- BloomFilter - HexUInt <|-- HDNode HexInt <|-- HexUInt - HexUInt <|-- Hash - HexUInt <|-- ThorId - HexUInt <|-- Keystore - HexUInt <|-- Revision - String <|-- Hex - String <|-- Txt + HexUInt <|-- Sha256 + HexUInt <|-- Address + VeChainDataModel <|.. Currency VeChainDataModel <|.. Hex VeChainDataModel <|.. Txt ``` diff --git a/packages/core/src/hash/Sha256.ts b/packages/core/src/hash/Sha256.ts new file mode 100644 index 000000000..f40b05a57 --- /dev/null +++ b/packages/core/src/hash/Sha256.ts @@ -0,0 +1,49 @@ +import { Hex, HexUInt, type Hash } from '../vcdm'; +import * as nh_sha256 from '@noble/hashes/sha256'; +import { InvalidOperation } from '@vechain/sdk-errors'; + +/** + * Represents the result of an [SHA256](https://en.wikipedia.org/wiki/SHA-2) hash operation. + * + * @implements Hash + */ +class Sha256 extends HexUInt implements Hash { + /** + * Creates a new instance of this class to represent the absolute `hui` hash result. + * + * @param hui The hash result. + * @protected + */ + protected constructor(hui: HexUInt) { + super(hui); + } + + /** + * Generates the [SHA256](https://en.wikipedia.org/wiki/SHA-2) hash of the given input. + * + * @param {bigint | number | string | Uint8Array | Hex} exp - The input value to hash. + * + * @returns {Sha256} - The [SHA256](https://en.wikipedia.org/wiki/SHA-2) hash of the input value. + * + * @throws {InvalidOperation} - If a hash error occurs. + */ + public static of(exp: bigint | number | string | Uint8Array | Hex): Sha256 { + try { + return new Sha256( + HexUInt.of( + nh_sha256.sha256( + (exp instanceof Hex ? exp : Hex.of(exp)).bytes + ) + ) + ); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + throw new InvalidOperation('Sha256.of', 'hash error', { + exp: `${exp}`, // Needed to serialize bigint values. + e + }); + } + } +} + +export { Sha256 }; diff --git a/packages/core/src/hash/index.ts b/packages/core/src/hash/index.ts index 188b89195..965bc21af 100644 --- a/packages/core/src/hash/index.ts +++ b/packages/core/src/hash/index.ts @@ -1,4 +1,4 @@ export * from './types.d'; export * from './blake2b256'; -export * from './sha256'; +export * from './Sha256'; export * from './keccak256'; diff --git a/packages/core/src/hash/sha256.ts b/packages/core/src/hash/sha256.ts deleted file mode 100644 index 2b24201ca..000000000 --- a/packages/core/src/hash/sha256.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Hex } from '../vcdm/Hex'; -import { InvalidDataType } from '@vechain/sdk-errors'; -import { sha256 as _sha256 } from '@noble/hashes/sha256'; -import { type ReturnType } from './types'; - -/* --- Overloaded functions start --- */ - -/** - * Calculates the SHA-256 hash of the given data. - * - * Secure audit function. - * * {@link sha256} - * - * @param {string | Uint8Array} data - The data to calculate the hash for. - * @return {Uint8Array} - The SHA-256 hash of the given data. - */ -function sha256(data: string | Uint8Array): Uint8Array; - -/** - * Calculates the SHA-256 hash of the given data. - * - * @param {string | Uint8Array} data - The data to calculate the SHA-256 hash for. - * @param {'buffer'} returnType - The return type for the hash. Currently only supports 'buffer'. - * - * @return {Uint8Array} - The SHA-256 hash as a Uint8Array. - */ -function sha256(data: string | Uint8Array, returnType: 'buffer'): Uint8Array; - -/** - * Calculates the SHA-256 hash of the given data. - * - * Secure audit function. - * * {@link sha256} - * - * @param {string | Uint8Array} data - The input data to be hashed. - * @param {'hex'} returnType - The desired return type of the hash. - * @return {string} The SHA-256 hash of the data in the specified return type. - */ -function sha256(data: string | Uint8Array, returnType: 'hex'): string; - -/* --- Overloaded functions end --- */ - -/** - * Computes the SHA-256 hash of the given data. - * - * Secure audit function. - * * [_sha256](https://github.com/paulmillr/noble-hashes?tab=readme-ov-file#sha2-sha256-sha384-sha512-sha512_256) - * - * @param {string | Uint8Array} data - The data to compute the hash for. - * @param {ReturnType} [returnType='buffer'] - The desired return type for the hash. Defaults to 'buffer'. - * @return {Uint8Array | string} - The computed SHA-256 hash. - * @throws {InvalidDataType} - */ -function sha256( - data: string | Uint8Array, - returnType: ReturnType = 'buffer' -): Uint8Array | string { - // Assert that the returnType is valid - if (!['hex', 'buffer'].includes(returnType)) { - throw new InvalidDataType( - 'sha256()', - "Validation error: Invalid return type. Return type in hash function must be 'buffer' or 'hex'.", - { returnType } - ); - } - - const hash = _sha256(data); - return returnType === 'buffer' ? hash : Hex.of(hash).toString(); -} - -export { sha256 }; diff --git a/packages/core/src/hdnode/hdnode.ts b/packages/core/src/hdnode/hdnode.ts index 27d8bb7ae..6ca3ec687 100644 --- a/packages/core/src/hdnode/hdnode.ts +++ b/packages/core/src/hdnode/hdnode.ts @@ -8,7 +8,7 @@ import { } from '@vechain/sdk-errors'; import { base58 } from '@scure/base'; import { secp256k1 } from '../secp256k1'; -import { sha256 } from '../hash'; +import { Sha256 } from '../hash'; import { VET_DERIVATION_PATH, X_PRIV_PREFIX, X_PUB_PREFIX } from '../utils'; /** @@ -82,7 +82,7 @@ function fromPrivateKey( privateKey ); privateKey.fill(0); // Clear the private key from memory. - const checksum = sha256(sha256(header)).subarray(0, 4); + const checksum = Sha256.of(Sha256.of(header)).bytes.subarray(0, 4); const expandedPrivateKey = n_utils.concatBytes(header, checksum); try { return n_bip32.HDKey.fromExtendedKey( @@ -125,7 +125,7 @@ function fromPublicKey( chainCode, secp256k1.compressPublicKey(publicKey) ); - const checksum = sha256(sha256(header)).subarray(0, 4); + const checksum = Sha256.of(Sha256.of(header)).bytes.subarray(0, 4); const expandedPublicKey = n_utils.concatBytes(header, checksum); try { return n_bip32.HDKey.fromExtendedKey( diff --git a/packages/core/src/vcdm/Currency.ts b/packages/core/src/vcdm/Currency.ts new file mode 100644 index 000000000..7cde71cab --- /dev/null +++ b/packages/core/src/vcdm/Currency.ts @@ -0,0 +1,21 @@ +import { type VeChainDataModel } from './VeChainDataModel'; + +export interface Currency extends VeChainDataModel { + get bi(): bigint; + + get code(): string; +} + +// class Wei implements Currency { +// } + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +enum Digits { + wei, + kwei = 3, + mwei = 6, + gwei = 9, + szabo = 12, + finney = 15, + ether = 18 +} diff --git a/packages/core/src/vcdm/Hash.ts b/packages/core/src/vcdm/Hash.ts new file mode 100644 index 000000000..faf62c5f0 --- /dev/null +++ b/packages/core/src/vcdm/Hash.ts @@ -0,0 +1,7 @@ +/** + * Marker interface for classes implementing hash functionalities. + * + * @interface + */ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface Hash {} diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index cab765fd6..3a8a7a0e0 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -1,6 +1,6 @@ import * as nh_utils from '@noble/hashes/utils'; import * as nc_utils from '@noble/curves/abstract/utils'; -import { InvalidCastType, InvalidDataType } from '@vechain/sdk-errors'; +import { InvalidOperation, InvalidDataType } from '@vechain/sdk-errors'; import { type VeChainDataModel } from './VeChainDataModel'; /** @@ -17,7 +17,7 @@ import { type VeChainDataModel } from './VeChainDataModel'; * * @implements {VeChainDataModel} */ -class Hex extends String implements VeChainDataModel { +class Hex implements VeChainDataModel { /** * Negative multiplier of the {@link hex} absolute value. * @@ -82,7 +82,6 @@ class Hex extends String implements VeChainDataModel { normalize: (digits: string) => string = (digits) => digits.toLowerCase() ) { const normalizedDigits = normalize(digits); - super((sign < 0 ? '-0x' : '0x') + normalizedDigits); this.hex = normalizedDigits; this.sign = sign; } @@ -119,7 +118,7 @@ class Hex extends String implements VeChainDataModel { * * @return {number} The value of n. * - * @throws {InvalidCastType} Throws an error if this instance doesn't represent + * @throws {InvalidOperation} Throws an error if this instance doesn't represent * an [IEEE 754 double precision 64 bits floating point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format). */ get n(): number { @@ -127,11 +126,9 @@ class Hex extends String implements VeChainDataModel { // The sign is part of the IEEE 754 representation hence no need to consider `this.sign` property. return new DataView(this.bytes.buffer).getFloat64(0); } - throw new InvalidCastType( - 'Hex.n', - 'not an IEEE 754 float 64 number', - this - ); + throw new InvalidOperation('Hex.n', 'not an IEEE 754 float 64 number', { + hex: this.toString() + }); } /** @@ -330,6 +327,15 @@ class Hex extends String implements VeChainDataModel { bytes }); } + + /** + * Returns a string representation of the object. + * + * @return {string} The string representation of the object. + */ + public toString(): string { + return (this.sign < 0 ? '-0x' : '0x') + this.hex; + } } export { Hex }; diff --git a/packages/core/src/vcdm/HexUInt.ts b/packages/core/src/vcdm/HexUInt.ts index 36323c365..4f29d6d58 100644 --- a/packages/core/src/vcdm/HexUInt.ts +++ b/packages/core/src/vcdm/HexUInt.ts @@ -43,7 +43,7 @@ class HexUInt extends HexInt { throw new InvalidDataType( 'HexUInt.of', 'not a hexadecimal positive integer expression', - { exp }, + { exp: `${exp}`, e }, // Needed to serialize bigint values. e ); } diff --git a/packages/core/src/vcdm/Txt.ts b/packages/core/src/vcdm/Txt.ts index a0446032d..ba0a24e0f 100644 --- a/packages/core/src/vcdm/Txt.ts +++ b/packages/core/src/vcdm/Txt.ts @@ -1,4 +1,4 @@ -import { InvalidCastType } from '@vechain/sdk-errors'; +import { InvalidOperation } from '@vechain/sdk-errors'; import { type VeChainDataModel } from './VeChainDataModel'; /** @@ -50,17 +50,17 @@ class Txt extends String implements VeChainDataModel { * Converts the current Txt string to a BigInt. * * @returns {bigint} The BigInt representation of the Txt string. - * @throws {InvalidCastType} If the conversion to BigInt fails because this Txt string doesn't represent an integer. + * @throws {InvalidOperation} If the conversion to BigInt fails because this Txt string doesn't represent an integer. */ get bi(): bigint { try { return BigInt(this.toString()); } catch (e) { // eslint-disable-next-line @typescript-eslint/no-unsafe-call - throw new InvalidCastType( + throw new InvalidOperation( 'Txt.bi()', "Can't cast to big integer", - this, + { txt: this.toString() }, e ); } @@ -84,7 +84,7 @@ class Txt extends String implements VeChainDataModel { * Converts the current Txt string to a number. * * @returns {number} The numeric value of the Txt string. - * @throws {InvalidCastType} If the conversion to number fails because this Txt string doesn't represent a decimal number. + * @throws {InvalidOperation} If the conversion to number fails because this Txt string doesn't represent a decimal number. */ get n(): number { return Number(this.toString()); diff --git a/packages/core/src/vcdm/VeChainDataModel.ts b/packages/core/src/vcdm/VeChainDataModel.ts index 7a2ce564e..994349949 100644 --- a/packages/core/src/vcdm/VeChainDataModel.ts +++ b/packages/core/src/vcdm/VeChainDataModel.ts @@ -1,6 +1,8 @@ /** * Root interface for all the classes part of the `VeChain Data Model` * to provide a coherent API to represent, encode, and cast data among data types. + * + * @interface */ export interface VeChainDataModel { // Properties. diff --git a/packages/core/src/vcdm/index.ts b/packages/core/src/vcdm/index.ts index e477be9a7..d29a89ca6 100644 --- a/packages/core/src/vcdm/index.ts +++ b/packages/core/src/vcdm/index.ts @@ -1,3 +1,5 @@ +export * from './Currency'; +export * from './Hash'; export * from './Hex'; export * from './HexInt'; export * from './HexUInt'; diff --git a/packages/core/tests/hash/Sha256.unit.test.ts b/packages/core/tests/hash/Sha256.unit.test.ts new file mode 100644 index 000000000..a095bd94f --- /dev/null +++ b/packages/core/tests/hash/Sha256.unit.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, test } from '@jest/globals'; +import { CONTENT, NO_CONTENT } from './fixture'; +import { Hex, Sha256 } from '../../src'; +import { InvalidOperation } from '@vechain/sdk-errors'; + +const CONTENT_SHA256 = Hex.of( + '0xdb484f1fdd0c7ae9268a04a876ee4d1b1c40f801e80e56ff718b198aa2f1166f' +); +const NO_CONTENT_SHA256 = Hex.of( + '0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' +); + +/** + * Test Sha256 class. + * @group unit/hash + */ +describe('Sha256 class tests', () => { + test('Return hash for content', () => { + const sha256 = Sha256.of(CONTENT); + expect(sha256.isEqual(CONTENT_SHA256)).toBe(true); + }); + + test('Return hash for no content', () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const sha256 = Sha256.of(NO_CONTENT); + expect(sha256.isEqual(NO_CONTENT_SHA256)).toBe(true); + }); + + test('Throw an exception for illegal content', () => { + expect(() => Sha256.of('0xfoe')).toThrow(InvalidOperation); + }); +}); diff --git a/packages/core/tests/hash/fixture.ts b/packages/core/tests/hash/fixture.ts index 085475835..cd7f9ec52 100644 --- a/packages/core/tests/hash/fixture.ts +++ b/packages/core/tests/hash/fixture.ts @@ -1,56 +1,47 @@ -import * as n_utils from '@noble/curves/abstract/utils'; -import { blake2b256, keccak256, sha256, ZERO_BYTES } from '../../src'; +// import * as n_utils from '@noble/curves/abstract/utils'; +import { Hex, Txt, ZERO_BYTES } from '../../src'; +// +// /** +// * Hash functions to test +// */ +// const hashFunctionsToTest = [ +// { +// hashFunction: keccak256, +// results: { +// HELLO_WORLD_HASH_VALUE_HEX: +// '0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad', +// HELLO_WORLD_HASH_VALUE_BUFFER: n_utils.hexToBytes( +// '47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad' +// ), +// ZERO_BUFFER_HASH_VALUE_HEX: +// '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', +// ZERO_BUFFER_HASH_VALUE_BUFFER: n_utils.hexToBytes( +// 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' +// ) +// } +// }, +// { +// hashFunction: blake2b256, +// results: { +// HELLO_WORLD_HASH_VALUE_HEX: +// '0x256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610', +// HELLO_WORLD_HASH_VALUE_BUFFER: n_utils.hexToBytes( +// '256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610' +// ), +// ZERO_BUFFER_HASH_VALUE_HEX: +// '0x0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8', +// ZERO_BUFFER_HASH_VALUE_BUFFER: n_utils.hexToBytes( +// '0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8' +// ) +// } +// }, +// ]; +// -/** - * Hash functions to test - */ -const hashFunctionsToTest = [ - { - hashFunction: keccak256, - results: { - HELLO_WORLD_HASH_VALUE_HEX: - '0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad', - HELLO_WORLD_HASH_VALUE_BUFFER: n_utils.hexToBytes( - '47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad' - ), - ZERO_BUFFER_HASH_VALUE_HEX: - '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', - ZERO_BUFFER_HASH_VALUE_BUFFER: n_utils.hexToBytes( - 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' - ) - } - }, - { - hashFunction: blake2b256, - results: { - HELLO_WORLD_HASH_VALUE_HEX: - '0x256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610', - HELLO_WORLD_HASH_VALUE_BUFFER: n_utils.hexToBytes( - '256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610' - ), - ZERO_BUFFER_HASH_VALUE_HEX: - '0x0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8', - ZERO_BUFFER_HASH_VALUE_BUFFER: n_utils.hexToBytes( - '0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8' - ) - } - }, - { - hashFunction: sha256, - results: { - HELLO_WORLD_HASH_VALUE_HEX: - '0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9', - HELLO_WORLD_HASH_VALUE_BUFFER: n_utils.hexToBytes( - 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9' - ), - ZERO_BUFFER_HASH_VALUE_HEX: - '0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - ZERO_BUFFER_HASH_VALUE_BUFFER: n_utils.hexToBytes( - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' - ) - } - } -]; +const CONTENT = Hex.of( + Txt.of('Hello world - Здравствуйте - こんにちは!').bytes +); +const NO_CONTENT = Hex.of('0x'); const BLAKE2B256_CONTENT = 'Hello world - Здравствуйте - こんにちは!'; const BLAKE2B256_CONTENT_HASH = @@ -65,5 +56,6 @@ export { BLAKE2B256_CONTENT_HASH, BLAKE2B256_NO_CONTENT, BLAKE2B256_NO_CONTENT_HASH, - hashFunctionsToTest + CONTENT, + NO_CONTENT }; diff --git a/packages/core/tests/hash/hash.unit.test.ts b/packages/core/tests/hash/hash.unit.test.ts index 68b8a181b..bcaa4b882 100644 --- a/packages/core/tests/hash/hash.unit.test.ts +++ b/packages/core/tests/hash/hash.unit.test.ts @@ -1,162 +1,162 @@ -import { Buffer } from 'buffer'; -import { Txt, ZERO_BYTES } from '../../src'; -import { bytesToHex } from '@noble/ciphers/utils'; -import { cert } from '../certificate/fixture'; -import { describe, expect, test } from '@jest/globals'; -import { ethers } from 'ethers'; -import { hashFunctionsToTest } from './fixture'; -import { InvalidDataType } from '@vechain/sdk-errors'; - -/** - * Test hash functions - * @group unit/hash - */ -describe('Hash', () => { - test('thordevkit', () => { - const json = JSON.stringify(cert); - console.log(json); - // const blake_dev_dir = ThorDevKit.blake2b256(json); - // console.log('dir', blake_dev_dir); - const buffer = Buffer.from(json, `utf8`); - console.log(bytesToHex(buffer)); - // const textEncoder = new TextEncoder(); - // form: "NFC" | "NFD" | "NFKC" | "NFKD"): string - const array = Txt.of(json).bytes; - console.log(bytesToHex(array)); - // const blake_dev_ind = ThorDevKit.blake2b256(buffer); - // console.log('ind', blake_dev_ind); - // const blake_sdk = blake2b256(buffer); - // console.log('sdk', blake_sdk); - }); - - /** - * Test each hash function - */ - hashFunctionsToTest.forEach((hashFunction) => { - describe(`Function ${hashFunction.hashFunction.name}`, () => { - // Correctness of hash function - test('Hash as hex is correct', () => { - // Hello world hash - expect(hashFunction.hashFunction('hello world', 'hex')).toBe( - hashFunction.results.HELLO_WORLD_HASH_VALUE_HEX - ); - - // Zero bytes hash - expect(hashFunction.hashFunction(ZERO_BYTES(0), 'hex')).toBe( - hashFunction.results.ZERO_BUFFER_HASH_VALUE_HEX - ); - }); - - test('Hash as buffer is correct', () => { - // Hello world hash - expect( - hashFunction.hashFunction('hello world', 'buffer') - ).toStrictEqual( - hashFunction.results.HELLO_WORLD_HASH_VALUE_BUFFER - ); - - expect(hashFunction.hashFunction('hello world')).toStrictEqual( - hashFunction.results.HELLO_WORLD_HASH_VALUE_BUFFER - ); - - // Zero bytes hash - expect( - hashFunction.hashFunction(ZERO_BYTES(0), 'buffer') - ).toStrictEqual( - hashFunction.results.ZERO_BUFFER_HASH_VALUE_BUFFER - ); - - expect(hashFunction.hashFunction(ZERO_BYTES(0))).toStrictEqual( - hashFunction.results.ZERO_BUFFER_HASH_VALUE_BUFFER - ); - }); - - // Different ways of giving input data take the same hash - test('Hash is the same for different input formats', () => { - // Different ways of giving input data take the same hash - const hashesToHex = [ - hashFunction.hashFunction('hello world', 'hex'), - hashFunction.hashFunction( - Buffer.from('hello world'), - 'hex' - ), - hashFunction.hashFunction( - ethers.toUtf8Bytes('hello world'), - 'hex' - ), - hashFunction.hashFunction( - ethers.toUtf8Bytes(['hello', ' world'].join('')), - 'hex' - ), - hashFunction.hashFunction( - Buffer.from('hello world', 'utf8'), - 'hex' - ) - ]; - - const hashesToBufferWithDefaultedParameter = [ - hashFunction.hashFunction('hello world'), - hashFunction.hashFunction(Buffer.from('hello world')), - hashFunction.hashFunction( - ethers.toUtf8Bytes('hello world') - ), - hashFunction.hashFunction( - ethers.toUtf8Bytes(['hello', ' world'].join('')) - ), - hashFunction.hashFunction( - Buffer.from('hello world', 'utf8') - ) - ]; - - const hashesToBuffer = [ - hashFunction.hashFunction('hello world', 'buffer'), - hashFunction.hashFunction( - Buffer.from('hello world'), - 'buffer' - ), - hashFunction.hashFunction( - ethers.toUtf8Bytes('hello world'), - 'buffer' - ), - hashFunction.hashFunction( - ethers.toUtf8Bytes(['hello', ' world'].join('')), - 'buffer' - ), - hashFunction.hashFunction( - Buffer.from('hello world', 'utf8'), - 'buffer' - ) - ]; - - // All hashes are the same - for (let i = 0; i < hashesToHex.length; ++i) { - for (let j = i + 1; j < hashesToHex.length; ++j) { - if (i !== j) { - expect(hashesToHex[i]).toStrictEqual( - hashesToHex[j] - ); - expect( - hashesToBufferWithDefaultedParameter[i] - ).toStrictEqual( - hashesToBufferWithDefaultedParameter[j] - ); - expect(hashesToBuffer[i]).toStrictEqual( - hashesToBuffer[j] - ); - expect(hashesToBuffer[i]).toStrictEqual( - hashesToBufferWithDefaultedParameter[j] - ); - } - } - } - }); - - test('Invalid return type throws an error', () => { - expect(() => - // @ts-expect-error: Invalid return type - hashFunction.hashFunction('hello world', 'foo') - ).toThrowError(InvalidDataType); - }); - }); - }); -}); +// import { Buffer } from 'buffer'; +// import { Txt, ZERO_BYTES } from '../../src'; +// import { bytesToHex } from '@noble/ciphers/utils'; +// import { cert } from '../certificate/fixture'; +// import { describe, expect, test } from '@jest/globals'; +// import { ethers } from 'ethers'; +// import { hashFunctionsToTest } from './fixture'; +// import { InvalidDataType } from '@vechain/sdk-errors'; +// +// /** +// * Test hash functions +// * @group unit/hash +// */ +// describe('Hash', () => { +// test('thordevkit', () => { +// const json = JSON.stringify(cert); +// console.log(json); +// // const blake_dev_dir = ThorDevKit.blake2b256(json); +// // console.log('dir', blake_dev_dir); +// const buffer = Buffer.from(json, `utf8`); +// console.log(bytesToHex(buffer)); +// // const textEncoder = new TextEncoder(); +// // form: "NFC" | "NFD" | "NFKC" | "NFKD"): string +// const array = Txt.of(json).bytes; +// console.log(bytesToHex(array)); +// // const blake_dev_ind = ThorDevKit.blake2b256(buffer); +// // console.log('ind', blake_dev_ind); +// // const blake_sdk = blake2b256(buffer); +// // console.log('sdk', blake_sdk); +// }); +// +// /** +// * Test each hash function +// */ +// hashFunctionsToTest.forEach((hashFunction) => { +// describe(`Function ${hashFunction.hashFunction.name}`, () => { +// // Correctness of hash function +// test('Hash as hex is correct', () => { +// // Hello world hash +// expect(hashFunction.hashFunction('hello world', 'hex')).toBe( +// hashFunction.results.HELLO_WORLD_HASH_VALUE_HEX +// ); +// +// // Zero bytes hash +// expect(hashFunction.hashFunction(ZERO_BYTES(0), 'hex')).toBe( +// hashFunction.results.ZERO_BUFFER_HASH_VALUE_HEX +// ); +// }); +// +// test('Hash as buffer is correct', () => { +// // Hello world hash +// expect( +// hashFunction.hashFunction('hello world', 'buffer') +// ).toStrictEqual( +// hashFunction.results.HELLO_WORLD_HASH_VALUE_BUFFER +// ); +// +// expect(hashFunction.hashFunction('hello world')).toStrictEqual( +// hashFunction.results.HELLO_WORLD_HASH_VALUE_BUFFER +// ); +// +// // Zero bytes hash +// expect( +// hashFunction.hashFunction(ZERO_BYTES(0), 'buffer') +// ).toStrictEqual( +// hashFunction.results.ZERO_BUFFER_HASH_VALUE_BUFFER +// ); +// +// expect(hashFunction.hashFunction(ZERO_BYTES(0))).toStrictEqual( +// hashFunction.results.ZERO_BUFFER_HASH_VALUE_BUFFER +// ); +// }); +// +// // Different ways of giving input data take the same hash +// test('Hash is the same for different input formats', () => { +// // Different ways of giving input data take the same hash +// const hashesToHex = [ +// hashFunction.hashFunction('hello world', 'hex'), +// hashFunction.hashFunction( +// Buffer.from('hello world'), +// 'hex' +// ), +// hashFunction.hashFunction( +// ethers.toUtf8Bytes('hello world'), +// 'hex' +// ), +// hashFunction.hashFunction( +// ethers.toUtf8Bytes(['hello', ' world'].join('')), +// 'hex' +// ), +// hashFunction.hashFunction( +// Buffer.from('hello world', 'utf8'), +// 'hex' +// ) +// ]; +// +// const hashesToBufferWithDefaultedParameter = [ +// hashFunction.hashFunction('hello world'), +// hashFunction.hashFunction(Buffer.from('hello world')), +// hashFunction.hashFunction( +// ethers.toUtf8Bytes('hello world') +// ), +// hashFunction.hashFunction( +// ethers.toUtf8Bytes(['hello', ' world'].join('')) +// ), +// hashFunction.hashFunction( +// Buffer.from('hello world', 'utf8') +// ) +// ]; +// +// const hashesToBuffer = [ +// hashFunction.hashFunction('hello world', 'buffer'), +// hashFunction.hashFunction( +// Buffer.from('hello world'), +// 'buffer' +// ), +// hashFunction.hashFunction( +// ethers.toUtf8Bytes('hello world'), +// 'buffer' +// ), +// hashFunction.hashFunction( +// ethers.toUtf8Bytes(['hello', ' world'].join('')), +// 'buffer' +// ), +// hashFunction.hashFunction( +// Buffer.from('hello world', 'utf8'), +// 'buffer' +// ) +// ]; +// +// // All hashes are the same +// for (let i = 0; i < hashesToHex.length; ++i) { +// for (let j = i + 1; j < hashesToHex.length; ++j) { +// if (i !== j) { +// expect(hashesToHex[i]).toStrictEqual( +// hashesToHex[j] +// ); +// expect( +// hashesToBufferWithDefaultedParameter[i] +// ).toStrictEqual( +// hashesToBufferWithDefaultedParameter[j] +// ); +// expect(hashesToBuffer[i]).toStrictEqual( +// hashesToBuffer[j] +// ); +// expect(hashesToBuffer[i]).toStrictEqual( +// hashesToBufferWithDefaultedParameter[j] +// ); +// } +// } +// } +// }); +// +// test('Invalid return type throws an error', () => { +// expect(() => +// // @ts-expect-error: Invalid return type +// hashFunction.hashFunction('hello world', 'foo') +// ).toThrowError(InvalidDataType); +// }); +// }); +// }); +// }); diff --git a/packages/core/tests/vcdm/Hex.unit.test.ts b/packages/core/tests/vcdm/Hex.unit.test.ts index 3d03b468b..52868e6c8 100644 --- a/packages/core/tests/vcdm/Hex.unit.test.ts +++ b/packages/core/tests/vcdm/Hex.unit.test.ts @@ -1,9 +1,9 @@ -import { Hex, Txt } from '../../src'; import { describe, expect, test } from '@jest/globals'; -import { InvalidCastType, InvalidDataType } from '@vechain/sdk-errors'; +import { Hex, Txt } from '../../src'; +import { InvalidDataType, InvalidOperation } from '@vechain/sdk-errors'; /** - * Test Txt class. + * Test Hex class. * @group unit/vcdm */ describe('Hex class tests', () => { @@ -43,7 +43,7 @@ describe('Hex class tests', () => { test('Throw an exception casting n from a not IEEE expression', () => { const exp = 12357n; const hex = Hex.of(exp); - expect(() => hex.n).toThrow(InvalidCastType); + expect(() => hex.n).toThrow(InvalidOperation); }); test('compareTo method tests - same signs', () => { diff --git a/packages/core/tests/vcdm/HexInt.unit.test.ts b/packages/core/tests/vcdm/HexInt.unit.test.ts index 623493cac..ded8a9513 100644 --- a/packages/core/tests/vcdm/HexInt.unit.test.ts +++ b/packages/core/tests/vcdm/HexInt.unit.test.ts @@ -3,7 +3,7 @@ import { Hex, HexInt } from '../../src'; import { InvalidDataType } from '@vechain/sdk-errors'; /** - * Test Txt class. + * Test HexInt class. * @group unit/vcdm */ describe('HexInt class tests', () => { diff --git a/packages/core/tests/vcdm/HexUInt.unit.test.ts b/packages/core/tests/vcdm/HexUInt.unit.test.ts index 982f12fce..17052360e 100644 --- a/packages/core/tests/vcdm/HexUInt.unit.test.ts +++ b/packages/core/tests/vcdm/HexUInt.unit.test.ts @@ -3,7 +3,7 @@ import { HexUInt } from '../../src'; import { InvalidDataType } from '@vechain/sdk-errors'; /** - * Test Txt class. + * Test HexUInt class. * @group unit/vcdm */ describe('HexUInt class tests', () => { diff --git a/packages/core/tests/vcdm/Txt.unit.test.ts b/packages/core/tests/vcdm/Txt.unit.test.ts index 082d0e355..0f83ade34 100644 --- a/packages/core/tests/vcdm/Txt.unit.test.ts +++ b/packages/core/tests/vcdm/Txt.unit.test.ts @@ -1,5 +1,5 @@ import { Txt } from '../../src'; -import { InvalidCastType } from '@vechain/sdk-errors'; +import { InvalidOperation } from '@vechain/sdk-errors'; import { TextEncoder } from 'util'; import { describe, expect, test } from '@jest/globals'; @@ -13,7 +13,7 @@ describe('Txt class tests', () => { describe('VeChain Data Model tests', () => { test('bi getter should throw an error if a decimal value is cast to big integer', () => { const txt = Txt.of('12.3'); - expect(() => txt.bi).toThrow(InvalidCastType); + expect(() => txt.bi).toThrow(InvalidOperation); }); test('bi getter should return a BigInt representation of the integer value', () => { diff --git a/packages/errors/src/available-errors/vcdm/vcdm.ts b/packages/errors/src/available-errors/vcdm/vcdm.ts index 0cf06aea9..7ede2c019 100644 --- a/packages/errors/src/available-errors/vcdm/vcdm.ts +++ b/packages/errors/src/available-errors/vcdm/vcdm.ts @@ -1,11 +1,12 @@ import { VechainSDKError } from '../sdk-error'; +import { type ObjectErrorData } from '../types'; /** * Invalid cast type error. * * WHEN TO USE: - * * Error will be thrown when a cast between types is invalid. + * * Error will be thrown when a method call or property read fails. */ -class InvalidCastType extends VechainSDKError {} +class InvalidOperation extends VechainSDKError {} -export { InvalidCastType }; +export { InvalidOperation }; diff --git a/packages/errors/tests/available-errors/vcdm.unit.test.ts b/packages/errors/tests/available-errors/vcdm.unit.test.ts index 56650bd48..5a327584e 100644 --- a/packages/errors/tests/available-errors/vcdm.unit.test.ts +++ b/packages/errors/tests/available-errors/vcdm.unit.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from '@jest/globals'; -import { InvalidCastType, VechainSDKError } from '../../src'; +import { InvalidOperation, VechainSDKError } from '../../src'; /** * Available errors test - VCDM @@ -7,13 +7,13 @@ import { InvalidCastType, VechainSDKError } from '../../src'; */ describe('Error package Available errors test - VCDM', () => { /** - * InvalidCastType + * InvalidOperation */ - test('InvalidCastType', () => { + test('InvalidOperation', () => { // Inner error [undefined, new Error('error')].forEach((innerError) => { expect(() => { - throw new InvalidCastType<{ data: string }>( + throw new InvalidOperation( 'method', 'message', { data: 'data' }, From 72f66f4818fa7887a6b88842b73ea67706253201 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Tue, 27 Aug 2024 13:54:26 +0100 Subject: [PATCH 09/48] feat: 1113 ThorId implemented --- docs/diagrams/architecture/vcdm.md | 6 + packages/core/src/vcdm/Mnemonic.ts | 27 +++-- packages/core/src/vcdm/ThorId.ts | 56 +++++---- packages/core/src/vcdm/Txt.ts | 9 +- packages/core/tests/vcdm/ThorId.unit.test.ts | 115 +++++++++++++++++++ 5 files changed, 179 insertions(+), 34 deletions(-) create mode 100644 packages/core/tests/vcdm/ThorId.unit.test.ts diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 59722c8aa..09d97a13b 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -69,6 +69,11 @@ classDiagram class Txt { +Txt of(bigint|number|string|Uint8Array exp)$ } + class ThorId { + +boolean isValid(string exp) + +boolean isValid0x(string exp) + +ThorID of(bigint|number|string|Uint8Array|HexInt exp)$ + } class VeChainDataModel{ <> +bigint bi @@ -93,6 +98,7 @@ classDiagram HexUInt <|-- Keccak256 HexUInt <|-- Quantity HexUInt <|-- Sha256 + HexUInt <|-- ThorId String <|-- Txt Txt <|-- Revision Txt <|-- Mnemonic diff --git a/packages/core/src/vcdm/Mnemonic.ts b/packages/core/src/vcdm/Mnemonic.ts index 1be1cc15a..9572b4888 100644 --- a/packages/core/src/vcdm/Mnemonic.ts +++ b/packages/core/src/vcdm/Mnemonic.ts @@ -120,20 +120,19 @@ class Mnemonic extends Txt { * [BIP39 Mnemonic Words](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) * and a derivation path as in the examples. * - * Secure audit function. - * - {@link bip32.HDKey}(https://github.com/paulmillr/scure-bip32) - * - {@link HDNode} - * * @example `m/0` (default) * @example `m/0/2` * @example `m/0/2/4/6` * - * * @param {string[]} words - The set of words used for mnemonic generation. * @param {string} [path='m/0'] - The derivation path from the current node. + * * @returns {Uint8Array} - The derived private key as a Uint8Array. * * @throws {InvalidHDNode} + * + * @remarks Security auditable method, depends on + * * {@link HDNode}. */ public static toPrivateKey( words: string[], @@ -159,18 +158,18 @@ class Mnemonic extends Txt { * [BIP39 Mnemonic Words](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) * phrase using the specified wordlist size and random generator. * - * Secure audit function. - * - [bip39](https://github.com/paulmillr/scure-bip39) - * - `randomGenerator` - **Must provide a cryptographic secure source of entropy - * else any secure audit certification related with this software is invalid.** - * * @param {WordlistSizeType} wordlistSize The number of words to generate the mnemonic. * @param {function} [randomGenerator] The random generator function used to generate the entropy. * * @returns {Mnemonic} The generated mnemonic. * * @throws {InvalidDataType} If the number of words is not valid. - * @remarks This method is a wrapper around the `generateMnemonic` function from the `bip39` package. + * + * @remarks Security auditable method, depends on + * * [entropyToMnemonic](https://github.com/paulmillr/scure-bip39); + * * [generateMnemonic](https://github.com/paulmillr/scure-bip39); + * * `randomGenerator` - **Must provide a cryptographic secure source of entropy + * else any secure audit certification related with this software is invalid.** */ public static generate( wordlistSize: WordlistSizeType = 12, @@ -203,9 +202,13 @@ class Mnemonic extends Txt { /** * Check if the given mnemonic words are valid. + * * @param {string | string[]} words The mnemonic words to check. + * * @returns {boolean} true if the words are valid, false otherwise. - * @remarks This method is a wrapper around the `validateMnemonic` function from the `bip39` package. + * + * @remarks Security auditable method, depends on + * * [validateMnemonic](https://github.com/paulmillr/scure-bip39). */ public static isValid(words: string | string[]): boolean { const wordsToValidate = Array.isArray(words) ? words.join(' ') : words; diff --git a/packages/core/src/vcdm/ThorId.ts b/packages/core/src/vcdm/ThorId.ts index 262e14c6d..318fe2ba3 100644 --- a/packages/core/src/vcdm/ThorId.ts +++ b/packages/core/src/vcdm/ThorId.ts @@ -1,14 +1,18 @@ import { Hex } from './Hex'; +import { HexUInt } from './HexUInt'; import { InvalidDataType } from '@vechain/sdk-errors'; /** - * Represents a ThorId. - * @experiemntal + * The ThorId class represents a Thor ID value, which is a hexadecimal positive integer having 64 digits. + * + * @extends HexInt */ -class ThorId extends Hex { +class ThorId extends HexUInt { /** * Number of digits to represent a Thor ID value. * + * @remarks The `0x` prefix is excluded. + * * @type {number} */ private static readonly DIGITS = 64; @@ -16,19 +20,18 @@ class ThorId extends Hex { /** * Constructs a ThorId object with the provided hexadecimal value. * - * @param {Hex} hex - The hexadecimal value representing the ThorId. + * @param {HexUInt} huint - The hexadecimal value representing the ThorId. * * @throws {InvalidDataType} - If the provided value is not a valid ThorId expression. - * @experiemntal */ - protected constructor(hex: Hex) { - if (ThorId.isValid(hex.digits)) { - super(Hex.POSITIVE, hex.digits); + protected constructor(huint: HexUInt) { + if (ThorId.isValid(huint.digits)) { + super(Hex.POSITIVE, huint.digits); } else { throw new InvalidDataType( 'ThorId.constructor', 'not a ThorId expression', - { hex } + { hex: huint } ); } } @@ -37,11 +40,11 @@ class ThorId extends Hex { * Check if the given expression is a valid ThorId. * * @param {string} exp - The expression to be validated. + * * @return {boolean} Returns true if the expression is a valid ThorId, false otherwise. - * @experimental */ public static isValid(exp: string): boolean { - return Hex.isValid(exp) && Hex.REGEX_HEX_PREFIX.test(exp) + return Hex.isValid(exp) && HexUInt.REGEX_HEXUINT_PREFIX.test(exp) ? exp.length === ThorId.DIGITS + 2 : exp.length === ThorId.DIGITS; } @@ -50,11 +53,11 @@ class ThorId extends Hex { * Determines whether the given string is a valid hex number prefixed with '0x'. * * @param {string} exp - The hex number to be checked. - * @returns {boolean} - True if the hex number is valid, false otherwise. - * @experimental + * + * @returns {boolean} - True if the hex number is valid, false otherwise. */ public static isValid0x(exp: string): boolean { - return Hex.REGEX_HEX_PREFIX.test(exp) && ThorId.isValid(exp); + return HexUInt.REGEX_HEXUINT_PREFIX.test(exp) && ThorId.isValid(exp); } /** @@ -65,16 +68,29 @@ class ThorId extends Hex { * - bigint: A BigInteger value that represents the ThorId. * - number: A number value that represents the ThorId. * - string: A string value that represents the ThorId. - * - Hex: A Hex object that represents the ThorId. + * - HexUInt: A HexUInt object that represents the ThorId. * - Uint8Array: A Uint8Array object that represents the ThorId. + * * @returns {ThorId} - A new ThorId object created from the given expression. - * @experimntal + * + * @throws {InvalidDataType} If the given expression is not a valid hexadecimal positive integer expression. */ - public static of(exp: bigint | number | string | Hex | Uint8Array): ThorId { - if (exp instanceof Hex) { - return new ThorId(exp.fit(this.DIGITS)); + public static of( + exp: bigint | number | string | Uint8Array | HexUInt + ): ThorId { + try { + if (exp instanceof Hex) { + return new ThorId(exp.fit(this.DIGITS)); + } + return new ThorId(HexUInt.of(exp).fit(ThorId.DIGITS)); + } catch (e) { + throw new InvalidDataType( + 'ThorId.of', + 'not a ThorId expression', + { exp: `${exp}` }, // Needed to serialize bigint values. + e + ); } - return new ThorId(Hex.of(exp).fit(ThorId.DIGITS)); } } diff --git a/packages/core/src/vcdm/Txt.ts b/packages/core/src/vcdm/Txt.ts index ba0a24e0f..83a522da1 100644 --- a/packages/core/src/vcdm/Txt.ts +++ b/packages/core/src/vcdm/Txt.ts @@ -50,7 +50,8 @@ class Txt extends String implements VeChainDataModel { * Converts the current Txt string to a BigInt. * * @returns {bigint} The BigInt representation of the Txt string. - * @throws {InvalidOperation} If the conversion to BigInt fails because this Txt string doesn't represent an integer. + * + * @throws {InvalidOperation} If the conversion to BigInt fails because this Txt string doesn't represent an integer. */ get bi(): bigint { try { @@ -84,6 +85,7 @@ class Txt extends String implements VeChainDataModel { * Converts the current Txt string to a number. * * @returns {number} The numeric value of the Txt string. + * * @throws {InvalidOperation} If the conversion to number fails because this Txt string doesn't represent a decimal number. */ get n(): number { @@ -94,6 +96,7 @@ class Txt extends String implements VeChainDataModel { * Compares the current instance to another instance of Txt. * * @param {Txt} that - The instance to compare with. + * * @return {number} - A negative number if the current instance is less than the specified instance, * zero if they are equal, or a positive number if the current instance is greater. */ @@ -105,7 +108,8 @@ class Txt extends String implements VeChainDataModel { * Checks if the current Txt object is equal to the given Txt object. * * @param {Txt} that - The Txt object to compare with. - * @return {boolean} - True if the objects are equal, false otherwise. + * + * @return {boolean} - True if the objects are equal, false otherwise. */ public isEqual(that: Txt): boolean { return this.compareTo(that) === 0; @@ -128,6 +132,7 @@ class Txt extends String implements VeChainDataModel { * * {@link number} is represented as a {@link NFC} encoded string expressing the value in base 10; * * {@link string} is encoded as {@link NFC} string; * * {@link Uint8Array} is {@link NFC} decoded to a string. + * * @returns {Txt} - A new Txt instance. */ public static of(exp: bigint | number | string | Uint8Array): Txt { diff --git a/packages/core/tests/vcdm/ThorId.unit.test.ts b/packages/core/tests/vcdm/ThorId.unit.test.ts new file mode 100644 index 000000000..07c23c9cd --- /dev/null +++ b/packages/core/tests/vcdm/ThorId.unit.test.ts @@ -0,0 +1,115 @@ +import { describe, expect, test } from '@jest/globals'; +import { HexUInt, ThorId } from '../../src'; +import { InvalidDataType } from '@vechain/sdk-errors'; + +const ThorIdFixture = { + invalid: { + short: '0x271f7db20141001975f71deb8fca90d6b22b8d6610d', + noHex: '0xInvalidThorID' + }, + valid: { + bytes: '0x271f7db20141001975f71deb8fca90d6b22b8d6610dfb5a3e0bbeaf78b5a4891', + number: '0x00000000000000000000000000000000000000000000000000000000000000ff' // This safely casts to number. + } +}; + +/** + * Test ThorId class. + * @group unit/vcdm + */ +describe('ThorId class tests.', () => { + describe('Construction tests', () => { + test('Return a ThorId instance if the passed argument is an array of bytes', () => { + const exp = HexUInt.of(ThorIdFixture.valid.bytes); + const tid = ThorId.of(exp.bytes); + expect(tid).toBeInstanceOf(ThorId); + expect(tid.isEqual(exp)).toBe(true); + }); + + test('Return a ThorId instance if the passed argument is a bigint', () => { + const exp = HexUInt.of(ThorIdFixture.valid.bytes); + const tid = ThorId.of(exp.bi); + expect(tid).toBeInstanceOf(ThorId); + expect(tid.isEqual(exp)).toBe(true); + }); + + test('Return a ThorId instance if the passed argument is a number', () => { + const exp = HexUInt.of(ThorIdFixture.valid.number); + const tid = ThorId.of(exp.n); // This is a safe number cast. + expect(tid).toBeInstanceOf(ThorId); + expect(tid.isEqual(exp)).toBe(true); + }); + + test('Return a ThorId instance if the passed argument is a `0x` prefixed string', () => { + const exp = HexUInt.of(ThorIdFixture.valid.bytes); + const tid = ThorId.of(exp.toString()); + expect(tid).toBeInstanceOf(ThorId); + expect(tid.isEqual(exp)).toBe(true); + }); + + test('Return a ThorId instance if the passed argument is a not prefixed string', () => { + const exp = HexUInt.of(ThorIdFixture.valid.bytes); + const tid = ThorId.of(exp.digits); + expect(tid).toBeInstanceOf(ThorId); + expect(tid.isEqual(exp)).toBe(true); + }); + + test('Throw an error if the passed argument is a negative bigint', () => { + expect(() => ThorId.of(-1)).toThrow(InvalidDataType); + }); + + test('Throw an error if the passed argument is a negative number', () => { + expect(() => ThorId.of(-1n)).toThrow(InvalidDataType); + }); + }); + + describe('isValid method tests', () => { + test('Return false for no hex expression', () => { + expect(ThorId.isValid(ThorIdFixture.invalid.noHex)).toBe(false); + }); + + test('Return false for short expression', () => { + expect(ThorId.isValid(ThorIdFixture.invalid.short)).toBe(false); + }); + + test('Return true for valid `0x` prefixed expression', () => { + expect(ThorId.isValid(ThorIdFixture.valid.bytes)).toBe(true); + }); + + test('Return true for valid `not prefixed expression', () => { + expect( + ThorId.isValid(HexUInt.of(ThorIdFixture.valid.bytes).digits) + ).toBe(true); + }); + }); + + describe('isValid0x method tests', () => { + test('Return false for no hex expression', () => { + expect(ThorId.isValid0x(ThorIdFixture.invalid.noHex)).toBe(false); + }); + + test('Return false for short expression', () => { + expect(ThorId.isValid0x(ThorIdFixture.invalid.short)).toBe(false); + }); + + test('Return true for valid `0x` prefixed expression', () => { + expect(ThorId.isValid0x(ThorIdFixture.valid.bytes)).toBe(true); + }); + + test('Return false for valid `not prefixed expression', () => { + expect( + ThorId.isValid0x(HexUInt.of(ThorIdFixture.valid.bytes).digits) + ).toBe(false); + }); + }); + + test('digits property should return 64 characters', () => { + const tid = ThorId.of(0); + expect(tid.digits.length).toBe(64); + }); + + test('toString method should return 66 characters', () => { + const tid = ThorId.of(0); + expect(tid.toString().length).toBe(66); + }); +}); From fc0fbce28e2d9786cc864db1b4b993fc63a416f0 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Tue, 27 Aug 2024 13:59:39 +0100 Subject: [PATCH 10/48] feat: 1113 ThorId implemented --- docs/diagrams/architecture/vcdm.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 09d97a13b..cf6737fcf 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -70,7 +70,6 @@ classDiagram +Txt of(bigint|number|string|Uint8Array exp)$ } class ThorId { - +boolean isValid(string exp) +boolean isValid0x(string exp) +ThorID of(bigint|number|string|Uint8Array|HexInt exp)$ } From ef0b5cfbaeeb8d399ec89a17d6720a512e6d2c17 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Wed, 28 Aug 2024 12:44:32 +0100 Subject: [PATCH 11/48] feat: 1113 RationalNumber in dev... --- packages/core/src/vcdm/RationalNumber.ts | 50 ++++++++++++++ packages/core/src/vcdm/ThorId.ts | 18 ++--- packages/core/src/vcdm/index.ts | 1 + .../tests/vcdm/RationalNumber.unit.test.ts | 69 +++++++++++++++++++ packages/core/tests/vcdm/ThorId.unit.test.ts | 7 ++ 5 files changed, 131 insertions(+), 14 deletions(-) create mode 100644 packages/core/src/vcdm/RationalNumber.ts create mode 100644 packages/core/tests/vcdm/RationalNumber.unit.test.ts diff --git a/packages/core/src/vcdm/RationalNumber.ts b/packages/core/src/vcdm/RationalNumber.ts new file mode 100644 index 000000000..cbd2dbe10 --- /dev/null +++ b/packages/core/src/vcdm/RationalNumber.ts @@ -0,0 +1,50 @@ +class RationalNumber { + public readonly mantissa: bigint; + + public readonly exponent: number; + + constructor(mantissa: bigint, exponent: number) { + this.mantissa = mantissa; + this.exponent = exponent; + } + + private static abs(bi: bigint): bigint { + return bi < 0n ? -bi : bi; + } + + public scale(): RationalNumber { + let mantissa = this.mantissa; + let exponent = this.exponent; + if (exponent < 0) { + while (mantissa % 10n === 0n) { + mantissa /= 10n; + exponent++; + } + } else if (exponent > 0) { + while (exponent > 0) { + mantissa *= 10n; + exponent--; + } + } + return new RationalNumber(mantissa, exponent); + } + + public toString(decimalSeparator: string = '.'): string { + const sign = this.mantissa < 0n ? '-' : ''; + const digits = RationalNumber.abs(this.mantissa).toString(); + if (this.exponent < 0) { + const decimals = digits.slice(this.exponent); + const integerLen = digits.length + this.exponent; + if (integerLen > 0) { + const integers = digits.slice(0, integerLen); + return sign + integers + decimalSeparator + decimals; + } else { + const padding = '0'.repeat(-1 * integerLen); + return sign + '0' + decimalSeparator + padding + decimals; + } + } + return sign + digits; + } +} + +export { RationalNumber }; diff --git a/packages/core/src/vcdm/ThorId.ts b/packages/core/src/vcdm/ThorId.ts index 318fe2ba3..a6beac415 100644 --- a/packages/core/src/vcdm/ThorId.ts +++ b/packages/core/src/vcdm/ThorId.ts @@ -21,19 +21,9 @@ class ThorId extends HexUInt { * Constructs a ThorId object with the provided hexadecimal value. * * @param {HexUInt} huint - The hexadecimal value representing the ThorId. - * - * @throws {InvalidDataType} - If the provided value is not a valid ThorId expression. */ protected constructor(huint: HexUInt) { - if (ThorId.isValid(huint.digits)) { - super(Hex.POSITIVE, huint.digits); - } else { - throw new InvalidDataType( - 'ThorId.constructor', - 'not a ThorId expression', - { hex: huint } - ); - } + super(Hex.POSITIVE, huint.fit(ThorId.DIGITS).digits); } /** @@ -79,10 +69,10 @@ class ThorId extends HexUInt { exp: bigint | number | string | Uint8Array | HexUInt ): ThorId { try { - if (exp instanceof Hex) { - return new ThorId(exp.fit(this.DIGITS)); + if (exp instanceof HexUInt) { + return new ThorId(exp); } - return new ThorId(HexUInt.of(exp).fit(ThorId.DIGITS)); + return new ThorId(HexUInt.of(exp)); } catch (e) { throw new InvalidDataType( 'ThorId.of', diff --git a/packages/core/src/vcdm/index.ts b/packages/core/src/vcdm/index.ts index d469c2e76..20e873aed 100644 --- a/packages/core/src/vcdm/index.ts +++ b/packages/core/src/vcdm/index.ts @@ -8,6 +8,7 @@ export * from './HexInt'; export * from './HexUInt'; export * from './Mnemonic'; export * from './Quantity'; +export * from './RationalNumber'; export * from './Revision'; export * from './ThorId'; export * from './Txt'; diff --git a/packages/core/tests/vcdm/RationalNumber.unit.test.ts b/packages/core/tests/vcdm/RationalNumber.unit.test.ts new file mode 100644 index 000000000..8f43485df --- /dev/null +++ b/packages/core/tests/vcdm/RationalNumber.unit.test.ts @@ -0,0 +1,69 @@ +import { describe, test } from '@jest/globals'; +import { RationalNumber } from '../../src'; + +/** + * Test Revision class. + * @group unit/vcdm + */ +describe('Revision class tests', () => { + describe('scale method tests', () => { + describe('negative exponent', () => { + test('negative exponent scale to 0', () => { + const rn = new RationalNumber(12345000n, -3).scale(); + console.log(rn.toString()); + }); + + test('negative exponent scale to negative', () => { + const rn = new RationalNumber(12345000n, -5).scale(); + console.log(rn.toString()); + }); + }); + + describe('positive exponent', () => { + test('positive exponent = 0', () => { + const rn = new RationalNumber(12345n, 0).scale(); + console.log(rn.toString()); + }); + + test('positive exponent > 0', () => { + const rn = new RationalNumber(12345n, 3).scale(); + console.log(rn.toString()); + }); + }); + }); + describe('toString method tests', () => { + describe('negative range', () => { + test('negative integer', () => { + const rn = new RationalNumber(-12345n, 0); + console.log(rn.toString()); + }); + + test('negative decimal > -1', () => { + const rn = new RationalNumber(-12345n, -8); + console.log(rn.toString()); + }); + + test('negative decimal < -1', () => { + const rn = new RationalNumber(-12345n, -3); + console.log(rn.toString()); + }); + }); + + describe('positive range', () => { + test('positive decimal < 1', () => { + const rn = new RationalNumber(12345n, -5); + console.log(rn.toString()); + }); + + test('positive decimal > 1', () => { + const rn = new RationalNumber(12345n, -2); + console.log(rn.toString()); + }); + + test('positive integer', () => { + const rn = new RationalNumber(12345n, 0); + console.log(rn.toString()); + }); + }); + }); +}); diff --git a/packages/core/tests/vcdm/ThorId.unit.test.ts b/packages/core/tests/vcdm/ThorId.unit.test.ts index 07c23c9cd..d1d6b5b37 100644 --- a/packages/core/tests/vcdm/ThorId.unit.test.ts +++ b/packages/core/tests/vcdm/ThorId.unit.test.ts @@ -54,6 +54,13 @@ describe('ThorId class tests.', () => { expect(tid.isEqual(exp)).toBe(true); }); + test('Return a ThorId instance if the passed argument is a HexUint instance', () => { + const exp = HexUInt.of(ThorIdFixture.valid.bytes); + const tid = ThorId.of(exp); + expect(tid).toBeInstanceOf(ThorId); + expect(tid.isEqual(exp)).toBe(true); + }); + test('Throw an error if the passed argument is a negative bigint', () => { expect(() => ThorId.of(-1)).toThrow(InvalidDataType); }); From 5381d0342cd8348338477e8230e8965c9035200a Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Wed, 28 Aug 2024 15:07:05 +0100 Subject: [PATCH 12/48] feat: 1113 RationalNumber in dev... --- packages/core/src/vcdm/RationalNumber.ts | 15 +++++-- .../tests/vcdm/RationalNumber.unit.test.ts | 45 ++++++++++++++++++- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/packages/core/src/vcdm/RationalNumber.ts b/packages/core/src/vcdm/RationalNumber.ts index cbd2dbe10..db5b7ee15 100644 --- a/packages/core/src/vcdm/RationalNumber.ts +++ b/packages/core/src/vcdm/RationalNumber.ts @@ -12,16 +12,23 @@ class RationalNumber { return bi < 0n ? -bi : bi; } - public scale(): RationalNumber { + public plus(that: RationalNumber): RationalNumber { + const mce = Math.min(this.exponent, that.exponent); // Minimum common exponent. + const a = this.scale(mce); + const b = that.scale(mce); + return new RationalNumber(a.mantissa + b.mantissa, mce).scale(); + } + + public scale(toExponent: number = 0): RationalNumber { let mantissa = this.mantissa; let exponent = this.exponent; - if (exponent < 0) { + if (exponent < toExponent) { while (mantissa % 10n === 0n) { mantissa /= 10n; exponent++; } - } else if (exponent > 0) { - while (exponent > 0) { + } else if (exponent > toExponent) { + while (exponent > toExponent) { mantissa *= 10n; exponent--; } diff --git a/packages/core/tests/vcdm/RationalNumber.unit.test.ts b/packages/core/tests/vcdm/RationalNumber.unit.test.ts index 8f43485df..d09c30501 100644 --- a/packages/core/tests/vcdm/RationalNumber.unit.test.ts +++ b/packages/core/tests/vcdm/RationalNumber.unit.test.ts @@ -6,8 +6,49 @@ import { RationalNumber } from '../../src'; * @group unit/vcdm */ describe('Revision class tests', () => { + describe('plus methods tests', () => { + test('with carry over', () => { + const a = new RationalNumber(123n, -2); + const b = new RationalNumber(277n, -2); + const r = a.plus(b); + console.log(r.toString()); + }); + + test('without carry over', () => { + const a = new RationalNumber(123n, 3); + const b = new RationalNumber(456n, -3); + const r = a.plus(b); + console.log(r.toString()); + }); + + /** + * @see https://mikemcl.github.io/bignumber.js/#plus + */ + test('bignumber.js compatibility', () => { + const a = new RationalNumber(1n, -1); + const b = new RationalNumber(2n, -1); + const c = new RationalNumber(7n, -1); + const r = a.plus(b).plus(c); + console.log(r.toString()); + }); + }); + describe('scale method tests', () => { - describe('negative exponent', () => { + test('scale down', () => { + const rn = new RationalNumber(12345n, -2); + console.log(rn.toString()); + console.log(rn.scale(-4).toString()); + console.log(rn.scale().toString()); + }); + + test('scale up', () => { + const rn = new RationalNumber(12345000n, -2); + console.log(rn.toString()); + console.log(rn.scale(5).toString()); + console.log(rn.scale().toString()); + }); + + describe('negative exponent to zero', () => { test('negative exponent scale to 0', () => { const rn = new RationalNumber(12345000n, -3).scale(); console.log(rn.toString()); @@ -19,7 +60,7 @@ describe('Revision class tests', () => { }); }); - describe('positive exponent', () => { + describe('positive exponent to zero', () => { test('positive exponent = 0', () => { const rn = new RationalNumber(12345n, 0).scale(); console.log(rn.toString()); From 447a7d5d675b29c1e40978b430b2a0b70592bc90 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Thu, 29 Aug 2024 18:45:28 +0100 Subject: [PATCH 13/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 169 ++++++++++++++++++ packages/core/src/vcdm/RationalNumber.ts | 57 ------ packages/core/src/vcdm/index.ts | 2 +- packages/core/tests/vcdm/FPN.unit.test.ts | 130 ++++++++++++++ .../tests/vcdm/RationalNumber.unit.test.ts | 110 ------------ 5 files changed, 300 insertions(+), 168 deletions(-) create mode 100644 packages/core/src/vcdm/FPN.ts delete mode 100644 packages/core/src/vcdm/RationalNumber.ts create mode 100644 packages/core/tests/vcdm/FPN.unit.test.ts delete mode 100644 packages/core/tests/vcdm/RationalNumber.unit.test.ts diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts new file mode 100644 index 000000000..3722325ae --- /dev/null +++ b/packages/core/src/vcdm/FPN.ts @@ -0,0 +1,169 @@ +class FPN { + protected static readonly DEFAULT_FRACTIONAL_DECIMALS = 18n; + + /** + * Scaled Value = value * 10 ^ fd. + * @private + */ + private readonly sv: bigint; + + /** + * Fractional Digits + * @private + */ + private readonly fd: bigint; + + constructor(sv: bigint, fd: bigint) { + this.sv = sv; + this.fd = fd; + } + + public abs(): FPN { + return new FPN(this.sv < 0n ? -this.sv : this.sv, this.fd); + } + + public absoluteValue(): FPN { + return this.abs(); + } + + public comparedTo(that: FPN): number { + const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + const delta = this.scale(fd).sv - that.scale(fd).sv; + return delta < 0n ? -1 : delta === 0n ? -0 : 1; + } + + public div(that: FPN): FPN { + const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + return new FPN(FPN.div(this.scale(fd).sv, that.scale(fd).sv, fd), fd); + } + + private static div(dividend: bigint, divisor: bigint, fd: bigint): bigint { + return (10n ** fd * dividend) / divisor; + } + + public dividedBy(that: FPN): FPN { + return this.div(that); + } + + public exponentiatedBy(that: FPN): FPN { + const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + return new FPN(FPN.pow(this.scale(fd).sv, that.scale(fd).sv, fd), fd); + } + + public minus(that: FPN): FPN { + const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + return new FPN(this.scale(fd).sv - that.scale(fd).sv, fd); + } + + private static mul( + multiplicand: bigint, + multiplicator: bigint, + fd: bigint + ): bigint { + return (multiplicand * multiplicator) / 10n ** fd; + } + + public multipliedBy(that: FPN): FPN { + return this.times(that); + } + + public static of( + exp: number, + fractionalDecimals: bigint = this.DEFAULT_FRACTIONAL_DECIMALS + ): FPN { + return new FPN(this.nToSV(exp, fractionalDecimals), fractionalDecimals); + } + + private static nToSV(n: number, fd: bigint): bigint { + return BigInt(Math.round(n * 10 ** Number(fd))); + } + + public pow(that: FPN): FPN { + const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + return new FPN(FPN.pow(this.scale(fd).sv, that.scale(fd).sv, fd), fd); + } + + private static pow(base: bigint, exponent: bigint, fd: bigint): bigint { + const sf = 10n ** fd; // Scale factor. + if (exponent < 0n) { + return FPN.pow(FPN.div(sf, base, fd), -exponent, fd); // Recursive. + } + if (exponent === 0n) { + return 1n; + } + if (exponent === sf) { + return base; + } + return FPN.pow(this.mul(base, base, fd), exponent - sf, fd); // Recursive. + } + + public plus(that: FPN): FPN { + const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + return new FPN(this.scale(fd).sv + that.scale(fd).sv, fd); + } + + public scale(fd: bigint): FPN { + const dd = fd - this.fd; // Fractional Decimals Difference. + if (dd < 0) { + return new FPN(this.sv / 10n ** -dd, fd); + } else { + return new FPN(this.sv * 10n ** dd, fd); + } + } + + private static sqr(value: bigint, fd: bigint): bigint { + if (value < 0n) { + throw new Error(); + } + const sf = fd * 10n; // Scale Factor. + let iteration = 0; + let actualResult = value; + let storedResult = 0n; + while (actualResult !== storedResult && iteration < sf) { + storedResult = actualResult; + actualResult = + (actualResult + FPN.div(value, actualResult, fd)) / 2n; + iteration++; + } + return actualResult; + } + + public sqrt(): FPN { + return new FPN(FPN.sqr(this.sv, this.fd), this.fd); + } + + public squareRoot(): FPN { + return this.sqrt(); + } + + public times(that: FPN): FPN { + const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + return new FPN(FPN.mul(this.scale(fd).sv, that.scale(fd).sv, fd), fd); + } + + 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 FPN.trimEnd(str.substring(0, str.length - sub.length), sub); + } + return str; + } + + public toString(decimalSeparator = '.'): string { + const sign = this.sv < 0n ? '-' : ''; + const digits = + this.sv < 0n ? (-this.sv).toString() : this.sv.toString(); + const padded = digits.padStart(Number(this.fd), '0'); + const decimals = padded.slice(Number(-this.fd)); + const integers = padded.slice(0, padded.length - decimals.length); + return ( + sign + + (integers.length < 1 ? '0' : integers) + + decimalSeparator + + FPN.trimEnd(decimals) + ); + } +} + +export { FPN }; diff --git a/packages/core/src/vcdm/RationalNumber.ts b/packages/core/src/vcdm/RationalNumber.ts deleted file mode 100644 index db5b7ee15..000000000 --- a/packages/core/src/vcdm/RationalNumber.ts +++ /dev/null @@ -1,57 +0,0 @@ -class RationalNumber { - public readonly mantissa: bigint; - - public readonly exponent: number; - - constructor(mantissa: bigint, exponent: number) { - this.mantissa = mantissa; - this.exponent = exponent; - } - - private static abs(bi: bigint): bigint { - return bi < 0n ? -bi : bi; - } - - public plus(that: RationalNumber): RationalNumber { - const mce = Math.min(this.exponent, that.exponent); // Minimum common exponent. - const a = this.scale(mce); - const b = that.scale(mce); - return new RationalNumber(a.mantissa + b.mantissa, mce).scale(); - } - - public scale(toExponent: number = 0): RationalNumber { - let mantissa = this.mantissa; - let exponent = this.exponent; - if (exponent < toExponent) { - while (mantissa % 10n === 0n) { - mantissa /= 10n; - exponent++; - } - } else if (exponent > toExponent) { - while (exponent > toExponent) { - mantissa *= 10n; - exponent--; - } - } - return new RationalNumber(mantissa, exponent); - } - - public toString(decimalSeparator: string = '.'): string { - const sign = this.mantissa < 0n ? '-' : ''; - const digits = RationalNumber.abs(this.mantissa).toString(); - if (this.exponent < 0) { - const decimals = digits.slice(this.exponent); - const integerLen = digits.length + this.exponent; - if (integerLen > 0) { - const integers = digits.slice(0, integerLen); - return sign + integers + decimalSeparator + decimals; - } else { - const padding = '0'.repeat(-1 * integerLen); - return sign + '0' + decimalSeparator + padding + decimals; - } - } - return sign + digits; - } -} - -export { RationalNumber }; diff --git a/packages/core/src/vcdm/index.ts b/packages/core/src/vcdm/index.ts index 20e873aed..3bebf8499 100644 --- a/packages/core/src/vcdm/index.ts +++ b/packages/core/src/vcdm/index.ts @@ -2,13 +2,13 @@ export * from './account'; export * from './Address'; export * from './BloomFilter'; export * from './Currency'; +export * from './FPN'; export * from './Hash'; export * from './Hex'; export * from './HexInt'; export * from './HexUInt'; export * from './Mnemonic'; export * from './Quantity'; -export * from './RationalNumber'; export * from './Revision'; export * from './ThorId'; export * from './Txt'; diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts new file mode 100644 index 000000000..d14bd6e75 --- /dev/null +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -0,0 +1,130 @@ +import { describe, test } from '@jest/globals'; +import { FPN } from '../../src'; + +describe('FPN class tests', () => { + describe('absoluteValue method tests', () => { + test('positive value', () => { + const a = FPN.of(123.45); + const r = a.absoluteValue(); + console.log(r); + }); + + test('positive value', () => { + const a = FPN.of(-123.45); + const r = a.absoluteValue(); + console.log(r); + }); + }); + + describe('compareTo method tests', () => { + test('less than', () => { + const a = FPN.of(123.45); + const b = a.plus(a); + console.log(a.comparedTo(b)); + }); + + test('equal', () => { + const a = FPN.of(123.45); + const b = a.minus(a); + console.log(a.comparedTo(b)); + }); + }); + + describe('dividedBy method tests', () => { + test('periodic result', () => { + const a = FPN.of(-1); + const b = FPN.of(3, 0n); + const r = a.dividedBy(b); + console.log(r); + }); + }); + + describe('exponentiatedBy method tests', () => { + test('power of < 1', () => { + const b = FPN.of(4, 18n); + const e = FPN.of(-2); + const r = b.exponentiatedBy(e); + console.log(r); + }); + + test('power of 0', () => { + const b = FPN.of(0.7); + const e = FPN.of(0); + const r = b.exponentiatedBy(e); + console.log(r); + }); + + test('power of 1', () => { + const b = FPN.of(0.7); + const e = FPN.of(1); + const r = b.exponentiatedBy(e); + console.log(r); + }); + + test('power of > 1', () => { + const b = FPN.of(1.5); + const e = FPN.of(3); + const r = b.exponentiatedBy(e); + console.log(r); + }); + }); + + describe('minus method tests', () => { + test('positive result', () => { + const a = FPN.of(0.3); + const b = FPN.of(0.1); + const r = a.minus(b); + console.log(r); + }); + }); + + describe('multipliedBy method tests', () => { + test('negative auto scale', () => { + const a = FPN.of(-3.5); + const b = FPN.of(2, 0n); + const r = a.multipliedBy(b); + console.log(r); + }); + }); + + describe('plus method tests', () => { + test('positive result', () => { + const a = FPN.of(5.75); + const b = FPN.of(2.5); + const r = a.plus(b); + console.log(r); + }); + }); + + test('scale', () => { + const a = FPN.of(-1, 18n); + console.log(a); + console.log(a.scale(3n)); + }); + + describe('squareRoot methods tests', () => { + test('integer result', () => { + const a = FPN.of(16); + const r = a.squareRoot(); + console.log(r); + }); + + test('not integer result', () => { + const a = FPN.of(3); + const r = a.squareRoot(); + console.log(r); + }); + }); + + describe('toString methods tests', () => { + test('< 1', () => { + const n = FPN.of(0.0001); + console.log(n.toString()); + console.log(n); + }); + test('> 1', () => { + const n = FPN.of(123.456); + console.log(n.toString()); + }); + }); +}); diff --git a/packages/core/tests/vcdm/RationalNumber.unit.test.ts b/packages/core/tests/vcdm/RationalNumber.unit.test.ts deleted file mode 100644 index d09c30501..000000000 --- a/packages/core/tests/vcdm/RationalNumber.unit.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { describe, test } from '@jest/globals'; -import { RationalNumber } from '../../src'; - -/** - * Test Revision class. - * @group unit/vcdm - */ -describe('Revision class tests', () => { - describe('plus methods tests', () => { - test('with carry over', () => { - const a = new RationalNumber(123n, -2); - const b = new RationalNumber(277n, -2); - const r = a.plus(b); - console.log(r.toString()); - }); - - test('without carry over', () => { - const a = new RationalNumber(123n, 3); - const b = new RationalNumber(456n, -3); - const r = a.plus(b); - console.log(r.toString()); - }); - - /** - * @see https://mikemcl.github.io/bignumber.js/#plus - */ - test('bignumber.js compatibility', () => { - const a = new RationalNumber(1n, -1); - const b = new RationalNumber(2n, -1); - const c = new RationalNumber(7n, -1); - const r = a.plus(b).plus(c); - console.log(r.toString()); - }); - }); - - describe('scale method tests', () => { - test('scale down', () => { - const rn = new RationalNumber(12345n, -2); - console.log(rn.toString()); - console.log(rn.scale(-4).toString()); - console.log(rn.scale().toString()); - }); - - test('scale up', () => { - const rn = new RationalNumber(12345000n, -2); - console.log(rn.toString()); - console.log(rn.scale(5).toString()); - console.log(rn.scale().toString()); - }); - - describe('negative exponent to zero', () => { - test('negative exponent scale to 0', () => { - const rn = new RationalNumber(12345000n, -3).scale(); - console.log(rn.toString()); - }); - - test('negative exponent scale to negative', () => { - const rn = new RationalNumber(12345000n, -5).scale(); - console.log(rn.toString()); - }); - }); - - describe('positive exponent to zero', () => { - test('positive exponent = 0', () => { - const rn = new RationalNumber(12345n, 0).scale(); - console.log(rn.toString()); - }); - - test('positive exponent > 0', () => { - const rn = new RationalNumber(12345n, 3).scale(); - console.log(rn.toString()); - }); - }); - }); - describe('toString method tests', () => { - describe('negative range', () => { - test('negative integer', () => { - const rn = new RationalNumber(-12345n, 0); - console.log(rn.toString()); - }); - - test('negative decimal > -1', () => { - const rn = new RationalNumber(-12345n, -8); - console.log(rn.toString()); - }); - - test('negative decimal < -1', () => { - const rn = new RationalNumber(-12345n, -3); - console.log(rn.toString()); - }); - }); - - describe('positive range', () => { - test('positive decimal < 1', () => { - const rn = new RationalNumber(12345n, -5); - console.log(rn.toString()); - }); - - test('positive decimal > 1', () => { - const rn = new RationalNumber(12345n, -2); - console.log(rn.toString()); - }); - - test('positive integer', () => { - const rn = new RationalNumber(12345n, 0); - console.log(rn.toString()); - }); - }); - }); -}); From 5155413d8b61688aabfa746347b94f25c6c96a7d Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Thu, 29 Aug 2024 19:00:47 +0100 Subject: [PATCH 14/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 3722325ae..ffb74d4d0 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -141,15 +141,6 @@ class FPN { return new FPN(FPN.mul(this.scale(fd).sv, that.scale(fd).sv, fd), fd); } - 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 FPN.trimEnd(str.substring(0, str.length - sub.length), sub); - } - return str; - } - public toString(decimalSeparator = '.'): string { const sign = this.sv < 0n ? '-' : ''; const digits = @@ -164,6 +155,15 @@ class FPN { FPN.trimEnd(decimals) ); } + + 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 FPN.trimEnd(str.substring(0, str.length - sub.length), sub); + } + return str; + } } export { FPN }; From f811feb7419391373dffd5ee581a2ed529a5229e Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Thu, 29 Aug 2024 19:03:40 +0100 Subject: [PATCH 15/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index ffb74d4d0..d8ee10f51 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -26,12 +26,16 @@ class FPN { return this.abs(); } - public comparedTo(that: FPN): number { + public compareTo(that: FPN): number { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. const delta = this.scale(fd).sv - that.scale(fd).sv; return delta < 0n ? -1 : delta === 0n ? -0 : 1; } + public comparedTo(that: FPN): number { + return this.compareTo(that); + } + public div(that: FPN): FPN { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. return new FPN(FPN.div(this.scale(fd).sv, that.scale(fd).sv, fd), fd); From df91ae7307fbbcdf89204cf01187baf8f506d8d5 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Thu, 29 Aug 2024 19:40:07 +0100 Subject: [PATCH 16/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 67 ++++++++++++----------- packages/core/tests/vcdm/FPN.unit.test.ts | 2 +- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index d8ee10f51..7dc4d0b12 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -2,24 +2,24 @@ class FPN { protected static readonly DEFAULT_FRACTIONAL_DECIMALS = 18n; /** - * Scaled Value = value * 10 ^ fd. + * Fractional Digits or decimal places. * @private */ - private readonly sv: bigint; + private readonly fd: bigint; /** - * Fractional Digits + * Scaled Value = value * 10 ^ fd. * @private */ - private readonly fd: bigint; + private readonly sv: bigint; - constructor(sv: bigint, fd: bigint) { - this.sv = sv; + constructor(fd: bigint, sv: bigint) { this.fd = fd; + this.sv = sv; } public abs(): FPN { - return new FPN(this.sv < 0n ? -this.sv : this.sv, this.fd); + return new FPN(this.fd, this.sv < 0n ? -this.sv : this.sv); } public absoluteValue(): FPN { @@ -28,7 +28,7 @@ class FPN { public compareTo(that: FPN): number { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - const delta = this.scale(fd).sv - that.scale(fd).sv; + const delta = this.dp(fd).sv - that.dp(fd).sv; return delta < 0n ? -1 : delta === 0n ? -0 : 1; } @@ -38,10 +38,10 @@ class FPN { public div(that: FPN): FPN { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - return new FPN(FPN.div(this.scale(fd).sv, that.scale(fd).sv, fd), fd); + return new FPN(fd, FPN.div(fd, this.dp(fd).sv, that.dp(fd).sv)); } - private static div(dividend: bigint, divisor: bigint, fd: bigint): bigint { + private static div(fd: bigint, dividend: bigint, divisor: bigint): bigint { return (10n ** fd * dividend) / divisor; } @@ -49,14 +49,28 @@ class FPN { return this.div(that); } + public dp(decimalPlaces: bigint | number): FPN { + const fp = BigInt(decimalPlaces); + const dd = fp - this.fd; // Fractional Decimals Difference. + if (dd < 0) { + return new FPN(fp, this.sv / 10n ** -dd); + } else { + return new FPN(fp, this.sv * 10n ** dd); + } + } + + public decimalPlaces(decimalPlaces: bigint): FPN { + return this.dp(decimalPlaces - this.fd * decimalPlaces); + } + public exponentiatedBy(that: FPN): FPN { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - return new FPN(FPN.pow(this.scale(fd).sv, that.scale(fd).sv, fd), fd); + return new FPN(fd, FPN.pow(fd, this.dp(fd).sv, that.dp(fd).sv)); } public minus(that: FPN): FPN { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - return new FPN(this.scale(fd).sv - that.scale(fd).sv, fd); + return new FPN(fd, this.dp(fd).sv - that.dp(fd).sv); } private static mul( @@ -73,9 +87,9 @@ class FPN { public static of( exp: number, - fractionalDecimals: bigint = this.DEFAULT_FRACTIONAL_DECIMALS + decimalPlaces: bigint = this.DEFAULT_FRACTIONAL_DECIMALS ): FPN { - return new FPN(this.nToSV(exp, fractionalDecimals), fractionalDecimals); + return new FPN(decimalPlaces, this.nToSV(exp, decimalPlaces)); } private static nToSV(n: number, fd: bigint): bigint { @@ -84,13 +98,13 @@ class FPN { public pow(that: FPN): FPN { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - return new FPN(FPN.pow(this.scale(fd).sv, that.scale(fd).sv, fd), fd); + return new FPN(fd, FPN.pow(fd, this.dp(fd).sv, that.dp(fd).sv)); } - private static pow(base: bigint, exponent: bigint, fd: bigint): bigint { + private static pow(fd: bigint, base: bigint, exponent: bigint): bigint { const sf = 10n ** fd; // Scale factor. if (exponent < 0n) { - return FPN.pow(FPN.div(sf, base, fd), -exponent, fd); // Recursive. + return FPN.pow(fd, FPN.div(fd, sf, base), -exponent); // Recursive. } if (exponent === 0n) { return 1n; @@ -98,21 +112,12 @@ class FPN { if (exponent === sf) { return base; } - return FPN.pow(this.mul(base, base, fd), exponent - sf, fd); // Recursive. + return FPN.pow(fd, this.mul(base, base, fd), exponent - sf); // Recursive. } public plus(that: FPN): FPN { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - return new FPN(this.scale(fd).sv + that.scale(fd).sv, fd); - } - - public scale(fd: bigint): FPN { - const dd = fd - this.fd; // Fractional Decimals Difference. - if (dd < 0) { - return new FPN(this.sv / 10n ** -dd, fd); - } else { - return new FPN(this.sv * 10n ** dd, fd); - } + return new FPN(fd, this.dp(fd).sv + that.dp(fd).sv); } private static sqr(value: bigint, fd: bigint): bigint { @@ -126,14 +131,14 @@ class FPN { while (actualResult !== storedResult && iteration < sf) { storedResult = actualResult; actualResult = - (actualResult + FPN.div(value, actualResult, fd)) / 2n; + (actualResult + FPN.div(fd, value, actualResult)) / 2n; iteration++; } return actualResult; } public sqrt(): FPN { - return new FPN(FPN.sqr(this.sv, this.fd), this.fd); + return new FPN(this.fd, FPN.sqr(this.sv, this.fd)); } public squareRoot(): FPN { @@ -142,7 +147,7 @@ class FPN { public times(that: FPN): FPN { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - return new FPN(FPN.mul(this.scale(fd).sv, that.scale(fd).sv, fd), fd); + return new FPN(fd, FPN.mul(this.dp(fd).sv, that.dp(fd).sv, fd)); } public toString(decimalSeparator = '.'): string { diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index d14bd6e75..922f8e1ef 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -99,7 +99,7 @@ describe('FPN class tests', () => { test('scale', () => { const a = FPN.of(-1, 18n); console.log(a); - console.log(a.scale(3n)); + console.log(a.dp(3n)); }); describe('squareRoot methods tests', () => { From 851862a7925ac3effbedb03786225b70b7e40951 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Fri, 30 Aug 2024 22:05:26 +0100 Subject: [PATCH 17/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 323 +++++++++++++++++++-- packages/core/src/vcdm/Hex.ts | 2 +- packages/core/tests/vcdm/FPN.unit.test.ts | 335 ++++++++++++++++++++-- 3 files changed, 612 insertions(+), 48 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 7dc4d0b12..e3f19a662 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -1,5 +1,63 @@ class FPN { - protected static readonly DEFAULT_FRACTIONAL_DECIMALS = 18n; + /** + * 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 fd} and {@link sv} not meaningful. + * + * @see [Number.NaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/NaN). + * + */ + public static readonly NaN = new FPN(0n, 0n, NaN); + + /** + * The negative Infinity value. + * + * @remarks {@link fd} and {@link sv} 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 FPN( + 0n, + 0n, + Number.NEGATIVE_INFINITY + ); + + /** + * The positive Infinite value. + * + * @remarks {@link fd} and {@link sv} 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 FPN( + 0n, + 0n, + Number.POSITIVE_INFINITY + ); + + /** + * Represents the zero constant. + */ + public static readonly ZERO = new FPN(0n, 0n, 0); + + /** + * Edge Flag denotes the {@link NaN} or {@link NEGATIVE_INFINITY} or {@link POSITIVE_INFINITY} value. + * + * @remarks If `ef` is not 0, {@link fd} and {sv} are not meaningful. + * + * @private + */ + private readonly ef: number; /** * Fractional Digits or decimal places. @@ -8,47 +66,174 @@ class FPN { private readonly fd: bigint; /** - * Scaled Value = value * 10 ^ fd. + * Scaled Value = value * 10 ^ {@link fd}. * @private */ private readonly sv: bigint; - constructor(fd: bigint, sv: bigint) { + /** + * Returns the new Fixed-Point Number (FDN) instance having + * + * @param {bigint} fd - Number of Fractional Digits (or decimal places). + * @param {bigint} sv - Scaled Value. + * @param {number} [ef=0] - Edge Flag, default is 0. + */ + protected constructor(fd: bigint, sv: bigint, ef: number = 0) { this.fd = fd; + this.ef = ef; this.sv = sv; } + /** + * Returns a FPN whose value is the absolute value, i.e. the magnitude, of the value of this FPN. + */ public abs(): FPN { return new FPN(this.fd, this.sv < 0n ? -this.sv : this.sv); } + /** + * Returns a FPN whose value is the absolute value, i.e. the magnitude, of the value of this FPN. + * See {@link abs}. + * + * @see [bignumber.js absoluteValue](https://mikemcl.github.io/bignumber.js/#abs). + */ public absoluteValue(): FPN { return this.abs(); } - public compareTo(that: FPN): number { + /** + * Compares this instance with the specified FPN instance. + * * Returns a null if either instance is NaN; + * * Returns 0 if this is equal to `that` FPN, including infinite with equal sign; + * * Returns -1, if this is -Infinite or less than `that` FPN;, + * * Returns 1 if this is +Infinite or greater than `that` FPN. + * + * @param {FPN} 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. + */ + public compareTo(that: FPN): null | number { + if (this.isNaN() || that.isNaN()) return null; + 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.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. const delta = this.dp(fd).sv - that.dp(fd).sv; - return delta < 0n ? -1 : delta === 0n ? -0 : 1; + return delta < 0n ? -1 : delta === 0n ? 0 : 1; } - public comparedTo(that: FPN): number { + /** + * Compares this instance with the specified FPN instance. + * See {@link compareTo}. + * + * @param {FPN} 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. + * + * @see [bignumber.js comparedTo](https://mikemcl.github.io/bignumber.js/#cmp). + */ + public comparedTo(that: FPN): null | number { return this.compareTo(that); } + /** + * Adjusts the precision of the floating-point number by the specified + * number of decimal places. + * See {@link dp}. + * + * @param {bigint | number} decimalPlaces - The number of decimal places to adjust to. + * + * @return {FPN} A new FPN instance with the adjusted precision. + * + * @see [bignumber.js decimalPlaces](https://mikemcl.github.io/bignumber.js/#dp). + */ + public decimalPlaces(decimalPlaces: bigint): FPN { + return this.dp(decimalPlaces - this.fd * decimalPlaces); + } + + /** + * Returns a FPN whose value is the value of this FPN divided by `that` FPN. + * + * Limit cases: + * - NaN / n = NaN; + * - n / NaN = NaN; + * - n / +/-Infinite = 0; + * - -n / 0 = -Infinite; + * - +n / 0 = +Infinite. + * + * @param {FPN} that - The fixed-point number to divide by. + * + * @return {FPN} The result of the division. + * + * @remarks The precision in the higher of the two operands. + */ public div(that: FPN): FPN { + if (this.isNaN() || that.isNaN()) return FPN.NaN; + if (that.isInfinite()) return FPN.ZERO; + if (that.isZero()) + return this.isNegative() + ? FPN.NEGATIVE_INFINITY + : FPN.POSITIVE_INFINITY; const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - return new FPN(fd, FPN.div(fd, this.dp(fd).sv, that.dp(fd).sv)); + try { + return new FPN(fd, FPN.div(fd, this.dp(fd).sv, that.dp(fd).sv)); + } catch (e) { + if (e instanceof RangeError) return FPN.NaN; + else throw e; + } } + /** + * 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 (10n ** fd * dividend) / divisor; + return divisor === 1n ? dividend : (10n ** fd * dividend) / divisor; } + /** + * Returns a FPN whose value is the value of this FPN divided by `that` FPN. + * See {@link div}. + * + * @param {FPN} that - The fixed-point number to divide by. + * + * @see [bignumber.js dividedBy](https://mikemcl.github.io/bignumber.js/#div). + */ public dividedBy(that: FPN): FPN { return this.div(that); } + /** + * Returns a FPN whose value is the integer part of dividing the value of this FPN by `that` FPN. + * See {@link idiv}. + * + * @param {FPN} that - The fixed-point number to divide by. + * + * @see [bignumber.js dividedToIntegerBy](https://mikemcl.github.io/bignumber.js/#divInt). + */ + public dividedToIntegerBy(that: FPN): FPN { + return this.idiv(that); + } + + /** + * Adjusts 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. + * + * @return {FPN} A new FPN instance with the adjusted precision. + */ public dp(decimalPlaces: bigint | number): FPN { const fp = BigInt(decimalPlaces); const dd = fp - this.fd; // Fractional Decimals Difference. @@ -59,15 +244,84 @@ class FPN { } } - public decimalPlaces(decimalPlaces: bigint): FPN { - return this.dp(decimalPlaces - this.fd * decimalPlaces); - } - public exponentiatedBy(that: FPN): FPN { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. return new FPN(fd, FPN.pow(fd, this.dp(fd).sv, that.dp(fd).sv)); } + /** + * 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: + * - NaN / n = NaN; + * - n / NaN = NaN; + * - n / +/-Infinite = 0; + * - -n / 0 = -Infinite; + * - +n / 0 = +Infinite. + * + * @param {FPN} that - The fixed-point number to divide by. + * + * @return {FPN} The result of the division. + * + * @remarks The precision in the higher of the two operands. + */ + public idiv(that: FPN): FPN { + if (this.isNaN() || that.isNaN()) return FPN.NaN; + if (that.isInfinite()) return FPN.ZERO; + if (that.isZero()) + return this.isNegative() + ? FPN.NEGATIVE_INFINITY + : FPN.POSITIVE_INFINITY; + const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + return new FPN(fd, FPN.idiv(fd, this.dp(fd).sv, that.dp(fd).sv)); + } + + /** + * 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) * 10n ** fd; + } + + public isFinite(): boolean { + return this.ef === 0; + } + + public isInfinite(): boolean { + return this.isNegativeInfinite() || this.isPositiveInfinite(); + } + + public isNaN(): boolean { + return Number.isNaN(this.ef); + } + + public isNegative(): boolean { + return (this.isFinite() && this.sv < 0n) || this.isNegativeInfinite(); + } + + public isNegativeInfinite(): boolean { + return this.ef === Number.NEGATIVE_INFINITY; + } + + public isPositive(): boolean { + return (this.isFinite() && this.sv >= 0n) || this.isPositiveInfinite(); + } + + public isPositiveInfinite(): boolean { + return this.ef === Number.POSITIVE_INFINITY; + } + + public isZero(): boolean { + return this.sv === 0n; + } + public minus(that: FPN): FPN { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. return new FPN(fd, this.dp(fd).sv - that.dp(fd).sv); @@ -85,10 +339,23 @@ class FPN { return this.times(that); } + get n(): number { + if (this.isFinite()) 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.sv) * 10 ** -Number(this.fd); + } + public static of( exp: number, decimalPlaces: bigint = this.DEFAULT_FRACTIONAL_DECIMALS ): FPN { + if (Number.isNaN(exp)) return new FPN(decimalPlaces, 0n, Number.NaN); + if (exp === Number.NEGATIVE_INFINITY) + return new FPN(decimalPlaces, -1n, Number.NEGATIVE_INFINITY); + if (exp === Number.POSITIVE_INFINITY) + return new FPN(decimalPlaces, 1n, Number.POSITIVE_INFINITY); return new FPN(decimalPlaces, this.nToSV(exp, decimalPlaces)); } @@ -150,19 +417,27 @@ class FPN { return new FPN(fd, FPN.mul(this.dp(fd).sv, that.dp(fd).sv, fd)); } + toNumber(): number { + return Number(this.sv) * 10 ** -Number(this.fd); + } + public toString(decimalSeparator = '.'): string { - const sign = this.sv < 0n ? '-' : ''; - const digits = - this.sv < 0n ? (-this.sv).toString() : this.sv.toString(); - const padded = digits.padStart(Number(this.fd), '0'); - const decimals = padded.slice(Number(-this.fd)); - const integers = padded.slice(0, padded.length - decimals.length); - return ( - sign + - (integers.length < 1 ? '0' : integers) + - decimalSeparator + - FPN.trimEnd(decimals) - ); + if (this.ef === 0) { + const sign = this.sv < 0n ? '-' : ''; + const digits = + this.sv < 0n ? (-this.sv).toString() : this.sv.toString(); + const padded = digits.padStart(Number(this.fd), '0'); + const decimals = padded.slice(Number(-this.fd)); + const integers = padded.slice(0, padded.length - decimals.length); + const integersShow = integers.length < 1 ? '0' : integers; + const decimalsShow = FPN.trimEnd(decimals); + return ( + sign + + integersShow + + (decimalsShow.length > 0 ? decimalSeparator + decimalsShow : '') + ); + } + return this.ef.toString(); } private static trimEnd(str: string, sub: string = '0'): string { diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index 363ee13aa..1b2b9912b 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -70,7 +70,7 @@ class Hex implements VeChainDataModel { * * {@link NEGATIVE} `-1` if negative, * * {@link POSITIVE} `1` if positive. */ - public readonly sign; + public readonly sign: number; /** * Creates a new instance of this class to represent the value diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 922f8e1ef..02d5a0a35 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -1,41 +1,318 @@ -import { describe, test } from '@jest/globals'; +import { describe, expect, test } from '@jest/globals'; import { FPN } from '../../src'; +import { BigNumber } from 'bignumber.js'; describe('FPN class tests', () => { describe('absoluteValue method tests', () => { - test('positive value', () => { - const a = FPN.of(123.45); - const r = a.absoluteValue(); - console.log(r); + test('n < 0', () => { + const n = -0.8; + const actual = FPN.of(n).abs(); + // console.log(actual.toString()); + const expected = BigNumber(n).abs(); + expect(actual.n).toEqual(expected.toNumber()); }); - test('positive value', () => { - const a = FPN.of(-123.45); - const r = a.absoluteValue(); - console.log(r); + test('n > 0', () => { + const n = 0.8; + const actual = FPN.of(n).abs(); + // console.log(actual.toString()); + const expected = BigNumber(n).abs(); + expect(actual.n).toEqual(expected.toNumber()); }); }); - describe('compareTo method tests', () => { - test('less than', () => { - const a = FPN.of(123.45); - const b = a.plus(a); - console.log(a.comparedTo(b)); + describe('comparedTo method tests', () => { + test('NaN ~ n', () => { + const l = NaN; + const r = 123.45; + const actual = FPN.of(l).comparedTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).comparedTo(BigNumber(r)); + expect(actual).toBe(expected); }); - test('equal', () => { - const a = FPN.of(123.45); - const b = a.minus(a); - console.log(a.comparedTo(b)); + test('n ~ NaN', () => { + const l = 123.45; + const r = NaN; + const actual = FPN.of(l).comparedTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).comparedTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('-Infinity ~ n', () => { + const l = Number.NEGATIVE_INFINITY; + const r = 123.45; + const actual = FPN.of(l).comparedTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).comparedTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('+Infinity ~ n', () => { + const l = Number.POSITIVE_INFINITY; + const r = 123.45; + const actual = FPN.of(l).comparedTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).comparedTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('n ~ -Infinity', () => { + const l = Number.NEGATIVE_INFINITY; + const r = 123.45; + const actual = FPN.of(l).comparedTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).comparedTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('n ~ +Infinity', () => { + const l = 123.45; + const r = Number.NEGATIVE_INFINITY; + const actual = FPN.of(l).comparedTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).comparedTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('-Infinity ~ -Infinity', () => { + const l = Number.NEGATIVE_INFINITY; + const r = Number.NEGATIVE_INFINITY; + const actual = FPN.of(l).comparedTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).comparedTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('-Infinity ~ +Infinity', () => { + const l = Number.NEGATIVE_INFINITY; + const r = Number.POSITIVE_INFINITY; + const actual = FPN.of(l).comparedTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).comparedTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('+Infinity ~ -Infinity', () => { + const l = Number.POSITIVE_INFINITY; + const r = Number.NEGATIVE_INFINITY; + const actual = FPN.of(l).comparedTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).comparedTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('+Infinity ~ +Infinity', () => { + const l = Number.POSITIVE_INFINITY; + const r = Number.POSITIVE_INFINITY; + const actual = FPN.of(l).comparedTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).comparedTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('l < r', () => { + const l = 123.45; + const r = l * 2; + const actual = FPN.of(l).comparedTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).comparedTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('l = r', () => { + const l = 123.45; + const r = l; + const actual = FPN.of(l).comparedTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).comparedTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('l > r', () => { + const l = 123.45; + const r = l / 2; + const actual = FPN.of(l).comparedTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).comparedTo(BigNumber(r)); + expect(actual).toBe(expected); }); }); describe('dividedBy method tests', () => { - test('periodic result', () => { - const a = FPN.of(-1); - const b = FPN.of(3, 0n); - const r = a.dividedBy(b); - console.log(r); + test('0/0 = NaN', () => { + const dividend = 0; + const divisor = 0; + const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).div(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('NaN / n = ', () => { + const dividend = NaN; + const divisor = 123.45; + const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).div(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('n / NaN = NaN', () => { + const dividend = 123.45; + const divisor = NaN; + const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + console.log(actual.toString()); + const expected = BigNumber(dividend).div(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('n / -Infinity = 0', () => { + const dividend = 123.45; + const divisor = Number.NEGATIVE_INFINITY; + const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).div(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('n / +Infinity = 0', () => { + const dividend = 123.45; + const divisor = Number.POSITIVE_INFINITY; + const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).div(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('-n / 0 = -Infinity', () => { + const dividend = -123.45; + const divisor = 0; + const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).div(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('+n / 0 = +Infinity', () => { + const dividend = 123.45; + const divisor = 0; + const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).div(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('x / y = periodic', () => { + const dividend = -1; + const divisor = 3; + const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).div(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('x / y = real', () => { + const dividend = 355; + const divisor = 113; + const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).div(BigNumber(divisor)); + const dp = 15; // BigNumber default precision diverges after 15 digits. + expect(actual.n.toFixed(dp)).toBe(expected.toNumber().toFixed(dp)); + }); + + test('x / y = integer', () => { + const dividend = 355; + const divisor = -5; + const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).div(BigNumber(divisor)); + expect(actual.n).toBe(expected.toNumber()); + }); + }); + + describe('dividedToIntegerBy method tests', () => { + test('0/0 = NaN', () => { + const dividend = 0; + const divisor = 0; + const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('NaN / n = ', () => { + const dividend = NaN; + const divisor = 123.45; + const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('n / NaN = NaN', () => { + const dividend = 123.45; + const divisor = NaN; + const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + console.log(actual.toString()); + const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('n / -Infinity = 0', () => { + const dividend = 123.45; + const divisor = Number.NEGATIVE_INFINITY; + const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('n / +Infinity = 0', () => { + const dividend = 123.45; + const divisor = Number.POSITIVE_INFINITY; + const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('-n / 0 = -Infinity', () => { + const dividend = -123.45; + const divisor = 0; + const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('+n / 0 = +Infinity', () => { + const dividend = 123.45; + const divisor = 0; + const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('n / integer', () => { + const dividend = 5; + const divisor = 3; + const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); + }); + + test('n / rational', () => { + const dividend = 5; + const divisor = 0.7; + const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + // console.log(actual.toString()); + const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + expect(actual.toString()).toBe(expected.toString()); }); }); @@ -126,5 +403,17 @@ describe('FPN class tests', () => { const n = FPN.of(123.456); console.log(n.toString()); }); + test('NaN', () => { + const r = FPN.of(Number.NaN); + console.log(r.toString()); + }); + test('Negative infinite', () => { + const r = FPN.of(Number.NEGATIVE_INFINITY); + console.log(r.toString()); + }); + test('Positive infinite', () => { + const r = FPN.of(Number.POSITIVE_INFINITY); + console.log(r.toString()); + }); }); }); From 140c5a31a5f28de7bde396e6ac02ce025900dd8f Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Fri, 30 Aug 2024 23:11:19 +0100 Subject: [PATCH 18/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 42 +++++-- packages/core/tests/vcdm/FPN.unit.test.ts | 143 ++++++++++++++++++++++ 2 files changed, 174 insertions(+), 11 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index e3f19a662..3bac21830 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -153,14 +153,17 @@ class FPN { * * @see [bignumber.js decimalPlaces](https://mikemcl.github.io/bignumber.js/#dp). */ - public decimalPlaces(decimalPlaces: bigint): FPN { - return this.dp(decimalPlaces - this.fd * decimalPlaces); + // TODO: implement round logic and return number if argument is null. + public decimalPlaces(decimalPlaces: number): FPN { + const dp = BigInt(decimalPlaces); + return this.dp(dp - this.fd * dp); } /** * Returns a FPN whose value is the value of this FPN divided by `that` FPN. * * Limit cases: + * - 0 / 0 = NaN; * - NaN / n = NaN; * - n / NaN = NaN; * - n / +/-Infinite = 0; @@ -177,9 +180,11 @@ class FPN { if (this.isNaN() || that.isNaN()) return FPN.NaN; if (that.isInfinite()) return FPN.ZERO; if (that.isZero()) - return this.isNegative() - ? FPN.NEGATIVE_INFINITY - : FPN.POSITIVE_INFINITY; + return this.isZero() + ? FPN.NaN + : this.isNegative() + ? FPN.NEGATIVE_INFINITY + : FPN.POSITIVE_INFINITY; const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. try { return new FPN(fd, FPN.div(fd, this.dp(fd).sv, that.dp(fd).sv)); @@ -244,6 +249,10 @@ class FPN { } } + public eq(that: FPN): boolean { + return this.isEqualTo(that); + } + public exponentiatedBy(that: FPN): FPN { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. return new FPN(fd, FPN.pow(fd, this.dp(fd).sv, that.dp(fd).sv)); @@ -254,6 +263,7 @@ class FPN { * by `that` fixed point number. * * Limit cases: + * - 0 / 0 = NaN; * - NaN / n = NaN; * - n / NaN = NaN; * - n / +/-Infinite = 0; @@ -270,9 +280,11 @@ class FPN { if (this.isNaN() || that.isNaN()) return FPN.NaN; if (that.isInfinite()) return FPN.ZERO; if (that.isZero()) - return this.isNegative() - ? FPN.NEGATIVE_INFINITY - : FPN.POSITIVE_INFINITY; + return this.isZero() + ? FPN.NaN + : this.isNegative() + ? FPN.NEGATIVE_INFINITY + : FPN.POSITIVE_INFINITY; const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. return new FPN(fd, FPN.idiv(fd, this.dp(fd).sv, that.dp(fd).sv)); } @@ -290,6 +302,14 @@ class FPN { return (dividend / divisor) * 10n ** fd; } + public isEqual(that: FPN): boolean { + return this.eq(that); + } + + public isEqualTo(that: FPN): boolean { + return this.compareTo(that) === 0; + } + public isFinite(): boolean { return this.ef === 0; } @@ -340,7 +360,7 @@ class FPN { } get n(): number { - if (this.isFinite()) return Number.NaN; + 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; @@ -417,8 +437,8 @@ class FPN { return new FPN(fd, FPN.mul(this.dp(fd).sv, that.dp(fd).sv, fd)); } - toNumber(): number { - return Number(this.sv) * 10 ** -Number(this.fd); + public toNumber(): number { + return this.n; } public toString(decimalSeparator = '.'): string { diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 02d5a0a35..6b3f829cc 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -140,6 +140,30 @@ describe('FPN class tests', () => { }); }); + describe('decimalPlaces method tests', () => { + test('scale down', () => { + const n = 1234.56; + const dp = 1; + const actual = FPN.of(n); // .decimalPlaces(dp); + console.log(actual.toString()); + const expected = BigNumber(n).decimalPlaces(dp); + // TODO: expect(actual.n).toBe(expected.toNumber()); + }); + + test('scale up', () => { + const n = 1234.56; + const dp = 4; + const actual = FPN.of(n).decimalPlaces(dp); + console.log(actual.toString()); + const expected = BigNumber(n).decimalPlaces(dp); + // TODO: expect(actual.n).toBe(expected.toNumber()); + }); + + test('no scale', () => { + // TODO + }); + }); + describe('dividedBy method tests', () => { test('0/0 = NaN', () => { const dividend = 0; @@ -346,6 +370,125 @@ describe('FPN class tests', () => { }); }); + describe('isEqualTo method tests', () => { + test('NaN = n', () => { + const l = NaN; + const r = 123.45; + const actual = FPN.of(l).isEqualTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).isEqualTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('n = NaN', () => { + const l = 123.45; + const r = NaN; + const actual = FPN.of(l).isEqualTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).isEqualTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('-Infinity = n', () => { + const l = Number.NEGATIVE_INFINITY; + const r = 123.45; + const actual = FPN.of(l).isEqualTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).isEqualTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('+Infinity = n', () => { + const l = Number.POSITIVE_INFINITY; + const r = 123.45; + const actual = FPN.of(l).isEqualTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).isEqualTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('n = -Infinity', () => { + const l = Number.NEGATIVE_INFINITY; + const r = 123.45; + const actual = FPN.of(l).isEqualTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).isEqualTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('n = +Infinity', () => { + const l = 123.45; + const r = Number.NEGATIVE_INFINITY; + const actual = FPN.of(l).isEqualTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).isEqualTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('-Infinity = -Infinity', () => { + const l = Number.NEGATIVE_INFINITY; + const r = Number.NEGATIVE_INFINITY; + const actual = FPN.of(l).isEqualTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).isEqualTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('-Infinity = +Infinity', () => { + const l = Number.NEGATIVE_INFINITY; + const r = Number.POSITIVE_INFINITY; + const actual = FPN.of(l).isEqualTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).isEqualTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('+Infinity = -Infinity', () => { + const l = Number.POSITIVE_INFINITY; + const r = Number.NEGATIVE_INFINITY; + const actual = FPN.of(l).isEqualTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).isEqualTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('+Infinity = +Infinity', () => { + const l = Number.POSITIVE_INFINITY; + const r = Number.POSITIVE_INFINITY; + const actual = FPN.of(l).isEqualTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).isEqualTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('l < r', () => { + const l = 123.45; + const r = l * 2; + const actual = FPN.of(l).isEqualTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).isEqualTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('l = r', () => { + const l = 123.45; + const r = l; + const actual = FPN.of(l).isEqualTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).isEqualTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('l > r', () => { + const l = 123.45; + const r = l / 2; + const actual = FPN.of(l).isEqualTo(FPN.of(r)); + // console.log(actual); + const expected = BigNumber(l).isEqualTo(BigNumber(r)); + expect(actual).toBe(expected); + }); + }); + describe('minus method tests', () => { test('positive result', () => { const a = FPN.of(0.3); From 2fc6818067a4632a06ff171bf0c475dba1242f8f Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Fri, 30 Aug 2024 23:17:22 +0100 Subject: [PATCH 19/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 3bac21830..b2f5d0cd0 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -302,6 +302,17 @@ class FPN { return (dividend / divisor) * 10n ** fd; } + /** + * Returns `true `if the value of thisFPN is equal to the value of `that` FPN, otherwise returns false. + * + * As with JavaScript, `NaN` does not equal `NaN`. + * + * @param {FPN} that - The FPN to compare against. + * + * @return {boolean} True if the FPN numbers are equal, otherwise false. + * + * @remarks This method uses {@link compareTo} internally. + */ public isEqual(that: FPN): boolean { return this.eq(that); } From 8ee00ac400001ad6d5e1e5c7b7fd9de6137449d9 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Sat, 31 Aug 2024 10:55:15 +0100 Subject: [PATCH 20/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 147 +++++------- packages/core/tests/vcdm/FPN.unit.test.ts | 276 ++++++++++++++++------ 2 files changed, 257 insertions(+), 166 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index b2f5d0cd0..09e439f14 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -86,19 +86,11 @@ class FPN { /** * Returns a FPN whose value is the absolute value, i.e. the magnitude, of the value of this FPN. - */ - public abs(): FPN { - return new FPN(this.fd, this.sv < 0n ? -this.sv : this.sv); - } - - /** - * Returns a FPN whose value is the absolute value, i.e. the magnitude, of the value of this FPN. - * See {@link abs}. * * @see [bignumber.js absoluteValue](https://mikemcl.github.io/bignumber.js/#abs). */ - public absoluteValue(): FPN { - return this.abs(); + public abs(): FPN { + return new FPN(this.fd, this.sv < 0n ? -this.sv : this.sv); } /** @@ -113,6 +105,8 @@ class FPN { * @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. + * + * @see [bignumber.js comparedTo](https://mikemcl.github.io/bignumber.js/#cmp). */ public compareTo(that: FPN): null | number { if (this.isNaN() || that.isNaN()) return null; @@ -126,55 +120,24 @@ class FPN { return delta < 0n ? -1 : delta === 0n ? 0 : 1; } - /** - * Compares this instance with the specified FPN instance. - * See {@link compareTo}. - * - * @param {FPN} 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. - * - * @see [bignumber.js comparedTo](https://mikemcl.github.io/bignumber.js/#cmp). - */ - public comparedTo(that: FPN): null | number { - return this.compareTo(that); - } - - /** - * Adjusts the precision of the floating-point number by the specified - * number of decimal places. - * See {@link dp}. - * - * @param {bigint | number} decimalPlaces - The number of decimal places to adjust to. - * - * @return {FPN} A new FPN instance with the adjusted precision. - * - * @see [bignumber.js decimalPlaces](https://mikemcl.github.io/bignumber.js/#dp). - */ - // TODO: implement round logic and return number if argument is null. - public decimalPlaces(decimalPlaces: number): FPN { - const dp = BigInt(decimalPlaces); - return this.dp(dp - this.fd * dp); - } - /** * Returns a FPN whose value is the value of this FPN divided by `that` FPN. * * Limit cases: - * - 0 / 0 = NaN; - * - NaN / n = NaN; - * - n / NaN = NaN; - * - n / +/-Infinite = 0; - * - -n / 0 = -Infinite; - * - +n / 0 = +Infinite. + * - 0 / 0 = NaN + * - NaN / n = NaN + * - n / NaN = NaN + * - n / ±Infinite = 0 + * - -n / 0 = -Infinite + * - +n / 0 = +Infinite * * @param {FPN} that - The fixed-point number to divide by. * * @return {FPN} The result of the division. * * @remarks The precision in the higher of the two operands. + * + * @see [bignumber.js dividedBy](https://mikemcl.github.io/bignumber.js/#div). */ public div(that: FPN): FPN { if (this.isNaN() || that.isNaN()) return FPN.NaN; @@ -207,30 +170,6 @@ class FPN { return divisor === 1n ? dividend : (10n ** fd * dividend) / divisor; } - /** - * Returns a FPN whose value is the value of this FPN divided by `that` FPN. - * See {@link div}. - * - * @param {FPN} that - The fixed-point number to divide by. - * - * @see [bignumber.js dividedBy](https://mikemcl.github.io/bignumber.js/#div). - */ - public dividedBy(that: FPN): FPN { - return this.div(that); - } - - /** - * Returns a FPN whose value is the integer part of dividing the value of this FPN by `that` FPN. - * See {@link idiv}. - * - * @param {FPN} that - The fixed-point number to divide by. - * - * @see [bignumber.js dividedToIntegerBy](https://mikemcl.github.io/bignumber.js/#divInt). - */ - public dividedToIntegerBy(that: FPN): FPN { - return this.idiv(that); - } - /** * Adjusts the precision of the floating-point number by the specified * number of decimal places. @@ -249,10 +188,6 @@ class FPN { } } - public eq(that: FPN): boolean { - return this.isEqualTo(that); - } - public exponentiatedBy(that: FPN): FPN { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. return new FPN(fd, FPN.pow(fd, this.dp(fd).sv, that.dp(fd).sv)); @@ -263,18 +198,20 @@ class FPN { * by `that` fixed point number. * * Limit cases: - * - 0 / 0 = NaN; - * - NaN / n = NaN; - * - n / NaN = NaN; - * - n / +/-Infinite = 0; - * - -n / 0 = -Infinite; - * - +n / 0 = +Infinite. + * - 0 / 0 = NaN + * - NaN / n = NaN + * - n / NaN = NaN + * - n / ±Infinite = 0 + * - -n / 0 = -Infinite + * - +n / 0 = +Infinite * * @param {FPN} that - The fixed-point number to divide by. * * @return {FPN} The result of the division. * * @remarks The precision in the higher of the two operands. + * + * @see [bignumber.js dividedToIntegerBy](https://mikemcl.github.io/bignumber.js/#divInt). */ public idiv(that: FPN): FPN { if (this.isNaN() || that.isNaN()) return FPN.NaN; @@ -314,10 +251,6 @@ class FPN { * @remarks This method uses {@link compareTo} internally. */ public isEqual(that: FPN): boolean { - return this.eq(that); - } - - public isEqualTo(that: FPN): boolean { return this.compareTo(that) === 0; } @@ -394,11 +327,47 @@ class FPN { return BigInt(Math.round(n * 10 ** Number(fd))); } + /** + * Returns a FPN whose value is the value of this FPN raised to the power of `that` FPN. + * + * Limit cases: + * - NaN ^ e = NaN + * - b ^ NaN = NaN + * - b ^ -Infinite = 0 + * - b ^ 0 = 1 + * - b ^ +Infinite = +Infinite + * - ±Infinite ^ -e = 0 + * - ±Infinite ^ +e = +Infinite + * + * @param {FPN} that - The exponent as a fixed-point number. + * It can be negative, it can be not an integer value + * ([bignumber.js pow](https://mikemcl.github.io/bignumber.js/#pow) + * doesn't support not integer exponents). + * @return {FPN} - The result of raising this fixed-point number to the power of the given exponent. + * + * @remarks The precision in the higher of the two operands. + * + * @see [bignumber.js exponentiatedBy](https://mikemcl.github.io/bignumber.js/#pow) + */ public pow(that: FPN): FPN { + if (this.isNaN() || that.isNaN()) return FPN.NaN; + if (this.isInfinite()) + return that.isNegative() ? FPN.ZERO : FPN.POSITIVE_INFINITY; + if (that.isNegativeInfinite()) return FPN.ZERO; + if (that.isPositiveInfinite()) return FPN.POSITIVE_INFINITY; + if (that.isZero()) return FPN.of(1); const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. return new FPN(fd, FPN.pow(fd, this.dp(fd).sv, that.dp(fd).sv)); } + /** + * Computes the power of a given base raised to a specified exponent. + * + * @param {bigint} fd - The scale factor for decimal precision. + * @param {bigint} base - The base number to be raised to the power. + * @param {bigint} exponent - The exponent to which the base should be raised. + * @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. if (exponent < 0n) { @@ -448,10 +417,6 @@ class FPN { return new FPN(fd, FPN.mul(this.dp(fd).sv, that.dp(fd).sv, fd)); } - public toNumber(): number { - return this.n; - } - public toString(decimalSeparator = '.'): string { if (this.ef === 0) { const sign = this.sv < 0n ? '-' : ''; diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 6b3f829cc..9b0f1c049 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -3,7 +3,7 @@ import { FPN } from '../../src'; import { BigNumber } from 'bignumber.js'; describe('FPN class tests', () => { - describe('absoluteValue method tests', () => { + describe('abs method tests', () => { test('n < 0', () => { const n = -0.8; const actual = FPN.of(n).abs(); @@ -21,11 +21,11 @@ describe('FPN class tests', () => { }); }); - describe('comparedTo method tests', () => { + describe('compareTo method tests', () => { test('NaN ~ n', () => { const l = NaN; const r = 123.45; - const actual = FPN.of(l).comparedTo(FPN.of(r)); + const actual = FPN.of(l).compareTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -34,7 +34,7 @@ describe('FPN class tests', () => { test('n ~ NaN', () => { const l = 123.45; const r = NaN; - const actual = FPN.of(l).comparedTo(FPN.of(r)); + const actual = FPN.of(l).compareTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -43,7 +43,7 @@ describe('FPN class tests', () => { test('-Infinity ~ n', () => { const l = Number.NEGATIVE_INFINITY; const r = 123.45; - const actual = FPN.of(l).comparedTo(FPN.of(r)); + const actual = FPN.of(l).compareTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -52,7 +52,7 @@ describe('FPN class tests', () => { test('+Infinity ~ n', () => { const l = Number.POSITIVE_INFINITY; const r = 123.45; - const actual = FPN.of(l).comparedTo(FPN.of(r)); + const actual = FPN.of(l).compareTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -61,7 +61,7 @@ describe('FPN class tests', () => { test('n ~ -Infinity', () => { const l = Number.NEGATIVE_INFINITY; const r = 123.45; - const actual = FPN.of(l).comparedTo(FPN.of(r)); + const actual = FPN.of(l).compareTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -70,7 +70,7 @@ describe('FPN class tests', () => { test('n ~ +Infinity', () => { const l = 123.45; const r = Number.NEGATIVE_INFINITY; - const actual = FPN.of(l).comparedTo(FPN.of(r)); + const actual = FPN.of(l).compareTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -79,7 +79,7 @@ describe('FPN class tests', () => { test('-Infinity ~ -Infinity', () => { const l = Number.NEGATIVE_INFINITY; const r = Number.NEGATIVE_INFINITY; - const actual = FPN.of(l).comparedTo(FPN.of(r)); + const actual = FPN.of(l).compareTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -88,7 +88,7 @@ describe('FPN class tests', () => { test('-Infinity ~ +Infinity', () => { const l = Number.NEGATIVE_INFINITY; const r = Number.POSITIVE_INFINITY; - const actual = FPN.of(l).comparedTo(FPN.of(r)); + const actual = FPN.of(l).compareTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -97,7 +97,7 @@ describe('FPN class tests', () => { test('+Infinity ~ -Infinity', () => { const l = Number.POSITIVE_INFINITY; const r = Number.NEGATIVE_INFINITY; - const actual = FPN.of(l).comparedTo(FPN.of(r)); + const actual = FPN.of(l).compareTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -106,7 +106,7 @@ describe('FPN class tests', () => { test('+Infinity ~ +Infinity', () => { const l = Number.POSITIVE_INFINITY; const r = Number.POSITIVE_INFINITY; - const actual = FPN.of(l).comparedTo(FPN.of(r)); + const actual = FPN.of(l).compareTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -115,7 +115,7 @@ describe('FPN class tests', () => { test('l < r', () => { const l = 123.45; const r = l * 2; - const actual = FPN.of(l).comparedTo(FPN.of(r)); + const actual = FPN.of(l).compareTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -124,7 +124,7 @@ describe('FPN class tests', () => { test('l = r', () => { const l = 123.45; const r = l; - const actual = FPN.of(l).comparedTo(FPN.of(r)); + const actual = FPN.of(l).compareTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -133,42 +133,42 @@ describe('FPN class tests', () => { test('l > r', () => { const l = 123.45; const r = l / 2; - const actual = FPN.of(l).comparedTo(FPN.of(r)); + const actual = FPN.of(l).compareTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); }); - describe('decimalPlaces method tests', () => { - test('scale down', () => { - const n = 1234.56; - const dp = 1; - const actual = FPN.of(n); // .decimalPlaces(dp); - console.log(actual.toString()); - const expected = BigNumber(n).decimalPlaces(dp); - // TODO: expect(actual.n).toBe(expected.toNumber()); - }); - - test('scale up', () => { - const n = 1234.56; - const dp = 4; - const actual = FPN.of(n).decimalPlaces(dp); - console.log(actual.toString()); - const expected = BigNumber(n).decimalPlaces(dp); - // TODO: expect(actual.n).toBe(expected.toNumber()); - }); - - test('no scale', () => { - // TODO - }); - }); - - describe('dividedBy method tests', () => { + // describe('decimalPlaces method tests', () => { + // test('scale down', () => { + // const n = 1234.56; + // const dp = 1; + // const actual = FPN.of(n); // .decimalPlaces(dp); + // console.log(actual.toString()); + // const expected = BigNumber(n).decimalPlaces(dp); + // // TODO: expect(actual.n).toBe(expected.toNumber()); + // }); + // + // test('scale up', () => { + // const n = 1234.56; + // const dp = 4; + // const actual = FPN.of(n).decimalPlaces(dp); + // console.log(actual.toString()); + // const expected = BigNumber(n).decimalPlaces(dp); + // // TODO: expect(actual.n).toBe(expected.toNumber()); + // }); + // + // test('no scale', () => { + // // TODO + // }); + // }); + + describe('div method tests', () => { test('0/0 = NaN', () => { const dividend = 0; const divisor = 0; - const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + const actual = FPN.of(dividend).div(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -177,7 +177,7 @@ describe('FPN class tests', () => { test('NaN / n = ', () => { const dividend = NaN; const divisor = 123.45; - const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + const actual = FPN.of(dividend).div(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -186,7 +186,7 @@ describe('FPN class tests', () => { test('n / NaN = NaN', () => { const dividend = 123.45; const divisor = NaN; - const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + const actual = FPN.of(dividend).div(FPN.of(divisor)); console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -195,7 +195,7 @@ describe('FPN class tests', () => { test('n / -Infinity = 0', () => { const dividend = 123.45; const divisor = Number.NEGATIVE_INFINITY; - const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + const actual = FPN.of(dividend).div(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -204,7 +204,7 @@ describe('FPN class tests', () => { test('n / +Infinity = 0', () => { const dividend = 123.45; const divisor = Number.POSITIVE_INFINITY; - const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + const actual = FPN.of(dividend).div(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -213,7 +213,7 @@ describe('FPN class tests', () => { test('-n / 0 = -Infinity', () => { const dividend = -123.45; const divisor = 0; - const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + const actual = FPN.of(dividend).div(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -222,7 +222,7 @@ describe('FPN class tests', () => { test('+n / 0 = +Infinity', () => { const dividend = 123.45; const divisor = 0; - const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + const actual = FPN.of(dividend).div(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -231,7 +231,7 @@ describe('FPN class tests', () => { test('x / y = periodic', () => { const dividend = -1; const divisor = 3; - const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + const actual = FPN.of(dividend).div(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -240,7 +240,7 @@ describe('FPN class tests', () => { test('x / y = real', () => { const dividend = 355; const divisor = 113; - const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + const actual = FPN.of(dividend).div(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); const dp = 15; // BigNumber default precision diverges after 15 digits. @@ -250,18 +250,18 @@ describe('FPN class tests', () => { test('x / y = integer', () => { const dividend = 355; const divisor = -5; - const actual = FPN.of(dividend).dividedBy(FPN.of(divisor)); + const actual = FPN.of(dividend).div(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.n).toBe(expected.toNumber()); }); }); - describe('dividedToIntegerBy method tests', () => { + describe('idiv method tests', () => { test('0/0 = NaN', () => { const dividend = 0; const divisor = 0; - const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + const actual = FPN.of(dividend).idiv(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -270,7 +270,7 @@ describe('FPN class tests', () => { test('NaN / n = ', () => { const dividend = NaN; const divisor = 123.45; - const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + const actual = FPN.of(dividend).idiv(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -279,7 +279,7 @@ describe('FPN class tests', () => { test('n / NaN = NaN', () => { const dividend = 123.45; const divisor = NaN; - const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + const actual = FPN.of(dividend).idiv(FPN.of(divisor)); console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -288,7 +288,7 @@ describe('FPN class tests', () => { test('n / -Infinity = 0', () => { const dividend = 123.45; const divisor = Number.NEGATIVE_INFINITY; - const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + const actual = FPN.of(dividend).idiv(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -297,7 +297,7 @@ describe('FPN class tests', () => { test('n / +Infinity = 0', () => { const dividend = 123.45; const divisor = Number.POSITIVE_INFINITY; - const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + const actual = FPN.of(dividend).idiv(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -306,7 +306,7 @@ describe('FPN class tests', () => { test('-n / 0 = -Infinity', () => { const dividend = -123.45; const divisor = 0; - const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + const actual = FPN.of(dividend).idiv(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -315,7 +315,7 @@ describe('FPN class tests', () => { test('+n / 0 = +Infinity', () => { const dividend = 123.45; const divisor = 0; - const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + const actual = FPN.of(dividend).idiv(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -324,7 +324,7 @@ describe('FPN class tests', () => { test('n / integer', () => { const dividend = 5; const divisor = 3; - const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + const actual = FPN.of(dividend).idiv(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); @@ -333,14 +333,140 @@ describe('FPN class tests', () => { test('n / rational', () => { const dividend = 5; const divisor = 0.7; - const actual = FPN.of(dividend).dividedToIntegerBy(FPN.of(divisor)); + const actual = FPN.of(dividend).idiv(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); }); - describe('exponentiatedBy method tests', () => { + describe('pow method tests', () => { + test('NaN ^ ±e', () => { + const b = NaN; + const e = 123.45; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + expect(FPN.of(-b).pow(FPN.of(e))).toBe(actual); + }); + + test('±b ^ NaN', () => { + const b = 123.45; + const e = NaN; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + expect(FPN.of(-b).pow(FPN.of(e))).toBe(actual); + }); + + test('±b ^ -Infinity', () => { + const b = 123.45; + const e = Number.NEGATIVE_INFINITY; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + expect(FPN.of(-b).pow(FPN.of(e))).toBe(actual); + }); + + test('±b ^ +Infinity', () => { + const b = 123.45; + const e = Number.POSITIVE_INFINITY; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + expect(FPN.of(-b).pow(FPN.of(e))).toBe(actual); + }); + + test('-Infinity ^ 0', () => { + const b = Number.NEGATIVE_INFINITY; + const e = 0; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('+Infinity ^ 0', () => { + const b = Number.POSITIVE_INFINITY; + const e = 0; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('-Infinity ^ -e', () => { + const b = Number.NEGATIVE_INFINITY; + const e = -123.45; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('-Infinity ^ +e', () => { + const b = Number.NEGATIVE_INFINITY; + const e = -123.45; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('+Infinity ^ -e', () => { + const b = Number.POSITIVE_INFINITY; + const e = -123.45; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('+Infinity ^ +e', () => { + const b = Number.POSITIVE_INFINITY; + const e = 123.45; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('-Infinity ^ -Infinity', () => { + const b = Number.NEGATIVE_INFINITY; + const e = Number.NEGATIVE_INFINITY; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('-Infinity ^ +Infinity', () => { + const b = Number.NEGATIVE_INFINITY; + const e = Number.POSITIVE_INFINITY; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('+Infinity ^ -Infinity', () => { + const b = Number.NEGATIVE_INFINITY; + const e = Number.POSITIVE_INFINITY; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('+Infinity ^ +Infinity', () => { + const b = Number.NEGATIVE_INFINITY; + const e = Number.POSITIVE_INFINITY; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('±b ^ -e', () => { + const b = 3; + const e = -2; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = BigNumber(b).pow(BigNumber(e)); + // expect(actual.dp(16).n).toBe(expected.toNumber()); + }); + test('-b ^ +e', () => {}); + test('-b ^ -e', () => {}); + test('power of < 1', () => { const b = FPN.of(4, 18n); const e = FPN.of(-2); @@ -370,11 +496,11 @@ describe('FPN class tests', () => { }); }); - describe('isEqualTo method tests', () => { + describe('isEqual method tests', () => { test('NaN = n', () => { const l = NaN; const r = 123.45; - const actual = FPN.of(l).isEqualTo(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); @@ -383,7 +509,7 @@ describe('FPN class tests', () => { test('n = NaN', () => { const l = 123.45; const r = NaN; - const actual = FPN.of(l).isEqualTo(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); @@ -392,7 +518,7 @@ describe('FPN class tests', () => { test('-Infinity = n', () => { const l = Number.NEGATIVE_INFINITY; const r = 123.45; - const actual = FPN.of(l).isEqualTo(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); @@ -401,7 +527,7 @@ describe('FPN class tests', () => { test('+Infinity = n', () => { const l = Number.POSITIVE_INFINITY; const r = 123.45; - const actual = FPN.of(l).isEqualTo(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); @@ -410,7 +536,7 @@ describe('FPN class tests', () => { test('n = -Infinity', () => { const l = Number.NEGATIVE_INFINITY; const r = 123.45; - const actual = FPN.of(l).isEqualTo(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); @@ -419,7 +545,7 @@ describe('FPN class tests', () => { test('n = +Infinity', () => { const l = 123.45; const r = Number.NEGATIVE_INFINITY; - const actual = FPN.of(l).isEqualTo(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); @@ -428,7 +554,7 @@ describe('FPN class tests', () => { test('-Infinity = -Infinity', () => { const l = Number.NEGATIVE_INFINITY; const r = Number.NEGATIVE_INFINITY; - const actual = FPN.of(l).isEqualTo(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); @@ -437,7 +563,7 @@ describe('FPN class tests', () => { test('-Infinity = +Infinity', () => { const l = Number.NEGATIVE_INFINITY; const r = Number.POSITIVE_INFINITY; - const actual = FPN.of(l).isEqualTo(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); @@ -446,7 +572,7 @@ describe('FPN class tests', () => { test('+Infinity = -Infinity', () => { const l = Number.POSITIVE_INFINITY; const r = Number.NEGATIVE_INFINITY; - const actual = FPN.of(l).isEqualTo(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); @@ -455,7 +581,7 @@ describe('FPN class tests', () => { test('+Infinity = +Infinity', () => { const l = Number.POSITIVE_INFINITY; const r = Number.POSITIVE_INFINITY; - const actual = FPN.of(l).isEqualTo(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); @@ -464,7 +590,7 @@ describe('FPN class tests', () => { test('l < r', () => { const l = 123.45; const r = l * 2; - const actual = FPN.of(l).isEqualTo(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); @@ -473,7 +599,7 @@ describe('FPN class tests', () => { test('l = r', () => { const l = 123.45; const r = l; - const actual = FPN.of(l).isEqualTo(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); @@ -482,7 +608,7 @@ describe('FPN class tests', () => { test('l > r', () => { const l = 123.45; const r = l / 2; - const actual = FPN.of(l).isEqualTo(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); From 5be6521f235c4316e8f7e7b60f4974ff45ae77b7 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Sat, 31 Aug 2024 11:03:28 +0100 Subject: [PATCH 21/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 6 ++++- packages/core/tests/vcdm/FPN.unit.test.ts | 30 ++--------------------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 09e439f14..5232e7163 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -352,7 +352,11 @@ class FPN { public pow(that: FPN): FPN { if (this.isNaN() || that.isNaN()) return FPN.NaN; if (this.isInfinite()) - return that.isNegative() ? FPN.ZERO : FPN.POSITIVE_INFINITY; + return that.isZero() + ? FPN.of(1) + : this.isNegative() + ? FPN.ZERO + : FPN.POSITIVE_INFINITY; if (that.isNegativeInfinite()) return FPN.ZERO; if (that.isPositiveInfinite()) return FPN.POSITIVE_INFINITY; if (that.isZero()) return FPN.of(1); diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 9b0f1c049..ac7650eb6 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -7,7 +7,6 @@ describe('FPN class tests', () => { test('n < 0', () => { const n = -0.8; const actual = FPN.of(n).abs(); - // console.log(actual.toString()); const expected = BigNumber(n).abs(); expect(actual.n).toEqual(expected.toNumber()); }); @@ -15,7 +14,6 @@ describe('FPN class tests', () => { test('n > 0', () => { const n = 0.8; const actual = FPN.of(n).abs(); - // console.log(actual.toString()); const expected = BigNumber(n).abs(); expect(actual.n).toEqual(expected.toNumber()); }); @@ -26,7 +24,6 @@ describe('FPN class tests', () => { const l = NaN; const r = 123.45; const actual = FPN.of(l).compareTo(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -35,7 +32,6 @@ describe('FPN class tests', () => { const l = 123.45; const r = NaN; const actual = FPN.of(l).compareTo(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -44,7 +40,6 @@ describe('FPN class tests', () => { const l = Number.NEGATIVE_INFINITY; const r = 123.45; const actual = FPN.of(l).compareTo(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -53,7 +48,6 @@ describe('FPN class tests', () => { const l = Number.POSITIVE_INFINITY; const r = 123.45; const actual = FPN.of(l).compareTo(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -62,7 +56,6 @@ describe('FPN class tests', () => { const l = Number.NEGATIVE_INFINITY; const r = 123.45; const actual = FPN.of(l).compareTo(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -71,7 +64,6 @@ describe('FPN class tests', () => { const l = 123.45; const r = Number.NEGATIVE_INFINITY; const actual = FPN.of(l).compareTo(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -80,7 +72,6 @@ describe('FPN class tests', () => { const l = Number.NEGATIVE_INFINITY; const r = Number.NEGATIVE_INFINITY; const actual = FPN.of(l).compareTo(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -89,7 +80,6 @@ describe('FPN class tests', () => { const l = Number.NEGATIVE_INFINITY; const r = Number.POSITIVE_INFINITY; const actual = FPN.of(l).compareTo(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -98,7 +88,6 @@ describe('FPN class tests', () => { const l = Number.POSITIVE_INFINITY; const r = Number.NEGATIVE_INFINITY; const actual = FPN.of(l).compareTo(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -107,7 +96,6 @@ describe('FPN class tests', () => { const l = Number.POSITIVE_INFINITY; const r = Number.POSITIVE_INFINITY; const actual = FPN.of(l).compareTo(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -116,7 +104,6 @@ describe('FPN class tests', () => { const l = 123.45; const r = l * 2; const actual = FPN.of(l).compareTo(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -464,8 +451,8 @@ describe('FPN class tests', () => { const expected = BigNumber(b).pow(BigNumber(e)); // expect(actual.dp(16).n).toBe(expected.toNumber()); }); - test('-b ^ +e', () => {}); - test('-b ^ -e', () => {}); + test('±b ^ +e', () => {}); + test('power of < 1', () => { const b = FPN.of(4, 18n); @@ -501,7 +488,6 @@ describe('FPN class tests', () => { const l = NaN; const r = 123.45; const actual = FPN.of(l).isEqual(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -510,7 +496,6 @@ describe('FPN class tests', () => { const l = 123.45; const r = NaN; const actual = FPN.of(l).isEqual(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -519,7 +504,6 @@ describe('FPN class tests', () => { const l = Number.NEGATIVE_INFINITY; const r = 123.45; const actual = FPN.of(l).isEqual(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -528,7 +512,6 @@ describe('FPN class tests', () => { const l = Number.POSITIVE_INFINITY; const r = 123.45; const actual = FPN.of(l).isEqual(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -537,7 +520,6 @@ describe('FPN class tests', () => { const l = Number.NEGATIVE_INFINITY; const r = 123.45; const actual = FPN.of(l).isEqual(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -546,7 +528,6 @@ describe('FPN class tests', () => { const l = 123.45; const r = Number.NEGATIVE_INFINITY; const actual = FPN.of(l).isEqual(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -555,7 +536,6 @@ describe('FPN class tests', () => { const l = Number.NEGATIVE_INFINITY; const r = Number.NEGATIVE_INFINITY; const actual = FPN.of(l).isEqual(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -564,7 +544,6 @@ describe('FPN class tests', () => { const l = Number.NEGATIVE_INFINITY; const r = Number.POSITIVE_INFINITY; const actual = FPN.of(l).isEqual(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -573,7 +552,6 @@ describe('FPN class tests', () => { const l = Number.POSITIVE_INFINITY; const r = Number.NEGATIVE_INFINITY; const actual = FPN.of(l).isEqual(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -582,7 +560,6 @@ describe('FPN class tests', () => { const l = Number.POSITIVE_INFINITY; const r = Number.POSITIVE_INFINITY; const actual = FPN.of(l).isEqual(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -591,7 +568,6 @@ describe('FPN class tests', () => { const l = 123.45; const r = l * 2; const actual = FPN.of(l).isEqual(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -600,7 +576,6 @@ describe('FPN class tests', () => { const l = 123.45; const r = l; const actual = FPN.of(l).isEqual(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -609,7 +584,6 @@ describe('FPN class tests', () => { const l = 123.45; const r = l / 2; const actual = FPN.of(l).isEqual(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); From 70874b13c398d5676b30ab4276bbf1ef7e6cd6b1 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Sat, 31 Aug 2024 16:38:09 +0100 Subject: [PATCH 22/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 14 +++++++------- packages/core/tests/vcdm/FPN.unit.test.ts | 14 ++++++++------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 5232e7163..02d234d1a 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -53,7 +53,7 @@ class FPN { /** * Edge Flag denotes the {@link NaN} or {@link NEGATIVE_INFINITY} or {@link POSITIVE_INFINITY} value. * - * @remarks If `ef` is not 0, {@link fd} and {sv} are not meaningful. + * @remarks If `ef` is not zero, {@link fd} and {@link sv} are not meaningful. * * @private */ @@ -76,7 +76,7 @@ class FPN { * * @param {bigint} fd - Number of Fractional Digits (or decimal places). * @param {bigint} sv - Scaled Value. - * @param {number} [ef=0] - Edge Flag, default is 0. + * @param {number} [ef=0] - Edge Flag. */ protected constructor(fd: bigint, sv: bigint, ef: number = 0) { this.fd = fd; @@ -94,7 +94,7 @@ class FPN { } /** - * Compares this instance with the specified FPN instance. + * Compares this instance with `that` FPN instance. * * Returns a null if either instance is NaN; * * Returns 0 if this is equal to `that` FPN, including infinite with equal sign; * * Returns -1, if this is -Infinite or less than `that` FPN;, @@ -135,7 +135,7 @@ class FPN { * * @return {FPN} The result of the division. * - * @remarks The precision in the higher of the two operands. + * @remarks The precision is the greater of the precision of the two operands. * * @see [bignumber.js dividedBy](https://mikemcl.github.io/bignumber.js/#div). */ @@ -209,7 +209,7 @@ class FPN { * * @return {FPN} The result of the division. * - * @remarks The precision in the higher of the two operands. + * @remarks The precision is the greater of the precision of the two operands. * * @see [bignumber.js dividedToIntegerBy](https://mikemcl.github.io/bignumber.js/#divInt). */ @@ -345,7 +345,7 @@ class FPN { * doesn't support not integer exponents). * @return {FPN} - The result of raising this fixed-point number to the power of the given exponent. * - * @remarks The precision in the higher of the two operands. + * @remarks The precision is the greater of the precision of the two operands. * * @see [bignumber.js exponentiatedBy](https://mikemcl.github.io/bignumber.js/#pow) */ @@ -354,7 +354,7 @@ class FPN { if (this.isInfinite()) return that.isZero() ? FPN.of(1) - : this.isNegative() + : that.isNegative() ? FPN.ZERO : FPN.POSITIVE_INFINITY; if (that.isNegativeInfinite()) return FPN.ZERO; diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index ac7650eb6..1a70cea2c 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -334,7 +334,7 @@ describe('FPN class tests', () => { const actual = FPN.of(b).pow(FPN.of(e)); const expected = b ** e; expect(actual.n).toBe(expected); - expect(FPN.of(-b).pow(FPN.of(e))).toBe(actual); + expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); }); test('±b ^ NaN', () => { @@ -343,7 +343,7 @@ describe('FPN class tests', () => { const actual = FPN.of(b).pow(FPN.of(e)); const expected = b ** e; expect(actual.n).toBe(expected); - expect(FPN.of(-b).pow(FPN.of(e))).toBe(actual); + expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); }); test('±b ^ -Infinity', () => { @@ -352,7 +352,7 @@ describe('FPN class tests', () => { const actual = FPN.of(b).pow(FPN.of(e)); const expected = b ** e; expect(actual.n).toBe(expected); - expect(FPN.of(-b).pow(FPN.of(e))).toBe(actual); + expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); }); test('±b ^ +Infinity', () => { @@ -361,7 +361,7 @@ describe('FPN class tests', () => { const actual = FPN.of(b).pow(FPN.of(e)); const expected = b ** e; expect(actual.n).toBe(expected); - expect(FPN.of(-b).pow(FPN.of(e))).toBe(actual); + expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); }); test('-Infinity ^ 0', () => { @@ -449,10 +449,12 @@ describe('FPN class tests', () => { const e = -2; const actual = FPN.of(b).pow(FPN.of(e)); const expected = BigNumber(b).pow(BigNumber(e)); - // expect(actual.dp(16).n).toBe(expected.toNumber()); + const fd = 16; // Fractional digits before divergence. + expect(actual.n.toFixed(fd)).toBe(expected.toNumber().toFixed(fd)); + expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); }); - test('±b ^ +e', () => {}); + test('±b ^ +e', () => {}); test('power of < 1', () => { const b = FPN.of(4, 18n); From 6253c29aaec9776869c0ce3f2e0e18bd5c688f8c Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Sat, 31 Aug 2024 17:00:18 +0100 Subject: [PATCH 23/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 32 +- packages/core/tests/vcdm/FPN.unit.test.ts | 412 ++++++++++++---------- 2 files changed, 243 insertions(+), 201 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 02d234d1a..1fe882f98 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -3,7 +3,7 @@ class FPN { * 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). + * [bignumber.js DECIMAL_PLACES](https://mikemcl.github.io/bignumber.js/#decimal-places) * * @constant {bigint} */ @@ -14,7 +14,7 @@ class FPN { * * @remarks {@link fd} and {@link sv} not meaningful. * - * @see [Number.NaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/NaN). + * @see [Number.NaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/NaN) * */ public static readonly NaN = new FPN(0n, 0n, NaN); @@ -24,7 +24,7 @@ class FPN { * * @remarks {@link fd} and {@link sv} not meaningful. * - * @see [Number.NEGATIVE_INFINITY](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/NEGATIVE_INFINITY). + * @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 FPN( 0n, @@ -37,7 +37,7 @@ class FPN { * * @remarks {@link fd} and {@link sv} not meaningful. * - * @see [Number.POSITIVE_INFINITY](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/POSITIVE_INFINITY). + * @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 FPN( 0n, @@ -87,7 +87,7 @@ class FPN { /** * Returns a FPN whose value is the absolute value, i.e. the magnitude, of the value of this FPN. * - * @see [bignumber.js absoluteValue](https://mikemcl.github.io/bignumber.js/#abs). + * @see [bignumber.js absoluteValue](https://mikemcl.github.io/bignumber.js/#abs) */ public abs(): FPN { return new FPN(this.fd, this.sv < 0n ? -this.sv : this.sv); @@ -106,7 +106,7 @@ class FPN { * -1, 0, or 1 if this instance is less than, equal to, or greater * than the specified instance, respectively. * - * @see [bignumber.js comparedTo](https://mikemcl.github.io/bignumber.js/#cmp). + * @see [bignumber.js comparedTo](https://mikemcl.github.io/bignumber.js/#cmp) */ public compareTo(that: FPN): null | number { if (this.isNaN() || that.isNaN()) return null; @@ -137,7 +137,7 @@ class FPN { * * @remarks The precision is the greater of the precision of the two operands. * - * @see [bignumber.js dividedBy](https://mikemcl.github.io/bignumber.js/#div). + * @see [bignumber.js dividedBy](https://mikemcl.github.io/bignumber.js/#div) */ public div(that: FPN): FPN { if (this.isNaN() || that.isNaN()) return FPN.NaN; @@ -188,11 +188,6 @@ class FPN { } } - public exponentiatedBy(that: FPN): FPN { - const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - return new FPN(fd, FPN.pow(fd, this.dp(fd).sv, that.dp(fd).sv)); - } - /** * 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. @@ -211,7 +206,7 @@ class FPN { * * @remarks The precision is the greater of the precision of the two operands. * - * @see [bignumber.js dividedToIntegerBy](https://mikemcl.github.io/bignumber.js/#divInt). + * @see [bignumber.js dividedToIntegerBy](https://mikemcl.github.io/bignumber.js/#divInt) */ public idiv(that: FPN): FPN { if (this.isNaN() || that.isNaN()) return FPN.NaN; @@ -254,10 +249,21 @@ class FPN { return this.compareTo(that) === 0; } + /** + * Returns `true` if the value of this FPN is a finite number, otherwise returns false. + * + * The only possible non-finite values of a FPN are {@link NaN}, {@link NEGATIVE_INFINITY} and {@link POSITIVE_INFINITY}. + * + * @see [bignumber.js isFinite](https://mikemcl.github.io/bignumber.js/#isF) + */ public isFinite(): boolean { return this.ef === 0; } + /** + * Return `true` if the value of this FPN is {@link NEGATIVE_INFINITY} and {@link POSITIVE_INFINITY}, + * otherwise returns false. + */ public isInfinite(): boolean { return this.isNegativeInfinite() || this.isPositiveInfinite(); } diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 1a70cea2c..c82978dfe 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -37,7 +37,7 @@ describe('FPN class tests', () => { }); test('-Infinity ~ n', () => { - const l = Number.NEGATIVE_INFINITY; + const l = -Infinity; const r = 123.45; const actual = FPN.of(l).compareTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); @@ -45,7 +45,7 @@ describe('FPN class tests', () => { }); test('+Infinity ~ n', () => { - const l = Number.POSITIVE_INFINITY; + const l = Infinity; const r = 123.45; const actual = FPN.of(l).compareTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); @@ -53,7 +53,7 @@ describe('FPN class tests', () => { }); test('n ~ -Infinity', () => { - const l = Number.NEGATIVE_INFINITY; + const l = -Infinity; const r = 123.45; const actual = FPN.of(l).compareTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); @@ -62,39 +62,39 @@ describe('FPN class tests', () => { test('n ~ +Infinity', () => { const l = 123.45; - const r = Number.NEGATIVE_INFINITY; + const r = -Infinity; const actual = FPN.of(l).compareTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); test('-Infinity ~ -Infinity', () => { - const l = Number.NEGATIVE_INFINITY; - const r = Number.NEGATIVE_INFINITY; + const l = -Infinity; + const r = -Infinity; const actual = FPN.of(l).compareTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); test('-Infinity ~ +Infinity', () => { - const l = Number.NEGATIVE_INFINITY; - const r = Number.POSITIVE_INFINITY; + const l = -Infinity; + const r = Infinity; const actual = FPN.of(l).compareTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); test('+Infinity ~ -Infinity', () => { - const l = Number.POSITIVE_INFINITY; - const r = Number.NEGATIVE_INFINITY; + const l = Infinity; + const r = -Infinity; const actual = FPN.of(l).compareTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); test('+Infinity ~ +Infinity', () => { - const l = Number.POSITIVE_INFINITY; - const r = Number.POSITIVE_INFINITY; + const l = Infinity; + const r = Infinity; const actual = FPN.of(l).compareTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -181,7 +181,7 @@ describe('FPN class tests', () => { test('n / -Infinity = 0', () => { const dividend = 123.45; - const divisor = Number.NEGATIVE_INFINITY; + const divisor = -Infinity; const actual = FPN.of(dividend).div(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); @@ -190,7 +190,7 @@ describe('FPN class tests', () => { test('n / +Infinity = 0', () => { const dividend = 123.45; - const divisor = Number.POSITIVE_INFINITY; + const divisor = Infinity; const actual = FPN.of(dividend).div(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); @@ -274,7 +274,7 @@ describe('FPN class tests', () => { test('n / -Infinity = 0', () => { const dividend = 123.45; - const divisor = Number.NEGATIVE_INFINITY; + const divisor = -Infinity; const actual = FPN.of(dividend).idiv(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); @@ -283,7 +283,7 @@ describe('FPN class tests', () => { test('n / +Infinity = 0', () => { const dividend = 123.45; - const divisor = Number.POSITIVE_INFINITY; + const divisor = Infinity; const actual = FPN.of(dividend).idiv(FPN.of(divisor)); // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); @@ -327,164 +327,6 @@ describe('FPN class tests', () => { }); }); - describe('pow method tests', () => { - test('NaN ^ ±e', () => { - const b = NaN; - const e = 123.45; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); - }); - - test('±b ^ NaN', () => { - const b = 123.45; - const e = NaN; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); - }); - - test('±b ^ -Infinity', () => { - const b = 123.45; - const e = Number.NEGATIVE_INFINITY; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); - }); - - test('±b ^ +Infinity', () => { - const b = 123.45; - const e = Number.POSITIVE_INFINITY; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); - }); - - test('-Infinity ^ 0', () => { - const b = Number.NEGATIVE_INFINITY; - const e = 0; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - }); - - test('+Infinity ^ 0', () => { - const b = Number.POSITIVE_INFINITY; - const e = 0; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - }); - - test('-Infinity ^ -e', () => { - const b = Number.NEGATIVE_INFINITY; - const e = -123.45; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - }); - - test('-Infinity ^ +e', () => { - const b = Number.NEGATIVE_INFINITY; - const e = -123.45; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - }); - - test('+Infinity ^ -e', () => { - const b = Number.POSITIVE_INFINITY; - const e = -123.45; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - }); - - test('+Infinity ^ +e', () => { - const b = Number.POSITIVE_INFINITY; - const e = 123.45; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - }); - - test('-Infinity ^ -Infinity', () => { - const b = Number.NEGATIVE_INFINITY; - const e = Number.NEGATIVE_INFINITY; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - }); - - test('-Infinity ^ +Infinity', () => { - const b = Number.NEGATIVE_INFINITY; - const e = Number.POSITIVE_INFINITY; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - }); - - test('+Infinity ^ -Infinity', () => { - const b = Number.NEGATIVE_INFINITY; - const e = Number.POSITIVE_INFINITY; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - }); - - test('+Infinity ^ +Infinity', () => { - const b = Number.NEGATIVE_INFINITY; - const e = Number.POSITIVE_INFINITY; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = b ** e; - expect(actual.n).toBe(expected); - }); - - test('±b ^ -e', () => { - const b = 3; - const e = -2; - const actual = FPN.of(b).pow(FPN.of(e)); - const expected = BigNumber(b).pow(BigNumber(e)); - const fd = 16; // Fractional digits before divergence. - expect(actual.n.toFixed(fd)).toBe(expected.toNumber().toFixed(fd)); - expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); - }); - - test('±b ^ +e', () => {}); - - test('power of < 1', () => { - const b = FPN.of(4, 18n); - const e = FPN.of(-2); - const r = b.exponentiatedBy(e); - console.log(r); - }); - - test('power of 0', () => { - const b = FPN.of(0.7); - const e = FPN.of(0); - const r = b.exponentiatedBy(e); - console.log(r); - }); - - test('power of 1', () => { - const b = FPN.of(0.7); - const e = FPN.of(1); - const r = b.exponentiatedBy(e); - console.log(r); - }); - - test('power of > 1', () => { - const b = FPN.of(1.5); - const e = FPN.of(3); - const r = b.exponentiatedBy(e); - console.log(r); - }); - }); - describe('isEqual method tests', () => { test('NaN = n', () => { const l = NaN; @@ -503,7 +345,7 @@ describe('FPN class tests', () => { }); test('-Infinity = n', () => { - const l = Number.NEGATIVE_INFINITY; + const l = -Infinity; const r = 123.45; const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).isEqualTo(BigNumber(r)); @@ -511,7 +353,7 @@ describe('FPN class tests', () => { }); test('+Infinity = n', () => { - const l = Number.POSITIVE_INFINITY; + const l = Infinity; const r = 123.45; const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).isEqualTo(BigNumber(r)); @@ -519,7 +361,7 @@ describe('FPN class tests', () => { }); test('n = -Infinity', () => { - const l = Number.NEGATIVE_INFINITY; + const l = -Infinity; const r = 123.45; const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).isEqualTo(BigNumber(r)); @@ -528,39 +370,39 @@ describe('FPN class tests', () => { test('n = +Infinity', () => { const l = 123.45; - const r = Number.NEGATIVE_INFINITY; + const r = -Infinity; const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); test('-Infinity = -Infinity', () => { - const l = Number.NEGATIVE_INFINITY; - const r = Number.NEGATIVE_INFINITY; + const l = -Infinity; + const r = -Infinity; const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); test('-Infinity = +Infinity', () => { - const l = Number.NEGATIVE_INFINITY; - const r = Number.POSITIVE_INFINITY; + const l = -Infinity; + const r = Infinity; const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); test('+Infinity = -Infinity', () => { - const l = Number.POSITIVE_INFINITY; - const r = Number.NEGATIVE_INFINITY; + const l = Infinity; + const r = -Infinity; const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); }); test('+Infinity = +Infinity', () => { - const l = Number.POSITIVE_INFINITY; - const r = Number.POSITIVE_INFINITY; + const l = Infinity; + const r = Infinity; const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).isEqualTo(BigNumber(r)); expect(actual).toBe(expected); @@ -591,6 +433,62 @@ describe('FPN class tests', () => { }); }); + describe('isFinite method tests', () => { + test('NaN -> false', () => { + const n = NaN; + const actual = FPN.of(n).isFinite(); + const expected = BigNumber(n).isFinite(); + expect(actual).toBe(expected); + }); + + test('-Infinite -> false', () => { + const n = -Infinity; + const actual = FPN.of(n).isFinite(); + const expected = BigNumber(n).isFinite(); + expect(actual).toBe(expected); + }); + + test('+Infinite -> false', () => { + const n = Infinity; + const actual = FPN.of(n).isFinite(); + const expected = BigNumber(n).isFinite(); + expect(actual).toBe(expected); + }); + + test('n -> true', () => { + const n = 123.45; + const actual = FPN.of(n).isFinite(); + const expected = BigNumber(n).isFinite(); + expect(actual).toBe(expected); + }); + }); + + describe(`isInfinite method tests`, () => { + test('NaN -> false', () => { + const n = NaN; + const actual = FPN.of(n).isInfinite(); + expect(actual).toBe(false); + }); + + test('-Infinite -> true', () => { + const n = -Infinity; + const actual = FPN.of(n).isInfinite(); + expect(actual).toBe(true); + }); + + test('+Infinite -> true', () => { + const n = Infinity; + const actual = FPN.of(n).isInfinite(); + expect(actual).toBe(true); + }); + + test('n -> false', () => { + const n = 0; + const actual = FPN.of(n).isInfinite(); + expect(actual).toBe(false); + }); + }); + describe('minus method tests', () => { test('positive result', () => { const a = FPN.of(0.3); @@ -618,6 +516,144 @@ describe('FPN class tests', () => { }); }); + describe('pow method tests', () => { + test('NaN ^ ±e', () => { + const b = NaN; + const e = 123.45; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); + }); + + test('±b ^ NaN', () => { + const b = 123.45; + const e = NaN; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); + }); + + test('±b ^ -Infinity', () => { + const b = 123.45; + const e = -Infinity; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); + }); + + test('±b ^ +Infinity', () => { + const b = 123.45; + const e = Infinity; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); + }); + + test('-Infinity ^ 0', () => { + const b = -Infinity; + const e = 0; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('+Infinity ^ 0', () => { + const b = Infinity; + const e = 0; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('-Infinity ^ -e', () => { + const b = -Infinity; + const e = -123.45; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('-Infinity ^ +e', () => { + const b = -Infinity; + const e = -123.45; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('+Infinity ^ -e', () => { + const b = Infinity; + const e = -123.45; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('+Infinity ^ +e', () => { + const b = Infinity; + const e = 123.45; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('-Infinity ^ -Infinity', () => { + const b = -Infinity; + const e = -Infinity; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('-Infinity ^ +Infinity', () => { + const b = -Infinity; + const e = Infinity; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('+Infinity ^ -Infinity', () => { + const b = -Infinity; + const e = Infinity; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('+Infinity ^ +Infinity', () => { + const b = -Infinity; + const e = Infinity; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = b ** e; + expect(actual.n).toBe(expected); + }); + + test('±b ^ -e', () => { + const b = 3; + const e = -2; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = BigNumber(b).pow(BigNumber(e)); + const fd = 16; // Fractional digits before divergence. + expect(actual.n.toFixed(fd)).toBe(expected.toNumber().toFixed(fd)); + expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); + }); + + test('±b ^ +e', () => { + const b = 0.7; + const e = -2; + const actual = FPN.of(b).pow(FPN.of(e)); + const expected = BigNumber(b).pow(BigNumber(e)); + const fd = 14; // Fractional digits before divergence. + expect(actual.n.toFixed(fd)).toBe(expected.toNumber().toFixed(fd)); + expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); + }); + }); + test('scale', () => { const a = FPN.of(-1, 18n); console.log(a); @@ -653,11 +689,11 @@ describe('FPN class tests', () => { console.log(r.toString()); }); test('Negative infinite', () => { - const r = FPN.of(Number.NEGATIVE_INFINITY); + const r = FPN.of(-Infinity); console.log(r.toString()); }); test('Positive infinite', () => { - const r = FPN.of(Number.POSITIVE_INFINITY); + const r = FPN.of(Infinity); console.log(r.toString()); }); }); From 0828015658ff7f1027cd1caf35881ef4108a8a09 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Sat, 31 Aug 2024 18:03:38 +0100 Subject: [PATCH 24/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 87 ++++++--- packages/core/tests/vcdm/FPN.unit.test.ts | 214 ++++++++++++++++------ 2 files changed, 226 insertions(+), 75 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 1fe882f98..dd74cd6bd 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -1,3 +1,5 @@ +import { InvalidOperation } from '@vechain/sdk-errors'; + class FPN { /** * The default number of decimal places to use for fixed-point math. @@ -95,21 +97,25 @@ class FPN { /** * Compares this instance with `that` FPN instance. - * * Returns a null if either instance is NaN; * * Returns 0 if this is equal to `that` FPN, including infinite with equal sign; * * Returns -1, if this is -Infinite or less than `that` FPN;, * * Returns 1 if this is +Infinite or greater than `that` FPN. * * @param {FPN} 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 + * @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` FPN is {@link NaN}. + * * @see [bignumber.js comparedTo](https://mikemcl.github.io/bignumber.js/#cmp) */ - public compareTo(that: FPN): null | number { - if (this.isNaN() || that.isNaN()) return null; + public compareTo(that: FPN): number { + if (this.isNaN() || that.isNaN()) + throw new InvalidOperation('FPN.compareTo', 'compare between NaN', { + this: `${this}`, + that: `${that}` + }); if (this.isNegativeInfinite()) return that.isNegativeInfinite() ? 0 : -1; if (this.isPositiveInfinite()) return that.isPositiveInfinite() ? 0 : 1; @@ -120,6 +126,31 @@ class FPN { return delta < 0n ? -1 : delta === 0n ? 0 : 1; } + /** + * Compares this instance with `that` FPN instance. + * * **Returns a null if either instance is NaN;** + * * Returns 0 if this is equal to `that` FPN, including infinite with equal sign; + * * Returns -1, if this is -Infinite or less than `that` FPN;, + * * Returns 1 if this is +Infinite or greater than `that` FPN. + * + * @param {FPN} 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: FPN): null | number { + try { + return this.compareTo(that); + } catch (e) { + return null; + } + } + /** * Returns a FPN whose value is the value of this FPN divided by `that` FPN. * @@ -188,6 +219,35 @@ class FPN { } } + /** + * Returns `true `if the value of thisFPN is equal to the value of `that` FPN, otherwise returns `false`. + * + * As with JavaScript, `NaN` does not equal `NaN`. + * + * @param {FPN} that - The FPN to compare against. + * + * @return {boolean} `true` if the FPN numbers are equal, otherwise `false`. + * + * @remarks This method uses {@link comparedTo} internally. + */ + public eq(that: FPN): boolean { + return this.comparedTo(that) === 0; + } + + /** + * Returns `true` if the value of this FPN is greater than `that` FPN`, otherwise returns `false`. + * + * @param {FPN} that - The FPN to compare against. + * + * @return {boolean} `true` if this FPN is greater than `that` FPN, otherwise `false`. + * + * @remarks This method uses {@link comparedTo} internally. + */ + public gt(that: FPN): 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. @@ -235,22 +295,7 @@ class FPN { } /** - * Returns `true `if the value of thisFPN is equal to the value of `that` FPN, otherwise returns false. - * - * As with JavaScript, `NaN` does not equal `NaN`. - * - * @param {FPN} that - The FPN to compare against. - * - * @return {boolean} True if the FPN numbers are equal, otherwise false. - * - * @remarks This method uses {@link compareTo} internally. - */ - public isEqual(that: FPN): boolean { - return this.compareTo(that) === 0; - } - - /** - * Returns `true` if the value of this FPN is a finite number, otherwise returns false. + * Returns `true` if the value of this FPN is a finite number, otherwise returns `false`. * * The only possible non-finite values of a FPN are {@link NaN}, {@link NEGATIVE_INFINITY} and {@link POSITIVE_INFINITY}. * diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index c82978dfe..ea906056f 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -19,11 +19,11 @@ describe('FPN class tests', () => { }); }); - describe('compareTo method tests', () => { + describe('comparedTo method tests', () => { test('NaN ~ n', () => { const l = NaN; const r = 123.45; - const actual = FPN.of(l).compareTo(FPN.of(r)); + const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -31,7 +31,7 @@ describe('FPN class tests', () => { test('n ~ NaN', () => { const l = 123.45; const r = NaN; - const actual = FPN.of(l).compareTo(FPN.of(r)); + const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -39,7 +39,7 @@ describe('FPN class tests', () => { test('-Infinity ~ n', () => { const l = -Infinity; const r = 123.45; - const actual = FPN.of(l).compareTo(FPN.of(r)); + const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -47,7 +47,7 @@ describe('FPN class tests', () => { test('+Infinity ~ n', () => { const l = Infinity; const r = 123.45; - const actual = FPN.of(l).compareTo(FPN.of(r)); + const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -55,7 +55,7 @@ describe('FPN class tests', () => { test('n ~ -Infinity', () => { const l = -Infinity; const r = 123.45; - const actual = FPN.of(l).compareTo(FPN.of(r)); + const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -63,7 +63,7 @@ describe('FPN class tests', () => { test('n ~ +Infinity', () => { const l = 123.45; const r = -Infinity; - const actual = FPN.of(l).compareTo(FPN.of(r)); + const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -71,7 +71,7 @@ describe('FPN class tests', () => { test('-Infinity ~ -Infinity', () => { const l = -Infinity; const r = -Infinity; - const actual = FPN.of(l).compareTo(FPN.of(r)); + const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -79,7 +79,7 @@ describe('FPN class tests', () => { test('-Infinity ~ +Infinity', () => { const l = -Infinity; const r = Infinity; - const actual = FPN.of(l).compareTo(FPN.of(r)); + const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -87,7 +87,7 @@ describe('FPN class tests', () => { test('+Infinity ~ -Infinity', () => { const l = Infinity; const r = -Infinity; - const actual = FPN.of(l).compareTo(FPN.of(r)); + const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -95,7 +95,7 @@ describe('FPN class tests', () => { test('+Infinity ~ +Infinity', () => { const l = Infinity; const r = Infinity; - const actual = FPN.of(l).compareTo(FPN.of(r)); + const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -103,7 +103,7 @@ describe('FPN class tests', () => { test('l < r', () => { const l = 123.45; const r = l * 2; - const actual = FPN.of(l).compareTo(FPN.of(r)); + const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); }); @@ -111,7 +111,7 @@ describe('FPN class tests', () => { test('l = r', () => { const l = 123.45; const r = l; - const actual = FPN.of(l).compareTo(FPN.of(r)); + const actual = FPN.of(l).comparedTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -120,7 +120,7 @@ describe('FPN class tests', () => { test('l > r', () => { const l = 123.45; const r = l / 2; - const actual = FPN.of(l).compareTo(FPN.of(r)); + const actual = FPN.of(l).comparedTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); @@ -327,108 +327,214 @@ describe('FPN class tests', () => { }); }); - describe('isEqual method tests', () => { - test('NaN = n', () => { + describe('eq method tests', () => { + test('NaN = n -> false', () => { const l = NaN; const r = 123.45; - const actual = FPN.of(l).isEqual(FPN.of(r)); - const expected = BigNumber(l).isEqualTo(BigNumber(r)); + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); }); - test('n = NaN', () => { + test('n = NaN -> false', () => { const l = 123.45; const r = NaN; - const actual = FPN.of(l).isEqual(FPN.of(r)); - const expected = BigNumber(l).isEqualTo(BigNumber(r)); + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); }); - test('-Infinity = n', () => { + test('-Infinity = n -> false', () => { const l = -Infinity; const r = 123.45; - const actual = FPN.of(l).isEqual(FPN.of(r)); - const expected = BigNumber(l).isEqualTo(BigNumber(r)); + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); }); - test('+Infinity = n', () => { + test('+Infinity = n -> false', () => { const l = Infinity; const r = 123.45; - const actual = FPN.of(l).isEqual(FPN.of(r)); - const expected = BigNumber(l).isEqualTo(BigNumber(r)); + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); }); - test('n = -Infinity', () => { + test('n = -Infinity -> false', () => { const l = -Infinity; const r = 123.45; - const actual = FPN.of(l).isEqual(FPN.of(r)); - const expected = BigNumber(l).isEqualTo(BigNumber(r)); + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); }); - test('n = +Infinity', () => { + test('n = +Infinity -> false', () => { const l = 123.45; const r = -Infinity; - const actual = FPN.of(l).isEqual(FPN.of(r)); - const expected = BigNumber(l).isEqualTo(BigNumber(r)); + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); }); - test('-Infinity = -Infinity', () => { + test('-Infinity = -Infinity -> true', () => { const l = -Infinity; const r = -Infinity; - const actual = FPN.of(l).isEqual(FPN.of(r)); - const expected = BigNumber(l).isEqualTo(BigNumber(r)); + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); }); - test('-Infinity = +Infinity', () => { + test('-Infinity = +Infinity -> false', () => { const l = -Infinity; const r = Infinity; - const actual = FPN.of(l).isEqual(FPN.of(r)); - const expected = BigNumber(l).isEqualTo(BigNumber(r)); + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); }); - test('+Infinity = -Infinity', () => { + test('+Infinity = -Infinity -> false', () => { const l = Infinity; const r = -Infinity; - const actual = FPN.of(l).isEqual(FPN.of(r)); - const expected = BigNumber(l).isEqualTo(BigNumber(r)); + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); }); - test('+Infinity = +Infinity', () => { + test('+Infinity = +Infinity -> false', () => { const l = Infinity; const r = Infinity; - const actual = FPN.of(l).isEqual(FPN.of(r)); - const expected = BigNumber(l).isEqualTo(BigNumber(r)); + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); }); - test('l < r', () => { + test('l < r -> false', () => { const l = 123.45; const r = l * 2; - const actual = FPN.of(l).isEqual(FPN.of(r)); - const expected = BigNumber(l).isEqualTo(BigNumber(r)); + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); }); - test('l = r', () => { + test('l = r -> true', () => { const l = 123.45; const r = l; - const actual = FPN.of(l).isEqual(FPN.of(r)); - const expected = BigNumber(l).isEqualTo(BigNumber(r)); + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); }); - test('l > r', () => { + test('l > r -> false', () => { + const l = 123.45; + const r = l / 2; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + }); + }); + + describe('gt method tests', () => { + test('NaN = n -> false', () => { + const l = NaN; + const r = 123.45; + const actual = FPN.of(l).gt(FPN.of(r)); + const expected = BigNumber(l).gt(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('n = NaN -> false', () => { + const l = 123.45; + const r = NaN; + const actual = FPN.of(l).gt(FPN.of(r)); + const expected = BigNumber(l).gt(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('-Infinity = n -> false', () => { + const l = -Infinity; + const r = 123.45; + const actual = FPN.of(l).gt(FPN.of(r)); + const expected = BigNumber(l).gt(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('+Infinity = n -> false', () => { + const l = Infinity; + const r = 123.45; + const actual = FPN.of(l).gt(FPN.of(r)); + const expected = BigNumber(l).gt(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('n = -Infinity -> false', () => { + const l = -Infinity; + const r = 123.45; + const actual = FPN.of(l).gt(FPN.of(r)); + const expected = BigNumber(l).gt(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('n = +Infinity -> false', () => { + const l = 123.45; + const r = -Infinity; + const actual = FPN.of(l).gt(FPN.of(r)); + const expected = BigNumber(l).gt(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('-Infinity = -Infinity -> true', () => { + const l = -Infinity; + const r = -Infinity; + const actual = FPN.of(l).gt(FPN.of(r)); + const expected = BigNumber(l).gt(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('-Infinity = +Infinity -> false', () => { + const l = -Infinity; + const r = Infinity; + const actual = FPN.of(l).gt(FPN.of(r)); + const expected = BigNumber(l).gt(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('+Infinity = -Infinity -> false', () => { + const l = Infinity; + const r = -Infinity; + const actual = FPN.of(l).gt(FPN.of(r)); + const expected = BigNumber(l).gt(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('+Infinity = +Infinity -> false', () => { + const l = Infinity; + const r = Infinity; + const actual = FPN.of(l).gt(FPN.of(r)); + const expected = BigNumber(l).gt(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('l < r -> false', () => { + const l = 123.45; + const r = l * 2; + const actual = FPN.of(l).gt(FPN.of(r)); + const expected = BigNumber(l).gt(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('l = r -> true', () => { + const l = 123.45; + const r = l; + const actual = FPN.of(l).gt(FPN.of(r)); + const expected = BigNumber(l).gt(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('l > r -> false', () => { const l = 123.45; const r = l / 2; - const actual = FPN.of(l).isEqual(FPN.of(r)); - const expected = BigNumber(l).isEqualTo(BigNumber(r)); + const actual = FPN.of(l).gt(FPN.of(r)); + const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); }); }); From f1e43f2430f003dfb7b239731d61e33c3d4fdb5a Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Sat, 31 Aug 2024 19:31:47 +0100 Subject: [PATCH 25/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 18 ++ packages/core/tests/vcdm/FPN.unit.test.ts | 230 ++++++++++++++++++---- 2 files changed, 213 insertions(+), 35 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index dd74cd6bd..67a337c74 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -242,12 +242,30 @@ class FPN { * @return {boolean} `true` if this FPN is greater than `that` FPN, otherwise `false`. * * @remarks This method uses {@link comparedTo} internally. + * + * @see [bignummber.js isGreaterThan](https://mikemcl.github.io/bignumber.js/#gt) */ public gt(that: FPN): boolean { const cmp = this.comparedTo(that); return cmp !== null && cmp > 0; } + /** + * Returns `true` if the value of this FPN is greater or equal than `that` FPN`, otherwise returns `false`. + * + * @param {FPN} that - The FPN to compare against. + * + * @return {boolean} `true` if this FPN is greater or equal than `that` FPN, otherwise `false`. + * + * @remarks This method uses {@link comparedTo} internally. + * + * @see [bignumber.js isGreaterThanOrEqualTo](https://mikemcl.github.io/bignumber.js/#gte) + */ + public gte(that: FPN): 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. diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index ea906056f..33a066ffa 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -20,15 +20,16 @@ describe('FPN class tests', () => { }); describe('comparedTo method tests', () => { - test('NaN ~ n', () => { + test('NaN ~ n -> null', () => { const l = NaN; const r = 123.45; const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(null); }); - test('n ~ NaN', () => { + test('n ~ NaN -> null', () => { const l = 123.45; const r = NaN; const actual = FPN.of(l).comparedTo(FPN.of(r)); @@ -36,94 +37,105 @@ describe('FPN class tests', () => { expect(actual).toBe(expected); }); - test('-Infinity ~ n', () => { + test('-Infinity ~ n -> -1', () => { const l = -Infinity; const r = 123.45; const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(-1); }); - test('+Infinity ~ n', () => { + test('+Infinity ~ n -> 1', () => { const l = Infinity; const r = 123.45; const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(1); }); - test('n ~ -Infinity', () => { - const l = -Infinity; - const r = 123.45; + test('n ~ -Infinity -> 1', () => { + const l = 123.45; + const r = -Infinity; const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(1); }); - test('n ~ +Infinity', () => { + test('n ~ +Infinity -> -1', () => { const l = 123.45; - const r = -Infinity; + const r = +Infinity; const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(-1); }); - test('-Infinity ~ -Infinity', () => { + test('-Infinity ~ -Infinity -> 0', () => { const l = -Infinity; const r = -Infinity; const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(0); }); - test('-Infinity ~ +Infinity', () => { + test('-Infinity ~ +Infinity -> -1', () => { const l = -Infinity; const r = Infinity; const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(-1); }); - test('+Infinity ~ -Infinity', () => { + test('+Infinity ~ -Infinity -> 1', () => { const l = Infinity; const r = -Infinity; const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(1); }); - test('+Infinity ~ +Infinity', () => { + test('+Infinity ~ +Infinity -> 0', () => { const l = Infinity; const r = Infinity; const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(0); }); - test('l < r', () => { + test('l < r -> -1', () => { const l = 123.45; const r = l * 2; const actual = FPN.of(l).comparedTo(FPN.of(r)); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(-1); }); - test('l = r', () => { + test('l = r -> 0', () => { const l = 123.45; const r = l; const actual = FPN.of(l).comparedTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(0); }); - test('l > r', () => { + test('l > r -> 1', () => { const l = 123.45; const r = l / 2; const actual = FPN.of(l).comparedTo(FPN.of(r)); // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(1); }); }); @@ -334,6 +346,7 @@ describe('FPN class tests', () => { const actual = FPN.of(l).eq(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); test('n = NaN -> false', () => { @@ -342,6 +355,7 @@ describe('FPN class tests', () => { const actual = FPN.of(l).eq(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); test('-Infinity = n -> false', () => { @@ -350,6 +364,7 @@ describe('FPN class tests', () => { const actual = FPN.of(l).eq(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); test('+Infinity = n -> false', () => { @@ -358,22 +373,25 @@ describe('FPN class tests', () => { const actual = FPN.of(l).eq(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); test('n = -Infinity -> false', () => { - const l = -Infinity; - const r = 123.45; + const l = 123.45; + const r = -Infinity; const actual = FPN.of(l).eq(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); test('n = +Infinity -> false', () => { const l = 123.45; - const r = -Infinity; + const r = +Infinity; const actual = FPN.of(l).eq(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); test('-Infinity = -Infinity -> true', () => { @@ -382,6 +400,7 @@ describe('FPN class tests', () => { const actual = FPN.of(l).eq(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(true); }); test('-Infinity = +Infinity -> false', () => { @@ -390,6 +409,7 @@ describe('FPN class tests', () => { const actual = FPN.of(l).eq(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); test('+Infinity = -Infinity -> false', () => { @@ -398,14 +418,16 @@ describe('FPN class tests', () => { const actual = FPN.of(l).eq(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); - test('+Infinity = +Infinity -> false', () => { + test('+Infinity = +Infinity -> true', () => { const l = Infinity; const r = Infinity; const actual = FPN.of(l).eq(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(true); }); test('l < r -> false', () => { @@ -414,6 +436,7 @@ describe('FPN class tests', () => { const actual = FPN.of(l).eq(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); test('l = r -> true', () => { @@ -422,6 +445,7 @@ describe('FPN class tests', () => { const actual = FPN.of(l).eq(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(true); }); test('l > r -> false', () => { @@ -430,88 +454,99 @@ describe('FPN class tests', () => { const actual = FPN.of(l).eq(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); }); describe('gt method tests', () => { - test('NaN = n -> false', () => { + test('NaN > n -> false', () => { const l = NaN; const r = 123.45; const actual = FPN.of(l).gt(FPN.of(r)); const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); - test('n = NaN -> false', () => { + test('n > NaN -> false', () => { const l = 123.45; const r = NaN; const actual = FPN.of(l).gt(FPN.of(r)); const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); - test('-Infinity = n -> false', () => { + test('-Infinity > n -> false', () => { const l = -Infinity; const r = 123.45; const actual = FPN.of(l).gt(FPN.of(r)); const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); - test('+Infinity = n -> false', () => { + test('+Infinity > n -> true', () => { const l = Infinity; const r = 123.45; const actual = FPN.of(l).gt(FPN.of(r)); const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(true); }); - test('n = -Infinity -> false', () => { - const l = -Infinity; - const r = 123.45; + test('n > -Infinity -> true', () => { + const l = -123.45; + const r = -Infinity; const actual = FPN.of(l).gt(FPN.of(r)); const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(true); }); - test('n = +Infinity -> false', () => { + test('n > +Infinity -> false', () => { const l = 123.45; - const r = -Infinity; + const r = +Infinity; const actual = FPN.of(l).gt(FPN.of(r)); const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); - test('-Infinity = -Infinity -> true', () => { + test('-Infinity > -Infinity -> false', () => { const l = -Infinity; const r = -Infinity; const actual = FPN.of(l).gt(FPN.of(r)); const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); - test('-Infinity = +Infinity -> false', () => { + test('-Infinity > +Infinity -> false', () => { const l = -Infinity; const r = Infinity; const actual = FPN.of(l).gt(FPN.of(r)); const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); - test('+Infinity = -Infinity -> false', () => { + test('+Infinity > -Infinity -> true', () => { const l = Infinity; const r = -Infinity; const actual = FPN.of(l).gt(FPN.of(r)); const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(true); }); - test('+Infinity = +Infinity -> false', () => { + test('+Infinity > +Infinity -> false', () => { const l = Infinity; const r = Infinity; const actual = FPN.of(l).gt(FPN.of(r)); const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); test('l < r -> false', () => { @@ -520,22 +555,143 @@ describe('FPN class tests', () => { const actual = FPN.of(l).gt(FPN.of(r)); const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); - test('l = r -> true', () => { + test('l = r -> false', () => { const l = 123.45; const r = l; const actual = FPN.of(l).gt(FPN.of(r)); const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(false); }); - test('l > r -> false', () => { + test('l > r -> true', () => { const l = 123.45; const r = l / 2; const actual = FPN.of(l).gt(FPN.of(r)); const expected = BigNumber(l).gt(BigNumber(r)); expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + }); + + describe('gte method tests', () => { + test('NaN >= n -> false', () => { + const l = NaN; + const r = 123.45; + const actual = FPN.of(l).gte(FPN.of(r)); + const expected = BigNumber(l).gte(BigNumber(r)); + expect(actual).toBe(expected); + }); + + test('n >= NaN -> false', () => { + const l = 123.45; + const r = NaN; + const actual = FPN.of(l).gte(FPN.of(r)); + const expected = BigNumber(l).gte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('-Infinity > n -> false', () => { + const l = -Infinity; + const r = 123.45; + const actual = FPN.of(l).gte(FPN.of(r)); + const expected = BigNumber(l).gte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('+Infinity >= n -> true', () => { + const l = Infinity; + const r = 123.45; + const actual = FPN.of(l).gte(FPN.of(r)); + const expected = BigNumber(l).gte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('n >= -Infinity -> true', () => { + const l = 123.45; + const r = -Infinity; + const actual = FPN.of(l).gte(FPN.of(r)); + const expected = BigNumber(l).gte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('n >= +Infinity -> false', () => { + const l = 123.45; + const r = +Infinity; + const actual = FPN.of(l).gte(FPN.of(r)); + const expected = BigNumber(l).gte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('-Infinity >= -Infinity -> true', () => { + const l = -Infinity; + const r = -Infinity; + const actual = FPN.of(l).gte(FPN.of(r)); + const expected = BigNumber(l).gte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('-Infinity >= +Infinity -> false', () => { + const l = -Infinity; + const r = Infinity; + const actual = FPN.of(l).gte(FPN.of(r)); + const expected = BigNumber(l).gte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('+Infinity >= -Infinity -> true', () => { + const l = Infinity; + const r = -Infinity; + const actual = FPN.of(l).gte(FPN.of(r)); + const expected = BigNumber(l).gte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('+Infinity >= +Infinity -> false', () => { + const l = Infinity; + const r = Infinity; + const actual = FPN.of(l).gte(FPN.of(r)); + const expected = BigNumber(l).gte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('l < r -> false', () => { + const l = 123.45; + const r = l * 2; + const actual = FPN.of(l).gte(FPN.of(r)); + const expected = BigNumber(l).gte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('l = r -> true', () => { + const l = 123.45; + const r = l; + const actual = FPN.of(l).gte(FPN.of(r)); + const expected = BigNumber(l).gte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('l > r -> true', () => { + const l = 123.45; + const r = l / 2; + const actual = FPN.of(l).gte(FPN.of(r)); + const expected = BigNumber(l).gte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); }); }); @@ -545,6 +701,7 @@ describe('FPN class tests', () => { const actual = FPN.of(n).isFinite(); const expected = BigNumber(n).isFinite(); expect(actual).toBe(expected); + expect(actual).toBe(false); }); test('-Infinite -> false', () => { @@ -552,6 +709,7 @@ describe('FPN class tests', () => { const actual = FPN.of(n).isFinite(); const expected = BigNumber(n).isFinite(); expect(actual).toBe(expected); + expect(actual).toBe(false); }); test('+Infinite -> false', () => { @@ -559,6 +717,7 @@ describe('FPN class tests', () => { const actual = FPN.of(n).isFinite(); const expected = BigNumber(n).isFinite(); expect(actual).toBe(expected); + expect(actual).toBe(false); }); test('n -> true', () => { @@ -566,6 +725,7 @@ describe('FPN class tests', () => { const actual = FPN.of(n).isFinite(); const expected = BigNumber(n).isFinite(); expect(actual).toBe(expected); + expect(actual).toBe(true); }); }); From 8d2258413143d0d3294ff9ec374ea2adad6a693b Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 10:49:54 +0100 Subject: [PATCH 26/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 58 ++++++++++++++++++++--- packages/core/tests/vcdm/FPN.unit.test.ts | 48 +++++++++++++++++-- 2 files changed, 96 insertions(+), 10 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 67a337c74..cec43d657 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -89,6 +89,8 @@ class FPN { /** * Returns a FPN whose value is the absolute value, i.e. the magnitude, of the value of this FPN. * + * @retrun the absolute value of this FPN. + * * @see [bignumber.js absoluteValue](https://mikemcl.github.io/bignumber.js/#abs) */ public abs(): FPN { @@ -306,7 +308,7 @@ class FPN { * @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. + * @return {bigint} - The scaled result of the integer division. */ private static idiv(fd: bigint, dividend: bigint, divisor: bigint): bigint { return (dividend / divisor) * 10n ** fd; @@ -317,6 +319,8 @@ class FPN { * * The only possible non-finite values of a FPN are {@link NaN}, {@link NEGATIVE_INFINITY} and {@link POSITIVE_INFINITY}. * + * @return `true` if the value of this FPN is a finite number, otherwise returns `false`. + * * @see [bignumber.js isFinite](https://mikemcl.github.io/bignumber.js/#isF) */ public isFinite(): boolean { @@ -326,11 +330,28 @@ class FPN { /** * Return `true` if the value of this FPN is {@link NEGATIVE_INFINITY} and {@link POSITIVE_INFINITY}, * otherwise returns false. + * + * @return true` if the value of this FPN is {@link NEGATIVE_INFINITY} and {@link POSITIVE_INFINITY}, */ public isInfinite(): boolean { return this.isNegativeInfinite() || this.isPositiveInfinite(); } + /** + * Returns `true` if the value of this FPN is an integer, + * otherwise returns `false`. + * + * @return `true` if the value of this FPN is an integer. + * + * @see [bignumber.js isInteger](https://mikemcl.github.io/bignumber.js/#isInt) + */ + public isInteger(): boolean { + if (this.isFinite()) { + return this.sv % 10n ** this.fd === 0n; + } + return false; + } + public isNaN(): boolean { return Number.isNaN(this.ef); } @@ -389,11 +410,10 @@ class FPN { return new FPN(decimalPlaces, -1n, Number.NEGATIVE_INFINITY); if (exp === Number.POSITIVE_INFINITY) return new FPN(decimalPlaces, 1n, Number.POSITIVE_INFINITY); - return new FPN(decimalPlaces, this.nToSV(exp, decimalPlaces)); - } - - private static nToSV(n: number, fd: bigint): bigint { - return BigInt(Math.round(n * 10 ** Number(fd))); + return new FPN( + decimalPlaces, + this.txtToSV(exp.toString(), decimalPlaces) + ); } /** @@ -517,6 +537,32 @@ class FPN { } return str; } + + 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 = 10n ** 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) + // Sign and Integer part + (sf * BigInt(fe)) / BigInt(10 ** fe.length) // Fractional part. + ); + } } export { FPN }; diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 33a066ffa..8ac5f9c73 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -731,8 +731,7 @@ describe('FPN class tests', () => { describe(`isInfinite method tests`, () => { test('NaN -> false', () => { - const n = NaN; - const actual = FPN.of(n).isInfinite(); + const actual = FPN.of(NaN).isInfinite(); expect(actual).toBe(false); }); @@ -743,8 +742,7 @@ describe('FPN class tests', () => { }); test('+Infinite -> true', () => { - const n = Infinity; - const actual = FPN.of(n).isInfinite(); + const actual = FPN.of(Infinity).isInfinite(); expect(actual).toBe(true); }); @@ -755,6 +753,48 @@ describe('FPN class tests', () => { }); }); + describe('isInteger method tests', () => { + test('NaN -> false', () => { + const n = NaN; + const actual = FPN.of(n).isInteger(); + const expected = BigNumber(n).isInteger(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('-Infinite -> false', () => { + const n = Infinity; + const actual = FPN.of(n).isInteger(); + const expected = BigNumber(n).isInteger(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('+Infinite -> false', () => { + const n = Infinity; + const actual = FPN.of(n).isInteger(); + const expected = BigNumber(n).isInteger(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('not integer -> false', () => { + const n = 123.45; + const actual = FPN.of(n).isInteger(); + const expected = BigNumber(n).isInteger(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('integer -> true', () => { + const n = 12345; + const actual = FPN.of(n).isInteger(); + const expected = BigNumber(n).isInteger(); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + }); + describe('minus method tests', () => { test('positive result', () => { const a = FPN.of(0.3); From 0c72ead453bb149f93a5b096eacdbccb79d7b4a2 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 11:11:53 +0100 Subject: [PATCH 27/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 23 +++ packages/core/tests/vcdm/FPN.unit.test.ts | 176 +++++++++++++++++++--- 2 files changed, 175 insertions(+), 24 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index cec43d657..6b30f5eab 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -352,6 +352,13 @@ class FPN { return false; } + /** + * Returns `true` if the value of this FPN is `NaN`, otherwise returns `false`. + * + * @return `true` if the value of this FPN is `NaN`, otherwise returns `false`. + * + * @see [bignumber.js isNaN](https://mikemcl.github.io/bignumber.js/#isNaN) + */ public isNaN(): boolean { return Number.isNaN(this.ef); } @@ -376,6 +383,22 @@ class FPN { return this.sv === 0n; } + /** + * Returns `true` if the value of this FPN is less than the value of `that` FPN, otherwise returns `false`. + * + * @param {FPN} that - The FPN to compare against. + * + * @return `true` if the value of this FPN is less than the value of `that` FPN, otherwise returns `false`. + * + * @remarks This method uses {@link comparedTo} internally. + * + * @see [bignumber.js isLessThan](https://mikemcl.github.io/bignumber.js/#lt) + */ + public lt(that: FPN): boolean { + const cmp = this.comparedTo(that); + return cmp !== null && cmp < 0; + } + public minus(that: FPN): FPN { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. return new FPN(fd, this.dp(fd).sv - that.dp(fd).sv); diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 8ac5f9c73..0f3146cf1 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -139,30 +139,6 @@ describe('FPN class tests', () => { }); }); - // describe('decimalPlaces method tests', () => { - // test('scale down', () => { - // const n = 1234.56; - // const dp = 1; - // const actual = FPN.of(n); // .decimalPlaces(dp); - // console.log(actual.toString()); - // const expected = BigNumber(n).decimalPlaces(dp); - // // TODO: expect(actual.n).toBe(expected.toNumber()); - // }); - // - // test('scale up', () => { - // const n = 1234.56; - // const dp = 4; - // const actual = FPN.of(n).decimalPlaces(dp); - // console.log(actual.toString()); - // const expected = BigNumber(n).decimalPlaces(dp); - // // TODO: expect(actual.n).toBe(expected.toNumber()); - // }); - // - // test('no scale', () => { - // // TODO - // }); - // }); - describe('div method tests', () => { test('0/0 = NaN', () => { const dividend = 0; @@ -795,6 +771,158 @@ describe('FPN class tests', () => { }); }); + describe(`isNaN method tests`, () => { + test('NaN -> true', () => { + const n = NaN; + const actual = FPN.of(n).isNaN(); + const expected = BigNumber(n).isNaN(); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + test('-Infinite -> false', () => { + const n = -Infinity; + const actual = FPN.of(n).isNaN(); + const expected = BigNumber(n).isNaN(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('+Infinite -> false', () => { + const n = Infinity; + const actual = FPN.of(n).isNaN(); + const expected = BigNumber(n).isNaN(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('finite -> false', () => { + const n = 0; + const actual = FPN.of(n).isNaN(); + const expected = BigNumber(n).isNaN(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + }); + + describe('lt method tests', () => { + test('NaN < n -> false', () => { + const l = NaN; + const r = 123.45; + const actual = FPN.of(l).lt(FPN.of(r)); + const expected = BigNumber(l).lt(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('n < NaN -> false', () => { + const l = 123.45; + const r = NaN; + const actual = FPN.of(l).lt(FPN.of(r)); + const expected = BigNumber(l).lt(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('-Infinity < n -> true', () => { + const l = -Infinity; + const r = 123.45; + const actual = FPN.of(l).lt(FPN.of(r)); + const expected = BigNumber(l).lt(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('+Infinity < n -> false', () => { + const l = Infinity; + const r = 123.45; + const actual = FPN.of(l).lt(FPN.of(r)); + const expected = BigNumber(l).lt(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('n < -Infinity -> false', () => { + const l = -123.45; + const r = -Infinity; + const actual = FPN.of(l).lt(FPN.of(r)); + const expected = BigNumber(l).lt(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('n < +Infinity -> true', () => { + const l = 123.45; + const r = +Infinity; + const actual = FPN.of(l).lt(FPN.of(r)); + const expected = BigNumber(l).lt(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('-Infinity < -Infinity -> false', () => { + const l = -Infinity; + const r = -Infinity; + const actual = FPN.of(l).lt(FPN.of(r)); + const expected = BigNumber(l).lt(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('-Infinity < +Infinity -> true', () => { + const l = -Infinity; + const r = Infinity; + const actual = FPN.of(l).lt(FPN.of(r)); + const expected = BigNumber(l).lt(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('+Infinity < -Infinity -> false', () => { + const l = Infinity; + const r = -Infinity; + const actual = FPN.of(l).lt(FPN.of(r)); + const expected = BigNumber(l).lt(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('+Infinity < +Infinity -> false', () => { + const l = Infinity; + const r = Infinity; + const actual = FPN.of(l).lt(FPN.of(r)); + const expected = BigNumber(l).lt(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('l < r -> true', () => { + const l = 123.45; + const r = l * 2; + const actual = FPN.of(l).lt(FPN.of(r)); + const expected = BigNumber(l).lt(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('l = r -> false', () => { + const l = 123.45; + const r = l; + const actual = FPN.of(l).lt(FPN.of(r)); + const expected = BigNumber(l).lt(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('l > r -> false', () => { + const l = 123.45; + const r = l / 2; + const actual = FPN.of(l).lt(FPN.of(r)); + const expected = BigNumber(l).lt(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + }); + describe('minus method tests', () => { test('positive result', () => { const a = FPN.of(0.3); From bc1c958c405a4a559ca7efa981ace7a5c6fb71b7 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 11:30:41 +0100 Subject: [PATCH 28/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 23 +++ packages/core/tests/vcdm/FPN.unit.test.ts | 187 ++++++++++++++++++++-- 2 files changed, 201 insertions(+), 9 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 6b30f5eab..b7bce4e7c 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -363,6 +363,13 @@ class FPN { return Number.isNaN(this.ef); } + /** + * Returns `true` if the sign of this FPN is negative, otherwise returns `false`. + * + * @return `true` if the sign of this FPN is negative, otherwise returns `false`. + * + * @see [bignumber.js isNegative](https://mikemcl.github.io/bignumber.js/#isNeg) + */ public isNegative(): boolean { return (this.isFinite() && this.sv < 0n) || this.isNegativeInfinite(); } @@ -399,6 +406,22 @@ class FPN { return cmp !== null && cmp < 0; } + /** + * Returns `true` if the value of this FPN is less than or equal to the value of `that` FPN, + * otherwise returns `false`. + * + * @param {FPN} that true` if the value of this FPN is less than or equal to the value of `that` FPN, + * otherwise returns `false`. + * + * @remarks This method uses {@link comparedTo} internally. + * + * @see [bignumber.js isLessThanOrEqualTo](https://mikemcl.github.io/bignumber.js/#lte) + */ + public lte(that: FPN): boolean { + const cmp = this.comparedTo(that); + return cmp !== null && cmp <= 0; + } + public minus(that: FPN): FPN { const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. return new FPN(fd, this.dp(fd).sv - that.dp(fd).sv); diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 0f3146cf1..7704f5965 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -554,7 +554,7 @@ describe('FPN class tests', () => { }); describe('gte method tests', () => { - test('NaN >= n -> false', () => { + test('NaN > n -> false', () => { const l = NaN; const r = 123.45; const actual = FPN.of(l).gte(FPN.of(r)); @@ -562,7 +562,7 @@ describe('FPN class tests', () => { expect(actual).toBe(expected); }); - test('n >= NaN -> false', () => { + test('n > NaN -> false', () => { const l = 123.45; const r = NaN; const actual = FPN.of(l).gte(FPN.of(r)); @@ -580,7 +580,7 @@ describe('FPN class tests', () => { expect(actual).toBe(false); }); - test('+Infinity >= n -> true', () => { + test('+Infinity > n -> true', () => { const l = Infinity; const r = 123.45; const actual = FPN.of(l).gte(FPN.of(r)); @@ -589,7 +589,7 @@ describe('FPN class tests', () => { expect(actual).toBe(true); }); - test('n >= -Infinity -> true', () => { + test('n > -Infinity -> true', () => { const l = 123.45; const r = -Infinity; const actual = FPN.of(l).gte(FPN.of(r)); @@ -598,7 +598,7 @@ describe('FPN class tests', () => { expect(actual).toBe(true); }); - test('n >= +Infinity -> false', () => { + test('n > +Infinity -> false', () => { const l = 123.45; const r = +Infinity; const actual = FPN.of(l).gte(FPN.of(r)); @@ -607,7 +607,7 @@ describe('FPN class tests', () => { expect(actual).toBe(false); }); - test('-Infinity >= -Infinity -> true', () => { + test('-Infinity > -Infinity -> true', () => { const l = -Infinity; const r = -Infinity; const actual = FPN.of(l).gte(FPN.of(r)); @@ -616,7 +616,7 @@ describe('FPN class tests', () => { expect(actual).toBe(true); }); - test('-Infinity >= +Infinity -> false', () => { + test('-Infinity > +Infinity -> false', () => { const l = -Infinity; const r = Infinity; const actual = FPN.of(l).gte(FPN.of(r)); @@ -625,7 +625,7 @@ describe('FPN class tests', () => { expect(actual).toBe(false); }); - test('+Infinity >= -Infinity -> true', () => { + test('+Infinity > -Infinity -> true', () => { const l = Infinity; const r = -Infinity; const actual = FPN.of(l).gte(FPN.of(r)); @@ -634,7 +634,7 @@ describe('FPN class tests', () => { expect(actual).toBe(true); }); - test('+Infinity >= +Infinity -> false', () => { + test('+Infinity > +Infinity -> false', () => { const l = Infinity; const r = Infinity; const actual = FPN.of(l).gte(FPN.of(r)); @@ -804,6 +804,56 @@ describe('FPN class tests', () => { }); }); + describe('isNegative method tests', () => { + test('NaN -> false', () => { + const n = NaN; + const actual = FPN.of(n).isNegative(); + const expected = BigNumber(n).isNegative(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('-Infinite -> true', () => { + const n = -Infinity; + const actual = FPN.of(n).isNegative(); + const expected = BigNumber(n).isNegative(); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('+Infinite -> false', () => { + const n = Infinity; + const actual = FPN.of(n).isNegative(); + const expected = BigNumber(n).isNegative(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('-n -> true', () => { + const n = -123.45; + const actual = FPN.of(n).isNegative(); + const expected = BigNumber(n).isNegative(); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('0 -> false', () => { + const n = 0; + const actual = FPN.of(n).isNegative(); + const expected = BigNumber(n).isNegative(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('n -> false', () => { + const n = 123.45; + const actual = FPN.of(n).isNegative(); + const expected = BigNumber(n).isNegative(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + }); + describe('lt method tests', () => { test('NaN < n -> false', () => { const l = NaN; @@ -923,6 +973,125 @@ describe('FPN class tests', () => { }); }); + describe('lte method tests', () => { + test('NaN < n -> false', () => { + const l = NaN; + const r = 123.45; + const actual = FPN.of(l).lte(FPN.of(r)); + const expected = BigNumber(l).lte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('n < NaN -> false', () => { + const l = 123.45; + const r = NaN; + const actual = FPN.of(l).lte(FPN.of(r)); + const expected = BigNumber(l).lte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('-Infinity < n -> true', () => { + const l = -Infinity; + const r = 123.45; + const actual = FPN.of(l).lte(FPN.of(r)); + const expected = BigNumber(l).lte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('+Infinity < n -> false', () => { + const l = Infinity; + const r = 123.45; + const actual = FPN.of(l).lte(FPN.of(r)); + const expected = BigNumber(l).lte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('n < -Infinity -> false', () => { + const l = -123.45; + const r = -Infinity; + const actual = FPN.of(l).lte(FPN.of(r)); + const expected = BigNumber(l).lte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('n < +Infinity -> true', () => { + const l = 123.45; + const r = +Infinity; + const actual = FPN.of(l).lte(FPN.of(r)); + const expected = BigNumber(l).lte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('-Infinity < -Infinity -> true', () => { + const l = -Infinity; + const r = -Infinity; + const actual = FPN.of(l).lte(FPN.of(r)); + const expected = BigNumber(l).lte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('-Infinity < +Infinity -> true', () => { + const l = -Infinity; + const r = Infinity; + const actual = FPN.of(l).lte(FPN.of(r)); + const expected = BigNumber(l).lte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('+Infinity < -Infinity -> false', () => { + const l = Infinity; + const r = -Infinity; + const actual = FPN.of(l).lte(FPN.of(r)); + const expected = BigNumber(l).lte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('+Infinity < +Infinity -> true', () => { + const l = Infinity; + const r = Infinity; + const actual = FPN.of(l).lte(FPN.of(r)); + const expected = BigNumber(l).lte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('l < r -> true', () => { + const l = 123.45; + const r = l * 2; + const actual = FPN.of(l).lte(FPN.of(r)); + const expected = BigNumber(l).lte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('l = r -> true', () => { + const l = 123.45; + const r = l; + const actual = FPN.of(l).lte(FPN.of(r)); + const expected = BigNumber(l).lte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('l > r -> false', () => { + const l = 123.45; + const r = l / 2; + const actual = FPN.of(l).lte(FPN.of(r)); + const expected = BigNumber(l).lte(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + }); + describe('minus method tests', () => { test('positive result', () => { const a = FPN.of(0.3); From eea7006c275315d2b1356caf2738f83f4c112bf7 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 11:39:24 +0100 Subject: [PATCH 29/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 3 +++ packages/core/tests/vcdm/FPN.unit.test.ts | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index b7bce4e7c..e3b31722d 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -374,6 +374,9 @@ class FPN { return (this.isFinite() && this.sv < 0n) || this.isNegativeInfinite(); } + /** + * Returns `true` if this FPN value is {@link NEGATIVE_INFINITY}, otherwise returns `false`. + */ public isNegativeInfinite(): boolean { return this.ef === Number.NEGATIVE_INFINITY; } diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 7704f5965..38a6eb82a 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -854,6 +854,26 @@ describe('FPN class tests', () => { }); }); + describe('isNegativeInfinite method tests', () => { + test('NaN -> false', () => { + expect(FPN.of(NaN).isNegativeInfinite()).toBe(false); + }); + + test('-Infinite -> true', () => { + expect(FPN.of(-Infinity).isNegativeInfinite()).toBe(true); + }); + + test('+Infinite -> false', () => { + expect(FPN.of(Infinity).isNegativeInfinite()).toBe(false); + }); + + test('n -> false', () => { + expect(FPN.of(-123.45).isNegativeInfinite()).toBe(false); + expect(FPN.of(0).isNegativeInfinite()).toBe(false); + expect(FPN.of(123.45).isNegativeInfinite()).toBe(false); + }); + }); + describe('lt method tests', () => { test('NaN < n -> false', () => { const l = NaN; From b7c0b836f4ce001bdf05d5919dfe260c20193aa8 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 11:44:33 +0100 Subject: [PATCH 30/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 7 ++++ packages/core/tests/vcdm/FPN.unit.test.ts | 50 +++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index e3b31722d..d046e4092 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -381,6 +381,13 @@ class FPN { return this.ef === Number.NEGATIVE_INFINITY; } + /** + * Returns `true` if the sign of this FPN is positive, otherwise returns `false`. + * + * @return `true` if the sign of this FPN is positive, otherwise returns `false`. + * + * @see [bignumber.js isPositive](https://mikemcl.github.io/bignumber.js/#isPos) + */ public isPositive(): boolean { return (this.isFinite() && this.sv >= 0n) || this.isPositiveInfinite(); } diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 38a6eb82a..fa67336a9 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -874,6 +874,56 @@ describe('FPN class tests', () => { }); }); + describe('isPositive method tests', () => { + test('NaN -> false', () => { + const n = NaN; + const actual = FPN.of(n).isPositive(); + const expected = BigNumber(n).isPositive(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('-Infinite -> false', () => { + const n = -Infinity; + const actual = FPN.of(n).isPositive(); + const expected = BigNumber(n).isPositive(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('+Infinite -> true', () => { + const n = Infinity; + const actual = FPN.of(n).isPositive(); + const expected = BigNumber(n).isPositive(); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('-n -> false', () => { + const n = -123.45; + const actual = FPN.of(n).isPositive(); + const expected = BigNumber(n).isPositive(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('0 -> true', () => { + const n = 0; + const actual = FPN.of(n).isPositive(); + const expected = BigNumber(n).isPositive(); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('n -> true', () => { + const n = 123.45; + const actual = FPN.of(n).isPositive(); + const expected = BigNumber(n).isPositive(); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + }); + describe('lt method tests', () => { test('NaN < n -> false', () => { const l = NaN; From cb1bb8ff6fb532e0bb0b346fc013d6958cb5516d Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 11:47:10 +0100 Subject: [PATCH 31/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 3 +++ packages/core/tests/vcdm/FPN.unit.test.ts | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index d046e4092..5047f4950 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -392,6 +392,9 @@ class FPN { return (this.isFinite() && this.sv >= 0n) || this.isPositiveInfinite(); } + /** + * Returns `true` if this FPN value is {@link POSITIVE_INFINITY}, otherwise returns `false`. + */ public isPositiveInfinite(): boolean { return this.ef === Number.POSITIVE_INFINITY; } diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index fa67336a9..4c0b7d6b4 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -924,6 +924,26 @@ describe('FPN class tests', () => { }); }); + describe('isPositiveInfinite method tests', () => { + test('NaN -> false', () => { + expect(FPN.of(NaN).isPositiveInfinite()).toBe(false); + }); + + test('-Infinite -> false', () => { + expect(FPN.of(-Infinity).isPositiveInfinite()).toBe(false); + }); + + test('+Infinite -> true', () => { + expect(FPN.of(Infinity).isPositiveInfinite()).toBe(true); + }); + + test('n -> false', () => { + expect(FPN.of(-123.45).isPositiveInfinite()).toBe(false); + expect(FPN.of(0).isPositiveInfinite()).toBe(false); + expect(FPN.of(123.45).isPositiveInfinite()).toBe(false); + }); + }); + describe('lt method tests', () => { test('NaN < n -> false', () => { const l = NaN; From 0145c02a101bda96e4a800da168ffa4e3ac15b35 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 11:59:08 +0100 Subject: [PATCH 32/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 11 ++++- packages/core/tests/vcdm/FPN.unit.test.ts | 50 +++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 5047f4950..c3139d6f1 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -394,13 +394,22 @@ class FPN { /** * Returns `true` if this FPN value is {@link POSITIVE_INFINITY}, otherwise returns `false`. + * + * @return `true` if this FPN value is {@link POSITIVE_INFINITY}, otherwise returns `false`. */ public isPositiveInfinite(): boolean { return this.ef === Number.POSITIVE_INFINITY; } + /** + * Returns `true` if the value of this FPN is zero or minus zero, otherwise returns `false`. + * + * @return `true` if the value of this FPN is zero or minus zero, otherwise returns `false`. + * + * [see bignumber.js isZero](https://mikemcl.github.io/bignumber.js/#isZ) + */ public isZero(): boolean { - return this.sv === 0n; + return this.isFinite() && this.sv === 0n; } /** diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 4c0b7d6b4..2cb1452a7 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -944,6 +944,56 @@ describe('FPN class tests', () => { }); }); + describe('isZero method tests', () => { + test('NaN -> false', () => { + const n = NaN; + const actual = FPN.of(n).isZero(); + const expected = BigNumber(n).isZero(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('-Infinite -> false', () => { + const n = -Infinity; + const actual = FPN.of(n).isZero(); + const expected = BigNumber(n).isZero(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('+Infinite -> false', () => { + const n = Infinity; + const actual = FPN.of(n).isZero(); + const expected = BigNumber(n).isZero(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('-n -> false', () => { + const n = -123.45; + const actual = FPN.of(n).isZero(); + const expected = BigNumber(n).isZero(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('0 -> true', () => { + const n = 0; + const actual = FPN.of(n).isZero(); + const expected = BigNumber(n).isZero(); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('+n -> false', () => { + const n = 123.45; + const actual = FPN.of(n).isZero(); + const expected = BigNumber(n).isZero(); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + }); + describe('lt method tests', () => { test('NaN < n -> false', () => { const l = NaN; From 21a9799977a56b12584be959bf19b8cedb18bf86 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 14:56:49 +0100 Subject: [PATCH 33/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 9 +- packages/core/tests/vcdm/FPN.unit.test.ts | 115 +++++++++++++++++++++- 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index c3139d6f1..c7ba707e6 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -445,6 +445,11 @@ class FPN { } public minus(that: FPN): FPN { + if (this.isNaN() || that.isNaN()) return FPN.NaN; + if (this.isNegativeInfinite()) + return that.isNegativeInfinite() ? FPN.NaN : FPN.NEGATIVE_INFINITY; + if (this.isPositiveInfinite()) + return that.isPositiveInfinite() ? FPN.NaN : FPN.POSITIVE_INFINITY; const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. return new FPN(fd, this.dp(fd).sv - that.dp(fd).sv); } @@ -627,8 +632,8 @@ class FPN { const ie = exp.substring(0, di); // Integer Expression. const fe = exp.substring(di + 1); // Fractional Expression. return ( - sign * sf * BigInt(ie) + // Sign and Integer part - (sf * BigInt(fe)) / BigInt(10 ** fe.length) // Fractional part. + sign * sf * BigInt(ie) + // Integer part + (sign * (sf * BigInt(fe))) / BigInt(10 ** fe.length) // Fractional part. ); } } diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 2cb1452a7..83a69de36 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -1233,11 +1233,116 @@ describe('FPN class tests', () => { }); describe('minus method tests', () => { - test('positive result', () => { - const a = FPN.of(0.3); - const b = FPN.of(0.1); - const r = a.minus(b); - console.log(r); + test('NaN - ±n -> NaN', () => { + const l = NaN; + const r = 123.45; + const actual = FPN.of(l).minus(FPN.of(r)); + const expected = BigNumber(l).minus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('±n - NaN -> NaN', () => { + const l = NaN; + const r = -123.45; + const actual = FPN.of(l).minus(FPN.of(r)); + const expected = BigNumber(l).minus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('-Infinity - -Infinity -> NaN', () => { + const l = -Infinity; + const r = -Infinity; + const actual = FPN.of(l).minus(FPN.of(r)); + const expected = BigNumber(l).minus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('-Infinity - +Infinity -> -Infinity', () => { + const l = -Infinity; + const r = +Infinity; + const actual = FPN.of(l).minus(FPN.of(r)); + const expected = BigNumber(l).minus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(-Infinity); + }); + + test('-Infinity - ±n -> -Infinity', () => { + const l = -Infinity; + const r = 123.45; + const actual = FPN.of(l).minus(FPN.of(r)); + const expected = BigNumber(l).minus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(-Infinity); + expect(actual.n).toBe(FPN.of(l).minus(FPN.of(-r)).n); + }); + + test('+Infinity - -Infinity -> +Infinity', () => { + const l = +Infinity; + const r = -Infinity; + const actual = FPN.of(l).minus(FPN.of(r)); + const expected = BigNumber(l).minus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(+Infinity); + }); + + test('+Infinity - +Infinity -> NaN', () => { + const l = +Infinity; + const r = +Infinity; + const actual = FPN.of(l).minus(FPN.of(r)); + const expected = BigNumber(l).minus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('+Infinity - ±n -> +Infinity', () => { + const l = +Infinity; + const r = 123.45; + const actual = FPN.of(l).minus(FPN.of(r)); + const expected = BigNumber(l).minus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(+Infinity); + expect(actual.n).toBe(FPN.of(l).minus(FPN.of(-r)).n); + }); + + test('n - 0 -> n', () => { + const l = 123.45; + const r = 0; + const actual = FPN.of(l).minus(FPN.of(r)); + const expected = BigNumber(l).minus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.eq(FPN.of(l))).toBe(true); + }); + + test('n - n -> 0', () => { + const l = 123.45; + const r = l; + const actual = FPN.of(l).minus(FPN.of(r)); + const expected = BigNumber(l).minus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.eq(FPN.ZERO)).toBe(true); + }); + + test('l - r -> >0', () => { + const fd = 13; + const l = 123.45; + const r = 23.45678; + const actual = FPN.of(l).minus(FPN.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); + }); + + test('l - r -> <0', () => { + const fd = 13; + const l = 123.45; + const r = -1234.5678; + const actual = FPN.of(l).minus(FPN.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); }); }); From f47810ab0f8132d8242c665c1530b4e9e00a8e0b Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 17:17:41 +0100 Subject: [PATCH 34/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 116 +++++++++++++++++----- packages/core/tests/vcdm/FPN.unit.test.ts | 102 +++++++++++++++++++ 2 files changed, 192 insertions(+), 26 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index c7ba707e6..9ab67cc9d 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -73,6 +73,25 @@ class FPN { */ private readonly sv: bigint; + get bi(): bigint { + if (this.isFinite()) { + return this.sv / 10n ** this.fd; + } + throw new InvalidOperation( + 'FPN.bi', + 'not finite value cannot cast to big integer', + { ef: this.ef, fd: this.fd, sv: this.sv } + ); + } + + 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.sv) * 10 ** -Number(this.fd); + } + /** * Returns the new Fixed-Point Number (FDN) instance having * @@ -89,7 +108,7 @@ class FPN { /** * Returns a FPN whose value is the absolute value, i.e. the magnitude, of the value of this FPN. * - * @retrun the absolute value of this FPN. + * @return {FPN} the absolute value of this FPN. * * @see [bignumber.js absoluteValue](https://mikemcl.github.io/bignumber.js/#abs) */ @@ -130,7 +149,7 @@ class FPN { /** * Compares this instance with `that` FPN instance. - * * **Returns a null if either instance is NaN;** + * * **Returns `null` if either instance is NaN;** * * Returns 0 if this is equal to `that` FPN, including infinite with equal sign; * * Returns -1, if this is -Infinite or less than `that` FPN;, * * Returns 1 if this is +Infinite or greater than `that` FPN. @@ -156,13 +175,13 @@ class FPN { /** * Returns a FPN whose value is the value of this FPN divided by `that` FPN. * - * Limit cases: - * - 0 / 0 = NaN - * - NaN / n = NaN - * - n / NaN = NaN - * - n / ±Infinite = 0 - * - -n / 0 = -Infinite - * - +n / 0 = +Infinite + * Limit cases + * * 0 / 0 = NaN + * * NaN / n = NaN + * * n / NaN = NaN + * * n / ±Infinity = 0 + * * -n / 0 = -Infinity + * * +n / 0 = +Infinity * * @param {FPN} that - The fixed-point number to divide by. * @@ -272,13 +291,13 @@ class FPN { * 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 - * - n / NaN = NaN - * - n / ±Infinite = 0 - * - -n / 0 = -Infinite - * - +n / 0 = +Infinite + * Limit cases + * * 0 / 0 = NaN + * * NaN / n = NaN + * * n / NaN = NaN + * * n / ±Infinite = 0 + * * -n / 0 = -Infinite + * * +n / 0 = +Infinite * * @param {FPN} that - The fixed-point number to divide by. * @@ -417,7 +436,7 @@ class FPN { * * @param {FPN} that - The FPN to compare against. * - * @return `true` if the value of this FPN is less than the value of `that` FPN, otherwise returns `false`. + * @return {boolean} `true` if the value of this FPN is less than the value of `that` FPN, otherwise returns `false`. * * @remarks This method uses {@link comparedTo} internally. * @@ -435,6 +454,9 @@ class FPN { * @param {FPN} that true` if the value of this FPN is less than or equal to the value of `that` FPN, * otherwise returns `false`. * + * @return {boolean} `true` if the value of this FPN is less than or equal to the value of `that` FPN, + * otherwise returns `false`. + * * @remarks This method uses {@link comparedTo} internally. * * @see [bignumber.js isLessThanOrEqualTo](https://mikemcl.github.io/bignumber.js/#lte) @@ -444,6 +466,25 @@ class FPN { return cmp !== null && cmp <= 0; } + /** + * Returns a FPN whose value is the value of this FPN minus `that` FPN. + * + * Limit cases + * * NaN - n = NaN + * * n - NaN = NaN + * * -Infinity - -Infinity = NaN + * * -Infinity - n = -Infinity + * * +Infinity - +Infinity = NaN + * * +Infinity - n = +Infinity + * + * @param {FPN} that The fixed-point number to subtract. + * + * @return {FPN} 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: FPN): FPN { if (this.isNaN() || that.isNaN()) return FPN.NaN; if (this.isNegativeInfinite()) @@ -454,6 +495,37 @@ class FPN { return new FPN(fd, this.dp(fd).sv - that.dp(fd).sv); } + /** + * Returns a FPN whose value is the value of this FPN modulo `that` FPN, + * i.e. the integer remainder of dividing this FPN by `that`. + * + * Limit cases + * * NaN % n = NaN + * * n % NaN = NaN + * * ±Infinity % n = NaN + * * n % ±Infinity = NaN + * + * @param that {FPN} The fixed-point number to divide by. + * + * @return {FPN} the integer remainder of dividing this FPN 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: FPN): FPN { + if (this.isNaN() || that.isNaN()) return FPN.NaN; + if (this.isInfinite() || that.isInfinite()) return FPN.NaN; + if (that.isZero()) return FPN.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; + while (modulo >= divisor) { + modulo -= divisor; + } + return new FPN(fd, modulo); + } + private static mul( multiplicand: bigint, multiplicator: bigint, @@ -466,14 +538,6 @@ class FPN { return this.times(that); } - 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.sv) * 10 ** -Number(this.fd); - } - public static of( exp: number, decimalPlaces: bigint = this.DEFAULT_FRACTIONAL_DECIMALS @@ -492,7 +556,7 @@ class FPN { /** * Returns a FPN whose value is the value of this FPN raised to the power of `that` FPN. * - * Limit cases: + * Limit cases * - NaN ^ e = NaN * - b ^ NaN = NaN * - b ^ -Infinite = 0 diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 83a69de36..bce103f3b 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -1346,6 +1346,108 @@ describe('FPN class tests', () => { }); }); + describe('modulo method tests', () => { + test('NaN % n -> NaN', () => { + const l = NaN; + const r = 123.45; + const actual = FPN.of(l).modulo(FPN.of(r)); + const expected = BigNumber(l).modulo(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('n % NaN -> NaN', () => { + const l = 123.45; + const r = NaN; + const actual = FPN.of(l).modulo(FPN.of(r)); + const expected = BigNumber(l).modulo(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('-Infinite % -Infinite -> NaN', () => { + const l = -Infinity; + const r = -Infinity; + const actual = FPN.of(l).modulo(FPN.of(r)); + const expected = BigNumber(l).modulo(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('-Infinite % +Infinite -> NaN', () => { + const l = -Infinity; + const r = Infinity; + const actual = FPN.of(l).modulo(FPN.of(r)); + const expected = BigNumber(l).modulo(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('-Infinite % ±n -> NaN', () => { + const l = -Infinity; + const r = 123.45; + const actual = FPN.of(l).modulo(FPN.of(r)); + const expected = BigNumber(l).modulo(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('+Infinite % -Infinite -> NaN', () => { + const l = Infinity; + const r = -Infinity; + const actual = FPN.of(l).modulo(FPN.of(r)); + const expected = BigNumber(l).modulo(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('+Infinite % +Infinite -> NaN', () => { + const l = Infinity; + const r = Infinity; + const actual = FPN.of(l).modulo(FPN.of(r)); + const expected = BigNumber(l).modulo(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('+Infinite % ±n -> NaN', () => { + const l = Infinity; + const r = 123.45; + const actual = FPN.of(l).modulo(FPN.of(r)); + const expected = BigNumber(l).modulo(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('n % 0 -> 0', () => { + const l = 123.45; + const r = 0; + const actual = FPN.of(l).modulo(FPN.of(r)); + const expected = BigNumber(l).modulo(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('integer % ±1 -> 0', () => { + const l = 123; + const r = 1; + const actual = FPN.of(l).modulo(FPN.of(r)); + const expected = BigNumber(l).modulo(BigNumber(r)); + expect(actual).toEqual(FPN.of(expected.toNumber())); + expect(FPN.of(l).modulo(FPN.of(-r))).toEqual(actual); + expect(actual.isZero()).toBe(true); + }); + + test('n % ±1 -> 0', () => { + const l = 123.45; + const r = 0.6789; + const actual = FPN.of(l).modulo(FPN.of(r)); + const expected = BigNumber(l).modulo(BigNumber(r)); + expect(FPN.of(l).modulo(FPN.of(-r))).toEqual(actual); + expect(actual).toEqual(FPN.of(expected.toNumber())); + }); + }); + describe('multipliedBy method tests', () => { test('negative auto scale', () => { const a = FPN.of(-3.5); From dc1dbb15c7068c7ec0ab1f3b8e95d61ffdc315e1 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 18:23:33 +0100 Subject: [PATCH 35/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 13 ++- packages/core/tests/vcdm/FPN.unit.test.ts | 126 ++++++++++++++++------ 2 files changed, 103 insertions(+), 36 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 9ab67cc9d..defc40b60 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -534,10 +534,6 @@ class FPN { return (multiplicand * multiplicator) / 10n ** fd; } - public multipliedBy(that: FPN): FPN { - return this.times(that); - } - public static of( exp: number, decimalPlaces: bigint = this.DEFAULT_FRACTIONAL_DECIMALS @@ -643,6 +639,15 @@ class FPN { } public times(that: FPN): FPN { + if (this.isNaN() || that.isNaN()) return FPN.NaN; + if (this.isNegativeInfinite()) + return that.isNegative() + ? FPN.POSITIVE_INFINITY + : FPN.NEGATIVE_INFINITY; + if (this.isPositiveInfinite()) + return that.isNegative() + ? FPN.NEGATIVE_INFINITY + : FPN.POSITIVE_INFINITY; const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. return new FPN(fd, FPN.mul(this.dp(fd).sv, that.dp(fd).sv, fd)); } diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index bce103f3b..9eb9b4ad2 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -1448,24 +1448,6 @@ describe('FPN class tests', () => { }); }); - describe('multipliedBy method tests', () => { - test('negative auto scale', () => { - const a = FPN.of(-3.5); - const b = FPN.of(2, 0n); - const r = a.multipliedBy(b); - console.log(r); - }); - }); - - describe('plus method tests', () => { - test('positive result', () => { - const a = FPN.of(5.75); - const b = FPN.of(2.5); - const r = a.plus(b); - console.log(r); - }); - }); - describe('pow method tests', () => { test('NaN ^ ±e', () => { const b = NaN; @@ -1604,23 +1586,103 @@ describe('FPN class tests', () => { }); }); - test('scale', () => { - const a = FPN.of(-1, 18n); - console.log(a); - console.log(a.dp(3n)); - }); + describe('times method tests', () => { + test('NaN * ±n -> NaN', () => { + const l = NaN; + const r = 123.45; + const actual = FPN.of(l).times(FPN.of(r)); + const expected = BigNumber(l).times(BigNumber(r)); + expect(actual.n).toEqual(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); - describe('squareRoot methods tests', () => { - test('integer result', () => { - const a = FPN.of(16); - const r = a.squareRoot(); - console.log(r); + test('±n * NaN -> NaN', () => { + const l = -123.45; + const r = NaN; + const actual = FPN.of(l).times(FPN.of(r)); + const expected = BigNumber(l).times(BigNumber(r)); + expect(actual.n).toEqual(expected.toNumber()); + expect(actual.n).toBe(NaN); }); - test('not integer result', () => { - const a = FPN.of(3); - const r = a.squareRoot(); - console.log(r); + test('-Infinity * -Infinity -> +Infinity', () => { + const l = -Infinity; + const r = -Infinity; + const actual = FPN.of(l).times(FPN.of(r)); + const expected = BigNumber(l).times(BigNumber(r)); + expect(actual.n).toEqual(expected.toNumber()); + expect(actual.n).toBe(Infinity); + }); + + test('-Infinity * +Infinity -> -Infinity', () => { + const l = -Infinity; + const r = Infinity; + const actual = FPN.of(l).times(FPN.of(r)); + const expected = BigNumber(l).times(BigNumber(r)); + expect(actual.n).toEqual(expected.toNumber()); + expect(actual.n).toBe(-Infinity); + }); + + test('-Infinity * -n -> +Infinity', () => { + const l = -Infinity; + const r = -123.45; + const actual = FPN.of(l).times(FPN.of(r)); + const expected = BigNumber(l).times(BigNumber(r)); + expect(actual.n).toEqual(expected.toNumber()); + expect(actual.n).toBe(Infinity); + }); + + test('-Infinity * +n -> -Infinity', () => { + const l = -Infinity; + const r = 123.45; + const actual = FPN.of(l).times(FPN.of(r)); + const expected = BigNumber(l).times(BigNumber(r)); + expect(actual.n).toEqual(expected.toNumber()); + expect(actual.n).toBe(-Infinity); + }); + + test('+Infinity * -Infinity -> -Infinity', () => { + const l = Infinity; + const r = -Infinity; + const actual = FPN.of(l).times(FPN.of(r)); + const expected = BigNumber(l).times(BigNumber(r)); + expect(actual.n).toEqual(expected.toNumber()); + expect(actual.n).toBe(-Infinity); + }); + + test('+Infinity * +Infinity -> +Infinity', () => { + const l = Infinity; + const r = Infinity; + const actual = FPN.of(l).times(FPN.of(r)); + const expected = BigNumber(l).times(BigNumber(r)); + expect(actual.n).toEqual(expected.toNumber()); + expect(actual.n).toBe(Infinity); + }); + + test('+Infinity * -n -> -Infinity', () => { + const l = Infinity; + const r = -123.45; + const actual = FPN.of(l).times(FPN.of(r)); + const expected = BigNumber(l).times(BigNumber(r)); + expect(actual.n).toEqual(expected.toNumber()); + expect(actual.n).toBe(-Infinity); + }); + + test('+Infinity * +n -> +Infinity', () => { + const l = Infinity; + const r = 123.45; + const actual = FPN.of(l).times(FPN.of(r)); + const expected = BigNumber(l).times(BigNumber(r)); + expect(actual.n).toEqual(expected.toNumber()); + expect(actual.n).toBe(Infinity); + }); + + test('l * r', () => { + const l = 0.6; + const r = 3; + const actual = FPN.of(l).times(FPN.of(r)); + const expected = BigNumber(l).times(BigNumber(r)); + expect(actual).toEqual(FPN.of(expected.toNumber())); }); }); From a2a5fee0f555293228767f86eb809cba657e2deb Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 18:28:15 +0100 Subject: [PATCH 36/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index defc40b60..98d146305 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -638,6 +638,17 @@ class FPN { return this.sqrt(); } + /** + * Returns a FPN whose value is the value of this FPN multiplied by `that` FPN. + * + * @param {FPN} that - The fixed-point number to multiply with this number. + * + * @return {FPN} a FPN whose value is the value of this FPN multiplied by `that` FPN. + * + * @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: FPN): FPN { if (this.isNaN() || that.isNaN()) return FPN.NaN; if (this.isNegativeInfinite()) From d980a39b4dbe7436da4bd9bf3bd390ae110452c5 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 18:39:23 +0100 Subject: [PATCH 37/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 55 +++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 98d146305..43a728c5b 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -177,9 +177,9 @@ class FPN { * * Limit cases * * 0 / 0 = NaN - * * NaN / n = NaN - * * n / NaN = NaN - * * n / ±Infinity = 0 + * * NaN / ±n = NaN + * * +n / NaN = NaN + * * +n / ±Infinity = 0 * * -n / 0 = -Infinity * * +n / 0 = +Infinity * @@ -293,9 +293,9 @@ class FPN { * * Limit cases * * 0 / 0 = NaN - * * NaN / n = NaN - * * n / NaN = NaN - * * n / ±Infinite = 0 + * * NaN / ±n = NaN + * * +n / NaN = NaN + * * +n / ±Infinite = 0 * * -n / 0 = -Infinite * * +n / 0 = +Infinite * @@ -470,12 +470,12 @@ class FPN { * Returns a FPN whose value is the value of this FPN minus `that` FPN. * * Limit cases - * * NaN - n = NaN - * * n - NaN = NaN + * * NaN - ±n = NaN + * * ±n - NaN = NaN * * -Infinity - -Infinity = NaN - * * -Infinity - n = -Infinity + * * -Infinity - +n = -Infinity * * +Infinity - +Infinity = NaN - * * +Infinity - n = +Infinity + * * +Infinity - +n = +Infinity * * @param {FPN} that The fixed-point number to subtract. * @@ -500,8 +500,8 @@ class FPN { * i.e. the integer remainder of dividing this FPN by `that`. * * Limit cases - * * NaN % n = NaN - * * n % NaN = NaN + * * NaN % ±n = NaN + * * ±n % NaN = NaN * * ±Infinity % n = NaN * * n % ±Infinity = NaN * @@ -526,6 +526,15 @@ class FPN { return new FPN(fd, modulo); } + /** + * 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, @@ -553,13 +562,13 @@ class FPN { * Returns a FPN whose value is the value of this FPN raised to the power of `that` FPN. * * Limit cases - * - NaN ^ e = NaN - * - b ^ NaN = NaN - * - b ^ -Infinite = 0 - * - b ^ 0 = 1 - * - b ^ +Infinite = +Infinite - * - ±Infinite ^ -e = 0 - * - ±Infinite ^ +e = +Infinite + * * NaN ^ e = NaN + * * b ^ NaN = NaN + * * b ^ -Infinite = 0 + * * b ^ 0 = 1 + * * b ^ +Infinite = +Infinite + * * ±Infinite ^ -e = 0 + * * ±Infinite ^ +e = +Infinite * * @param {FPN} that - The exponent as a fixed-point number. * It can be negative, it can be not an integer value @@ -641,6 +650,14 @@ class FPN { /** * Returns a FPN whose value is the value of this FPN multiplied by `that` FPN. * + * Limits cases + * * NaN * n = NaN + * * n * NaN = NaN + * * -Infinite * -n = +Infinite + * * -Infinite * +n = -Infinite + * * +Infinite * -n = -Infinite + * * +Infinite * +n = +Infinite + * * @param {FPN} that - The fixed-point number to multiply with this number. * * @return {FPN} a FPN whose value is the value of this FPN multiplied by `that` FPN. From 3589ed12ff3ca7dd0bc022fb5d0717e959c863e2 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 19:09:53 +0100 Subject: [PATCH 38/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 34 +++++-- packages/core/tests/vcdm/FPN.unit.test.ts | 104 ++++++++++++++++++++++ 2 files changed, 133 insertions(+), 5 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 43a728c5b..cc7324c2f 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -595,6 +595,35 @@ class FPN { return new FPN(fd, FPN.pow(fd, this.dp(fd).sv, that.dp(fd).sv)); } + /** + * Returns a FPN whose value is the value of this FPN plus `that` FPN. + * + * Limit cases + * * NaN + ±n = NaN + * * ±n + NaN = NaN + * * -Infinity + -Infinity = -Infinity + * * -Infinity + +Infinity = NaN + * * +Infinity + -Infinity = NaN + * * +Infinity + +Infinity = +Infinity + * + * @param {FPN} that - The fixed-point number to add to the current number. + * + * @return {FPN} 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: FPN): FPN { + if (this.isNaN() || that.isNaN()) return FPN.NaN; + if (this.isNegativeInfinite()) + return that.isPositiveInfinite() ? FPN.NaN : FPN.NEGATIVE_INFINITY; + if (this.isPositiveInfinite()) + return that.isNegativeInfinite() ? FPN.NaN : FPN.POSITIVE_INFINITY; + const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + return new FPN(fd, this.dp(fd).sv + that.dp(fd).sv); + } + /** * Computes the power of a given base raised to a specified exponent. * @@ -617,11 +646,6 @@ class FPN { return FPN.pow(fd, this.mul(base, base, fd), exponent - sf); // Recursive. } - public plus(that: FPN): FPN { - const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - return new FPN(fd, this.dp(fd).sv + that.dp(fd).sv); - } - private static sqr(value: bigint, fd: bigint): bigint { if (value < 0n) { throw new Error(); diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 9eb9b4ad2..39f356613 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -1448,6 +1448,110 @@ describe('FPN class tests', () => { }); }); + describe('plus method tests', () => { + test('NaN + ±n -> NaN', () => { + const l = NaN; + const r = 123.45; + const actual = FPN.of(l).plus(FPN.of(r)); + const expected = BigNumber(l).plus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('±n + NaN -> NaN', () => { + const l = NaN; + const r = -123.45; + const actual = FPN.of(l).plus(FPN.of(r)); + const expected = BigNumber(l).plus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('-Infinity + -Infinity -> -Infinity', () => { + const l = -Infinity; + const r = -Infinity; + const actual = FPN.of(l).plus(FPN.of(r)); + const expected = BigNumber(l).plus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(-Infinity); + }); + + test('-Infinity + +Infinity -> NaN', () => { + const l = -Infinity; + const r = +Infinity; + const actual = FPN.of(l).plus(FPN.of(r)); + const expected = BigNumber(l).plus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('-Infinity + ±n -> -Infinity', () => { + const l = -Infinity; + const r = 123.45; + const actual = FPN.of(l).plus(FPN.of(r)); + const expected = BigNumber(l).plus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(-Infinity); + expect(actual.n).toBe(FPN.of(l).plus(FPN.of(-r)).n); + }); + + test('+Infinity + -Infinity -> NaN', () => { + const l = +Infinity; + const r = -Infinity; + const actual = FPN.of(l).plus(FPN.of(r)); + const expected = BigNumber(l).plus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('+Infinity + +Infinity -> Infinity', () => { + const l = +Infinity; + const r = +Infinity; + const actual = FPN.of(l).plus(FPN.of(r)); + const expected = BigNumber(l).plus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(Infinity); + }); + + test('+Infinity + ±n -> +Infinity', () => { + const l = +Infinity; + const r = 123.45; + const actual = FPN.of(l).plus(FPN.of(r)); + const expected = BigNumber(l).plus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(+Infinity); + expect(actual.n).toBe(FPN.of(l).plus(FPN.of(-r)).n); + }); + + test('n + 0 -> n', () => { + const l = 123.45; + const r = 0; + const actual = FPN.of(l).plus(FPN.of(r)); + const expected = BigNumber(l).plus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.eq(FPN.of(l))).toBe(true); + }); + + test('n + -n -> 0', () => { + const l = 123.45; + const r = -l; + const actual = FPN.of(l).plus(FPN.of(r)); + const expected = BigNumber(l).plus(BigNumber(r)); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.eq(FPN.ZERO)).toBe(true); + }); + + test('l + r -> >0', () => { + const fd = 13; + const l = 0.1; + const r = 0.2; + const actual = FPN.of(l).plus(FPN.of(r)); + const expected = BigNumber(l).plus(BigNumber(r)); + expect(actual.n.toFixed(fd)).toBe(expected.toNumber().toFixed(fd)); + expect(actual.n).toBe(l + r); + }); + }); + describe('pow method tests', () => { test('NaN ^ ±e', () => { const b = NaN; From 3adf29400cd2d40fe8c17a6fec67f419d9ee445d Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 19:37:26 +0100 Subject: [PATCH 39/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 37 +++++++++++++++++---- packages/core/tests/vcdm/FPN.unit.test.ts | 40 +++++++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index cc7324c2f..41c674899 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -646,9 +646,19 @@ class FPN { return FPN.pow(fd, this.mul(base, base, fd), exponent - sf); // Recursive. } + /** + * 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 Error(); + throw new RangeError(`Value must be positive`); } const sf = fd * 10n; // Scale Factor. let iteration = 0; @@ -663,12 +673,27 @@ class FPN { return actualResult; } + /** + * Returns a FPN whose value is the square root of the value of this FPN + * + * Limit cases + * * √ NaN = NaN + * * +Infinite = +Infinite + * * -n = NaN + * + * @return {FPN} The square root of the number. + * + * @see [bignumber.js sqrt](https://mikemcl.github.io/bignumber.js/#sqrt) + */ public sqrt(): FPN { - return new FPN(this.fd, FPN.sqr(this.sv, this.fd)); - } - - public squareRoot(): FPN { - return this.sqrt(); + if (this.isNaN()) return FPN.NaN; + if (this.isPositiveInfinite()) return FPN.POSITIVE_INFINITY; + try { + return new FPN(this.fd, FPN.sqr(this.sv, this.fd)); + } catch (e) { + if (e instanceof RangeError) return FPN.NaN; + else throw e; + } } /** diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 39f356613..32013704a 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -1690,6 +1690,46 @@ describe('FPN class tests', () => { }); }); + describe('sqrt method tests', () => { + test('√ NaN -> NaN', () => { + const n = NaN; + const actual = FPN.of(n).sqrt(); + const expected = BigNumber(n).sqrt(); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('√ +Infinity -> +Infinity', () => { + const n = Infinity; + const actual = FPN.of(n).sqrt(); + const expected = BigNumber(n).sqrt(); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(Infinity); + }); + test('√ -n -> NaN', () => { + const n = -123.45; + const actual = FPN.of(n).sqrt(); + const expected = BigNumber(n).sqrt(); + expect(actual.n).toBe(expected.toNumber()); + expect(actual.n).toBe(NaN); + }); + + test('√ n -> integer', () => { + const n = 16; + const actual = FPN.of(n).sqrt(); + const expected = BigNumber(n).sqrt(); + expect(actual.n).toBe(expected.toNumber()); + }); + + test('√ n -> rational', () => { + const fd = 13; + const n = 3; + const actual = FPN.of(n).sqrt(); + const expected = BigNumber(n).sqrt(); + expect(actual.n.toFixed(fd)).toBe(expected.toNumber().toFixed(fd)); + }); + }); + describe('times method tests', () => { test('NaN * ±n -> NaN', () => { const l = NaN; From 6d2b0cb94fb5e4ea900ce32e9e2f63634c12d397 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Mon, 2 Sep 2024 19:55:41 +0100 Subject: [PATCH 40/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 41c674899..4287c6781 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -1,6 +1,8 @@ import { InvalidOperation } from '@vechain/sdk-errors'; +import { type VeChainDataModel } from './VeChainDataModel'; +import { Txt } from './Txt'; -class FPN { +class FPN implements VeChainDataModel { /** * The default number of decimal places to use for fixed-point math. * @@ -73,6 +75,13 @@ class FPN { */ private readonly sv: bigint; + /** + * Returns the integer part of this FPN value. + * + * @return {bigint} the integer part of this FPN value. + * + * @throws {InvalidOperation} If the value is not finite. + */ get bi(): bigint { if (this.isFinite()) { return this.sv / 10n ** this.fd; @@ -80,10 +89,19 @@ class FPN { throw new InvalidOperation( 'FPN.bi', 'not finite value cannot cast to big integer', - { ef: this.ef, fd: this.fd, sv: this.sv } + { 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; + } + get n(): number { if (this.isNaN()) return Number.NaN; if (this.isNegativeInfinite()) return Number.NEGATIVE_INFINITY; @@ -333,6 +351,10 @@ class FPN { return (dividend / divisor) * 10n ** fd; } + public isEqual(that: FPN): boolean { + return this.eq(that); + } + /** * Returns `true` if the value of this FPN is a finite number, otherwise returns `false`. * From 68706cb987b996b58e290b39a5883d1fcd3dfdc5 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Tue, 3 Sep 2024 09:13:26 +0100 Subject: [PATCH 41/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 16 ++++++++++++++++ packages/core/tests/vcdm/FPN.unit.test.ts | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 4287c6781..6a64308b9 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -102,6 +102,9 @@ class FPN implements VeChainDataModel { 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; @@ -268,6 +271,8 @@ class FPN implements VeChainDataModel { * @return {boolean} `true` if the FPN 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: FPN): boolean { return this.comparedTo(that) === 0; @@ -351,6 +356,17 @@ class FPN implements VeChainDataModel { return (dividend / divisor) * 10n ** fd; } + /** + * Returns `true `if the value of thisFPN is equal to the value of `that` FPN, otherwise returns `false`. + * + * As with JavaScript, `NaN` does not equal `NaN`. + * + * @param {FPN} that - The FPN to compare against. + * + * @return {boolean} `true` if the FPN numbers are equal, otherwise `false`. + * + * @remarks This method uses {@link eq} internally. + */ public isEqual(that: FPN): boolean { return this.eq(that); } diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 32013704a..9dd78ce73 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -2,6 +2,10 @@ import { describe, expect, test } from '@jest/globals'; import { FPN } from '../../src'; import { BigNumber } from 'bignumber.js'; +/** + * Test FPN class. + * @group unit/vcdm + */ describe('FPN class tests', () => { describe('abs method tests', () => { test('n < 0', () => { From 3d0c44119e150aa3234020988788abcc1d70cd2f Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Tue, 3 Sep 2024 10:10:23 +0100 Subject: [PATCH 42/48] feat: 1113 Fixed Point Number math in dev... --- docs/diagrams/architecture/vcdm.md | 36 ++++++++++++++++++- packages/core/src/vcdm/FPN.ts | 58 +++++++++++++++--------------- 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 7a20d5964..8fa4e26ff 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -25,9 +25,41 @@ classDiagram } class Contract class Currency { - <> + +string code } class ExternallyOwnedAccount + class FPN { + +FPN NaN$ + +FPN NEGATIVE_INFINITY$ + +FPN POSITIVE_INFINITY$ + +FPN ZERO$ + +FPN abs() + +null|number comparedTo(FPN that) + +FPN div(FPN that) + +FPN dp(bigint|number decimalPlaces) + +boolean eq(FPN that) + +boolean gt(FPN that) + +boolean gte(FPN that) + +FPN idiv(FPN that) + +boolean isFinite() + +boolean isInfinite() + +boolean isInteger() + +boolean isNaN() + +boolean isNegative() + +boolean isNegativeInfinite() + +boolean isPositive() + +boolean isPositiveInfinite() + +boolean isZero() + +boolean lt(FPN that) + +boolean lte(FPN that) + +FPN minus(FPN that) + +FPN modulo(FPN that) + +FPN of(bigint|number|string exp)$ + +FPN plus(FPN that) + +FPN pow(FPN that) + +FPN sqrt() + +FPN times(FPN that) + } class Hash { <> } @@ -83,6 +115,7 @@ classDiagram Account "1" ..|> "1" Address : has Account "1" ..|> "1" Currency : has Account <|-- Contract + FPN <|-- Currency Hash <|.. Blake2b256 Hash <|.. Keccak256 Hash <|.. Sha256 @@ -98,6 +131,7 @@ classDiagram Txt <|-- Revision Txt <|-- Mnemonic VeChainDataModel <|.. BloomFilter + VeChainDataModel <|.. FPN VeChainDataModel <|.. Hex VeChainDataModel <|.. Txt ``` diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 6a64308b9..90a60cf38 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -596,6 +596,35 @@ class FPN implements VeChainDataModel { ); } + /** + * Returns a FPN whose value is the value of this FPN plus `that` FPN. + * + * Limit cases + * * NaN + ±n = NaN + * * ±n + NaN = NaN + * * -Infinity + -Infinity = -Infinity + * * -Infinity + +Infinity = NaN + * * +Infinity + -Infinity = NaN + * * +Infinity + +Infinity = +Infinity + * + * @param {FPN} that - The fixed-point number to add to the current number. + * + * @return {FPN} 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: FPN): FPN { + if (this.isNaN() || that.isNaN()) return FPN.NaN; + if (this.isNegativeInfinite()) + return that.isPositiveInfinite() ? FPN.NaN : FPN.NEGATIVE_INFINITY; + if (this.isPositiveInfinite()) + return that.isNegativeInfinite() ? FPN.NaN : FPN.POSITIVE_INFINITY; + const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. + return new FPN(fd, this.dp(fd).sv + that.dp(fd).sv); + } + /** * Returns a FPN whose value is the value of this FPN raised to the power of `that` FPN. * @@ -633,35 +662,6 @@ class FPN implements VeChainDataModel { return new FPN(fd, FPN.pow(fd, this.dp(fd).sv, that.dp(fd).sv)); } - /** - * Returns a FPN whose value is the value of this FPN plus `that` FPN. - * - * Limit cases - * * NaN + ±n = NaN - * * ±n + NaN = NaN - * * -Infinity + -Infinity = -Infinity - * * -Infinity + +Infinity = NaN - * * +Infinity + -Infinity = NaN - * * +Infinity + +Infinity = +Infinity - * - * @param {FPN} that - The fixed-point number to add to the current number. - * - * @return {FPN} 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: FPN): FPN { - if (this.isNaN() || that.isNaN()) return FPN.NaN; - if (this.isNegativeInfinite()) - return that.isPositiveInfinite() ? FPN.NaN : FPN.NEGATIVE_INFINITY; - if (this.isPositiveInfinite()) - return that.isNegativeInfinite() ? FPN.NaN : FPN.POSITIVE_INFINITY; - const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - return new FPN(fd, this.dp(fd).sv + that.dp(fd).sv); - } - /** * Computes the power of a given base raised to a specified exponent. * From 67985d313b439c19a8608ef7213366ffbcaf55ab Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Tue, 3 Sep 2024 10:16:13 +0100 Subject: [PATCH 43/48] feat: 1113 Fixed Point Number math in dev... --- packages/core/src/vcdm/FPN.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 90a60cf38..dca4eefcc 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -582,7 +582,7 @@ class FPN implements VeChainDataModel { } public static of( - exp: number, + exp: bigint | number | string, decimalPlaces: bigint = this.DEFAULT_FRACTIONAL_DECIMALS ): FPN { if (Number.isNaN(exp)) return new FPN(decimalPlaces, 0n, Number.NaN); From 5447c0a409397dc9d03aa4cf65dc966c4ab213f8 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Tue, 3 Sep 2024 12:15:18 +0100 Subject: [PATCH 44/48] feat: 1113 Fixed Point Number math --- packages/core/tests/vcdm/FPN.unit.test.ts | 477 +++++++++++++++------- 1 file changed, 336 insertions(+), 141 deletions(-) diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 9dd78ce73..18fe3b44e 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -1,12 +1,347 @@ import { describe, expect, test } from '@jest/globals'; -import { FPN } from '../../src'; import { BigNumber } from 'bignumber.js'; +import { FPN, Txt } from '../../src'; +import { InvalidOperation } from '@vechain/sdk-errors'; /** * Test FPN class. * @group unit/vcdm */ describe('FPN class tests', () => { + describe('VeChain Data Model tests', () => { + describe('get bi tests', () => { + test('NaN throws exception', () => { + expect(() => { + console.log(FPN.NaN.bi); + }).toThrow(InvalidOperation); + }); + + test('-Infinity throws exception', () => { + expect(() => { + console.log(FPN.NEGATIVE_INFINITY.bi); + }).toThrow(InvalidOperation); + }); + + test('+Infinity throws exception', () => { + expect(() => { + console.log(FPN.POSITIVE_INFINITY.bi); + }).toThrow(InvalidOperation); + }); + + test('Integers result the same', () => { + const expected = 12345n; + const actual = FPN.of(expected).bi; + expect(actual).toEqual(expected); + }); + + test('Rational is truncated', () => { + const n = 123.45; + const actual = FPN.of(n).bi; + const expected = BigInt(Math.trunc(n)); + expect(actual).toEqual(expected); + }); + }); + + test('get bytes tests', () => { + const exp = Txt.of('123.45'); + const actual = FPN.of(exp.toString()).bytes; + const expected = exp.bytes; + expect(actual).toEqual(expected); + }); + + describe('get n tests', () => { + test('NaN', () => { + expect(FPN.NaN.n).toEqual(NaN); + }); + + test('-Infinity', () => { + expect(FPN.NEGATIVE_INFINITY.n).toEqual(-Infinity); + }); + + test('+Infinity', () => { + expect(FPN.POSITIVE_INFINITY.n).toEqual(Infinity); + }); + + test('±n', () => { + const n = 123.45; + expect(FPN.of(n).n).toEqual(n); + expect(FPN.of(-n).n).toEqual(-n); + }); + }); + + describe('compareTo tests', () => { + test('NaN ~ n throws exception', () => { + const l = NaN; + const r = 123.45; + expect(() => FPN.of(l).compareTo(FPN.of(r))).toThrow( + InvalidOperation + ); + }); + + test('n ~ NaN -> throw exception', () => { + const l = 123.45; + const r = NaN; + expect(() => FPN.of(l).compareTo(FPN.of(r))).toThrow( + InvalidOperation + ); + }); + + test('-Infinity ~ n -> -1', () => { + const l = -Infinity; + const r = 123.45; + const actual = FPN.of(l).compareTo(FPN.of(r)); + expect(actual).toBe(-1); + }); + + test('+Infinity ~ n -> 1', () => { + const l = Infinity; + const r = 123.45; + const actual = FPN.of(l).compareTo(FPN.of(r)); + expect(actual).toBe(1); + }); + + test('n ~ -Infinity -> 1', () => { + const l = 123.45; + const r = -Infinity; + const actual = FPN.of(l).compareTo(FPN.of(r)); + expect(actual).toBe(1); + }); + + test('n ~ +Infinity -> -1', () => { + const l = 123.45; + const r = +Infinity; + const actual = FPN.of(l).compareTo(FPN.of(r)); + expect(actual).toBe(-1); + }); + + test('-Infinity ~ -Infinity -> 0', () => { + const l = -Infinity; + const r = -Infinity; + const actual = FPN.of(l).compareTo(FPN.of(r)); + expect(actual).toBe(0); + }); + + test('-Infinity ~ +Infinity -> -1', () => { + const l = -Infinity; + const r = Infinity; + const actual = FPN.of(l).compareTo(FPN.of(r)); + expect(actual).toBe(-1); + }); + + test('+Infinity ~ -Infinity -> 1', () => { + const l = Infinity; + const r = -Infinity; + const actual = FPN.of(l).compareTo(FPN.of(r)); + expect(actual).toBe(1); + }); + + test('+Infinity ~ +Infinity -> 0', () => { + const l = Infinity; + const r = Infinity; + const actual = FPN.of(l).compareTo(FPN.of(r)); + expect(actual).toBe(0); + }); + + test('l < r -> -1', () => { + const l = 123.45; + const r = l * 2; + const actual = FPN.of(l).compareTo(FPN.of(r)); + expect(actual).toBe(-1); + }); + + test('l = r -> 0', () => { + const l = 123.45; + const r = l; + const actual = FPN.of(l).compareTo(FPN.of(r)); + expect(actual).toBe(0); + }); + + test('l > r -> 1', () => { + const l = 123.45; + const r = l / 2; + const actual = FPN.of(l).compareTo(FPN.of(r)); + expect(actual).toBe(1); + }); + }); + + describe('isEqual method tests', () => { + test('NaN = n -> false', () => { + const l = NaN; + const r = 123.45; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('n = NaN -> false', () => { + const l = 123.45; + const r = NaN; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('-Infinity = n -> false', () => { + const l = -Infinity; + const r = 123.45; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('+Infinity = n -> false', () => { + const l = Infinity; + const r = 123.45; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('n = -Infinity -> false', () => { + const l = 123.45; + const r = -Infinity; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('n = +Infinity -> false', () => { + const l = 123.45; + const r = +Infinity; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('-Infinity = -Infinity -> true', () => { + const l = -Infinity; + const r = -Infinity; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('-Infinity = +Infinity -> false', () => { + const l = -Infinity; + const r = Infinity; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('+Infinity = -Infinity -> false', () => { + const l = Infinity; + const r = -Infinity; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('+Infinity = +Infinity -> true', () => { + const l = Infinity; + const r = Infinity; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('l < r -> false', () => { + const l = 123.45; + const r = l * 2; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + + test('l = r -> true', () => { + const l = 123.45; + const r = l; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(true); + }); + + test('l > r -> false', () => { + const l = 123.45; + const r = l / 2; + const actual = FPN.of(l).eq(FPN.of(r)); + const expected = BigNumber(l).eq(BigNumber(r)); + expect(actual).toBe(expected); + expect(actual).toBe(false); + }); + }); + }); + + describe('Construction tests', () => { + test('of NaN', () => { + const n = NaN; + const fpn = FPN.of(n); + expect(fpn).toBeInstanceOf(FPN); + expect(fpn.toString()).toBe(n.toString()); + }); + + test('of -Infinity', () => { + const n = -Infinity; + const fpn = FPN.of(n); + expect(fpn).toBeInstanceOf(FPN); + expect(fpn.toString()).toBe(n.toString()); + }); + + test('of +Infinity', () => { + const n = Infinity; + const fpn = FPN.of(n); + expect(fpn).toBeInstanceOf(FPN); + expect(fpn.toString()).toBe(n.toString()); + }); + + test('of bigint', () => { + const bi = Infinity; + const fpn = FPN.of(bi); + expect(fpn).toBeInstanceOf(FPN); + expect(fpn.toString()).toBe(bi.toString()); + }); + + test('of -n', () => { + const n = -123.0067; + const fpn = FPN.of(n); + expect(fpn).toBeInstanceOf(FPN); + expect(fpn.toString()).toBe(n.toString()); + }); + + test('of +n', () => { + const n = 123.0067; + const fpn = FPN.of(n); + expect(fpn).toBeInstanceOf(FPN); + expect(fpn.toString()).toBe(n.toString()); + }); + + test('of -n', () => { + const n = -123.0067; + const fpn = FPN.of(n); + expect(fpn).toBeInstanceOf(FPN); + expect(fpn.toString()).toBe(n.toString()); + }); + + test('of string', () => { + const n = -123.0067; + const fpn = FPN.of(n.toString()); + expect(fpn).toBeInstanceOf(FPN); + expect(fpn.toString()).toBe(n.toString()); + }); + }); + describe('abs method tests', () => { test('n < 0', () => { const n = -0.8; @@ -126,7 +461,6 @@ describe('FPN class tests', () => { const l = 123.45; const r = l; const actual = FPN.of(l).comparedTo(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(0); @@ -136,7 +470,6 @@ describe('FPN class tests', () => { const l = 123.45; const r = l / 2; const actual = FPN.of(l).comparedTo(FPN.of(r)); - // console.log(actual); const expected = BigNumber(l).comparedTo(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(1); @@ -148,7 +481,6 @@ describe('FPN class tests', () => { const dividend = 0; const divisor = 0; const actual = FPN.of(dividend).div(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -157,7 +489,6 @@ describe('FPN class tests', () => { const dividend = NaN; const divisor = 123.45; const actual = FPN.of(dividend).div(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -166,7 +497,6 @@ describe('FPN class tests', () => { const dividend = 123.45; const divisor = NaN; const actual = FPN.of(dividend).div(FPN.of(divisor)); - console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -175,7 +505,6 @@ describe('FPN class tests', () => { const dividend = 123.45; const divisor = -Infinity; const actual = FPN.of(dividend).div(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -184,7 +513,6 @@ describe('FPN class tests', () => { const dividend = 123.45; const divisor = Infinity; const actual = FPN.of(dividend).div(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -193,7 +521,6 @@ describe('FPN class tests', () => { const dividend = -123.45; const divisor = 0; const actual = FPN.of(dividend).div(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -202,7 +529,6 @@ describe('FPN class tests', () => { const dividend = 123.45; const divisor = 0; const actual = FPN.of(dividend).div(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -211,7 +537,6 @@ describe('FPN class tests', () => { const dividend = -1; const divisor = 3; const actual = FPN.of(dividend).div(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -220,7 +545,6 @@ describe('FPN class tests', () => { const dividend = 355; const divisor = 113; const actual = FPN.of(dividend).div(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); const dp = 15; // BigNumber default precision diverges after 15 digits. expect(actual.n.toFixed(dp)).toBe(expected.toNumber().toFixed(dp)); @@ -230,7 +554,6 @@ describe('FPN class tests', () => { const dividend = 355; const divisor = -5; const actual = FPN.of(dividend).div(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).div(BigNumber(divisor)); expect(actual.n).toBe(expected.toNumber()); }); @@ -241,7 +564,6 @@ describe('FPN class tests', () => { const dividend = 0; const divisor = 0; const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -250,7 +572,6 @@ describe('FPN class tests', () => { const dividend = NaN; const divisor = 123.45; const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -259,7 +580,6 @@ describe('FPN class tests', () => { const dividend = 123.45; const divisor = NaN; const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -268,7 +588,6 @@ describe('FPN class tests', () => { const dividend = 123.45; const divisor = -Infinity; const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -277,7 +596,6 @@ describe('FPN class tests', () => { const dividend = 123.45; const divisor = Infinity; const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -286,7 +604,6 @@ describe('FPN class tests', () => { const dividend = -123.45; const divisor = 0; const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -295,7 +612,6 @@ describe('FPN class tests', () => { const dividend = 123.45; const divisor = 0; const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -304,7 +620,6 @@ describe('FPN class tests', () => { const dividend = 5; const divisor = 3; const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); @@ -313,131 +628,11 @@ describe('FPN class tests', () => { const dividend = 5; const divisor = 0.7; const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - // console.log(actual.toString()); const expected = BigNumber(dividend).idiv(BigNumber(divisor)); expect(actual.toString()).toBe(expected.toString()); }); }); - describe('eq method tests', () => { - test('NaN = n -> false', () => { - const l = NaN; - const r = 123.45; - const actual = FPN.of(l).eq(FPN.of(r)); - const expected = BigNumber(l).eq(BigNumber(r)); - expect(actual).toBe(expected); - expect(actual).toBe(false); - }); - - test('n = NaN -> false', () => { - const l = 123.45; - const r = NaN; - const actual = FPN.of(l).eq(FPN.of(r)); - const expected = BigNumber(l).eq(BigNumber(r)); - expect(actual).toBe(expected); - expect(actual).toBe(false); - }); - - test('-Infinity = n -> false', () => { - const l = -Infinity; - const r = 123.45; - const actual = FPN.of(l).eq(FPN.of(r)); - const expected = BigNumber(l).eq(BigNumber(r)); - expect(actual).toBe(expected); - expect(actual).toBe(false); - }); - - test('+Infinity = n -> false', () => { - const l = Infinity; - const r = 123.45; - const actual = FPN.of(l).eq(FPN.of(r)); - const expected = BigNumber(l).eq(BigNumber(r)); - expect(actual).toBe(expected); - expect(actual).toBe(false); - }); - - test('n = -Infinity -> false', () => { - const l = 123.45; - const r = -Infinity; - const actual = FPN.of(l).eq(FPN.of(r)); - const expected = BigNumber(l).eq(BigNumber(r)); - expect(actual).toBe(expected); - expect(actual).toBe(false); - }); - - test('n = +Infinity -> false', () => { - const l = 123.45; - const r = +Infinity; - const actual = FPN.of(l).eq(FPN.of(r)); - const expected = BigNumber(l).eq(BigNumber(r)); - expect(actual).toBe(expected); - expect(actual).toBe(false); - }); - - test('-Infinity = -Infinity -> true', () => { - const l = -Infinity; - const r = -Infinity; - const actual = FPN.of(l).eq(FPN.of(r)); - const expected = BigNumber(l).eq(BigNumber(r)); - expect(actual).toBe(expected); - expect(actual).toBe(true); - }); - - test('-Infinity = +Infinity -> false', () => { - const l = -Infinity; - const r = Infinity; - const actual = FPN.of(l).eq(FPN.of(r)); - const expected = BigNumber(l).eq(BigNumber(r)); - expect(actual).toBe(expected); - expect(actual).toBe(false); - }); - - test('+Infinity = -Infinity -> false', () => { - const l = Infinity; - const r = -Infinity; - const actual = FPN.of(l).eq(FPN.of(r)); - const expected = BigNumber(l).eq(BigNumber(r)); - expect(actual).toBe(expected); - expect(actual).toBe(false); - }); - - test('+Infinity = +Infinity -> true', () => { - const l = Infinity; - const r = Infinity; - const actual = FPN.of(l).eq(FPN.of(r)); - const expected = BigNumber(l).eq(BigNumber(r)); - expect(actual).toBe(expected); - expect(actual).toBe(true); - }); - - test('l < r -> false', () => { - const l = 123.45; - const r = l * 2; - const actual = FPN.of(l).eq(FPN.of(r)); - const expected = BigNumber(l).eq(BigNumber(r)); - expect(actual).toBe(expected); - expect(actual).toBe(false); - }); - - test('l = r -> true', () => { - const l = 123.45; - const r = l; - const actual = FPN.of(l).eq(FPN.of(r)); - const expected = BigNumber(l).eq(BigNumber(r)); - expect(actual).toBe(expected); - expect(actual).toBe(true); - }); - - test('l > r -> false', () => { - const l = 123.45; - const r = l / 2; - const actual = FPN.of(l).eq(FPN.of(r)); - const expected = BigNumber(l).eq(BigNumber(r)); - expect(actual).toBe(expected); - expect(actual).toBe(false); - }); - }); - describe('gt method tests', () => { test('NaN > n -> false', () => { const l = NaN; From 061a3637f2bcf81e3acb21de38dd3e82815b8792 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Tue, 3 Sep 2024 17:39:35 +0100 Subject: [PATCH 45/48] feat: 1113 Fixed Point Number math additional tests --- packages/core/src/vcdm/FPN.ts | 13 +- packages/core/tests/vcdm/FPN.unit.test.ts | 265 +++++++++++++--------- 2 files changed, 163 insertions(+), 115 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index dca4eefcc..934bc0240 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -222,12 +222,7 @@ class FPN implements VeChainDataModel { ? FPN.NEGATIVE_INFINITY : FPN.POSITIVE_INFINITY; const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. - try { - return new FPN(fd, FPN.div(fd, this.dp(fd).sv, that.dp(fd).sv)); - } catch (e) { - if (e instanceof RangeError) return FPN.NaN; - else throw e; - } + return new FPN(fd, FPN.div(fd, this.dp(fd).sv, that.dp(fd).sv)); } /** @@ -657,7 +652,6 @@ class FPN implements VeChainDataModel { : FPN.POSITIVE_INFINITY; if (that.isNegativeInfinite()) return FPN.ZERO; if (that.isPositiveInfinite()) return FPN.POSITIVE_INFINITY; - if (that.isZero()) return FPN.of(1); const fd = this.fd > that.fd ? this.fd : that.fd; // Max common fractional decimals. return new FPN(fd, FPN.pow(fd, this.dp(fd).sv, that.dp(fd).sv)); } @@ -676,7 +670,7 @@ class FPN implements VeChainDataModel { return FPN.pow(fd, FPN.div(fd, sf, base), -exponent); // Recursive. } if (exponent === 0n) { - return 1n; + return 1n * sf; } if (exponent === sf) { return base; @@ -729,8 +723,7 @@ class FPN implements VeChainDataModel { try { return new FPN(this.fd, FPN.sqr(this.sv, this.fd)); } catch (e) { - if (e instanceof RangeError) return FPN.NaN; - else throw e; + return FPN.NaN; } } diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 18fe3b44e..13e949e7b 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -168,7 +168,7 @@ describe('FPN class tests', () => { test('NaN = n -> false', () => { const l = NaN; const r = 123.45; - const actual = FPN.of(l).eq(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(false); @@ -177,7 +177,7 @@ describe('FPN class tests', () => { test('n = NaN -> false', () => { const l = 123.45; const r = NaN; - const actual = FPN.of(l).eq(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(false); @@ -186,7 +186,7 @@ describe('FPN class tests', () => { test('-Infinity = n -> false', () => { const l = -Infinity; const r = 123.45; - const actual = FPN.of(l).eq(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(false); @@ -195,7 +195,7 @@ describe('FPN class tests', () => { test('+Infinity = n -> false', () => { const l = Infinity; const r = 123.45; - const actual = FPN.of(l).eq(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(false); @@ -204,7 +204,7 @@ describe('FPN class tests', () => { test('n = -Infinity -> false', () => { const l = 123.45; const r = -Infinity; - const actual = FPN.of(l).eq(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(false); @@ -213,7 +213,7 @@ describe('FPN class tests', () => { test('n = +Infinity -> false', () => { const l = 123.45; const r = +Infinity; - const actual = FPN.of(l).eq(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(false); @@ -222,7 +222,7 @@ describe('FPN class tests', () => { test('-Infinity = -Infinity -> true', () => { const l = -Infinity; const r = -Infinity; - const actual = FPN.of(l).eq(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(true); @@ -231,7 +231,7 @@ describe('FPN class tests', () => { test('-Infinity = +Infinity -> false', () => { const l = -Infinity; const r = Infinity; - const actual = FPN.of(l).eq(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(false); @@ -240,7 +240,7 @@ describe('FPN class tests', () => { test('+Infinity = -Infinity -> false', () => { const l = Infinity; const r = -Infinity; - const actual = FPN.of(l).eq(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(false); @@ -249,7 +249,7 @@ describe('FPN class tests', () => { test('+Infinity = +Infinity -> true', () => { const l = Infinity; const r = Infinity; - const actual = FPN.of(l).eq(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(true); @@ -258,7 +258,7 @@ describe('FPN class tests', () => { test('l < r -> false', () => { const l = 123.45; const r = l * 2; - const actual = FPN.of(l).eq(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(false); @@ -267,7 +267,7 @@ describe('FPN class tests', () => { test('l = r -> true', () => { const l = 123.45; const r = l; - const actual = FPN.of(l).eq(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(true); @@ -276,7 +276,7 @@ describe('FPN class tests', () => { test('l > r -> false', () => { const l = 123.45; const r = l / 2; - const actual = FPN.of(l).eq(FPN.of(r)); + const actual = FPN.of(l).isEqual(FPN.of(r)); const expected = BigNumber(l).eq(BigNumber(r)); expect(actual).toBe(expected); expect(actual).toBe(false); @@ -334,12 +334,19 @@ describe('FPN class tests', () => { expect(fpn.toString()).toBe(n.toString()); }); - test('of string', () => { + test('of negative string', () => { const n = -123.0067; const fpn = FPN.of(n.toString()); expect(fpn).toBeInstanceOf(FPN); expect(fpn.toString()).toBe(n.toString()); }); + + test('of positive string', () => { + const exp = '+123.45'; + const fpn = FPN.of(exp); + expect(fpn).toBeInstanceOf(FPN); + expect(fpn.n).toBe(Number(exp)); + }); }); describe('abs method tests', () => { @@ -476,161 +483,194 @@ describe('FPN class tests', () => { }); }); + describe('dp methods', () => { + test('scale down', () => { + const fd = 5n; + const n = 123.45; + expect(FPN.of(n).dp(fd)).toEqual(FPN.of(n, fd)); + }); + + test('scale up', () => { + const fd = 25n; + const n = 123.45; + expect(FPN.of(n).dp(fd)).toEqual(FPN.of(n, fd)); + }); + }); + describe('div method tests', () => { test('0/0 = NaN', () => { - const dividend = 0; - const divisor = 0; - const actual = FPN.of(dividend).div(FPN.of(divisor)); - const expected = BigNumber(dividend).div(BigNumber(divisor)); + const lr = 0; + const r = 0; + const actual = FPN.of(lr).div(FPN.of(r)); + const expected = BigNumber(lr).div(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('NaN / n = ', () => { - const dividend = NaN; - const divisor = 123.45; - const actual = FPN.of(dividend).div(FPN.of(divisor)); - const expected = BigNumber(dividend).div(BigNumber(divisor)); + const lr = NaN; + const r = 123.45; + const actual = FPN.of(lr).div(FPN.of(r)); + const expected = BigNumber(lr).div(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('n / NaN = NaN', () => { - const dividend = 123.45; - const divisor = NaN; - const actual = FPN.of(dividend).div(FPN.of(divisor)); - const expected = BigNumber(dividend).div(BigNumber(divisor)); + const lr = 123.45; + const r = NaN; + const actual = FPN.of(lr).div(FPN.of(r)); + const expected = BigNumber(lr).div(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('n / -Infinity = 0', () => { - const dividend = 123.45; - const divisor = -Infinity; - const actual = FPN.of(dividend).div(FPN.of(divisor)); - const expected = BigNumber(dividend).div(BigNumber(divisor)); + const lr = 123.45; + const r = -Infinity; + const actual = FPN.of(lr).div(FPN.of(r)); + const expected = BigNumber(lr).div(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('n / +Infinity = 0', () => { - const dividend = 123.45; - const divisor = Infinity; - const actual = FPN.of(dividend).div(FPN.of(divisor)); - const expected = BigNumber(dividend).div(BigNumber(divisor)); + const lr = 123.45; + const r = Infinity; + const actual = FPN.of(lr).div(FPN.of(r)); + const expected = BigNumber(lr).div(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('-n / 0 = -Infinity', () => { - const dividend = -123.45; - const divisor = 0; - const actual = FPN.of(dividend).div(FPN.of(divisor)); - const expected = BigNumber(dividend).div(BigNumber(divisor)); + const lr = -123.45; + const r = 0; + const actual = FPN.of(lr).div(FPN.of(r)); + const expected = BigNumber(lr).div(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('+n / 0 = +Infinity', () => { - const dividend = 123.45; - const divisor = 0; - const actual = FPN.of(dividend).div(FPN.of(divisor)); - const expected = BigNumber(dividend).div(BigNumber(divisor)); + const lr = 123.45; + const r = 0; + const actual = FPN.of(lr).div(FPN.of(r)); + const expected = BigNumber(lr).div(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('x / y = periodic', () => { - const dividend = -1; - const divisor = 3; - const actual = FPN.of(dividend).div(FPN.of(divisor)); - const expected = BigNumber(dividend).div(BigNumber(divisor)); + const lr = -1; + const r = 3; + const actual = FPN.of(lr).div(FPN.of(r)); + const expected = BigNumber(lr).div(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('x / y = real', () => { - const dividend = 355; - const divisor = 113; - const actual = FPN.of(dividend).div(FPN.of(divisor)); - const expected = BigNumber(dividend).div(BigNumber(divisor)); + const lr = 355; + const r = 113; + const actual = FPN.of(lr).div(FPN.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)); }); test('x / y = integer', () => { - const dividend = 355; - const divisor = -5; - const actual = FPN.of(dividend).div(FPN.of(divisor)); - const expected = BigNumber(dividend).div(BigNumber(divisor)); + const lr = 355; + const r = -5; + const actual = FPN.of(lr).div(FPN.of(r)); + const expected = BigNumber(lr).div(BigNumber(r)); expect(actual.n).toBe(expected.toNumber()); }); + + test('x / 1 = x scale test', () => { + const l = 123.45; + const r = 1; + const actualUp = FPN.of(l, 7n).div(FPN.of(r, 5n)); + const actualDn = FPN.of(l, 5n).div(FPN.of(r, 7n)); + expect(actualUp.isEqual(actualDn)).toBe(true); + expect(actualUp.isEqual(FPN.of(l))).toBe(true); + }); }); describe('idiv method tests', () => { test('0/0 = NaN', () => { - const dividend = 0; - const divisor = 0; - const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + const l = 0; + const r = 0; + const actual = FPN.of(l).idiv(FPN.of(r)); + const expected = BigNumber(l).idiv(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('NaN / n = ', () => { - const dividend = NaN; - const divisor = 123.45; - const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + const l = NaN; + const r = 123.45; + const actual = FPN.of(l).idiv(FPN.of(r)); + const expected = BigNumber(l).idiv(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('n / NaN = NaN', () => { - const dividend = 123.45; - const divisor = NaN; - const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + const l = 123.45; + const r = NaN; + const actual = FPN.of(l).idiv(FPN.of(r)); + const expected = BigNumber(l).idiv(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('n / -Infinity = 0', () => { - const dividend = 123.45; - const divisor = -Infinity; - const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + const l = 123.45; + const r = -Infinity; + const actual = FPN.of(l).idiv(FPN.of(r)); + const expected = BigNumber(l).idiv(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('n / +Infinity = 0', () => { - const dividend = 123.45; - const divisor = Infinity; - const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + const l = 123.45; + const r = Infinity; + const actual = FPN.of(l).idiv(FPN.of(r)); + const expected = BigNumber(l).idiv(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('-n / 0 = -Infinity', () => { - const dividend = -123.45; - const divisor = 0; - const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + const l = -123.45; + const r = 0; + const actual = FPN.of(l).idiv(FPN.of(r)); + const expected = BigNumber(l).idiv(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('+n / 0 = +Infinity', () => { - const dividend = 123.45; - const divisor = 0; - const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + const l = 123.45; + const r = 0; + const actual = FPN.of(l).idiv(FPN.of(r)); + const expected = BigNumber(l).idiv(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('n / integer', () => { - const dividend = 5; - const divisor = 3; - const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + const l = 5; + const r = 3; + const actual = FPN.of(l).idiv(FPN.of(r)); + const expected = BigNumber(l).idiv(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); test('n / rational', () => { - const dividend = 5; - const divisor = 0.7; - const actual = FPN.of(dividend).idiv(FPN.of(divisor)); - const expected = BigNumber(dividend).idiv(BigNumber(divisor)); + const l = 5; + const r = 0.7; + const actual = FPN.of(l).idiv(FPN.of(r)); + const expected = BigNumber(l).idiv(BigNumber(r)); expect(actual.toString()).toBe(expected.toString()); }); + + test('x / 1 = x scale test', () => { + const l = 123.45; + const r = 1; + const actualUp = FPN.of(l, 7n).idiv(FPN.of(r, 5n)); + const actualDn = FPN.of(l, 5n).idiv(FPN.of(r, 7n)); + const expected = BigNumber(l).idiv(BigNumber(r)); + expect(actualUp.isEqual(actualDn)).toBe(true); + expect(actualDn.n).toBe(expected.toNumber()); + }); }); describe('gt method tests', () => { @@ -1515,13 +1555,15 @@ describe('FPN class tests', () => { expect(actual.eq(FPN.of(l))).toBe(true); }); - test('n - n -> 0', () => { + test('n - n -> 0 scale test', () => { const l = 123.45; const r = l; - const actual = FPN.of(l).minus(FPN.of(r)); + const actualUp = FPN.of(l, 7n).minus(FPN.of(r, 5n)); + const actualDn = FPN.of(l, 5n).minus(FPN.of(r, 7n)); const expected = BigNumber(l).minus(BigNumber(r)); - expect(actual.n).toBe(expected.toNumber()); - expect(actual.eq(FPN.ZERO)).toBe(true); + expect(actualUp.n).toBe(expected.toNumber()); + expect(actualUp.eq(FPN.ZERO)).toBe(true); + expect(actualDn.eq(actualUp)).toBe(true); }); test('l - r -> >0', () => { @@ -1637,13 +1679,14 @@ describe('FPN class tests', () => { expect(actual.isZero()).toBe(true); }); - test('n % ±1 -> 0', () => { + test('n % ±1 -> 0 - scale test', () => { const l = 123.45; const r = 0.6789; - const actual = FPN.of(l).modulo(FPN.of(r)); + const actualUp = FPN.of(l, 7n).modulo(FPN.of(r, 5n)); + const actualDn = FPN.of(l, 5n).modulo(FPN.of(r, 7n)); const expected = BigNumber(l).modulo(BigNumber(r)); - expect(FPN.of(l).modulo(FPN.of(-r))).toEqual(actual); - expect(actual).toEqual(FPN.of(expected.toNumber())); + expect(actualUp.eq(FPN.of(expected.toNumber()))).toBe(true); + expect(actualUp.eq(actualDn)).toBe(true); }); }); @@ -1731,13 +1774,15 @@ describe('FPN class tests', () => { expect(actual.eq(FPN.of(l))).toBe(true); }); - test('n + -n -> 0', () => { + test('n + -n -> 0 - scale test', () => { const l = 123.45; const r = -l; - const actual = FPN.of(l).plus(FPN.of(r)); + const actualUp = FPN.of(l, 7n).plus(FPN.of(r, 5n)); + const actualDn = FPN.of(l, 7n).plus(FPN.of(r, 5n)); const expected = BigNumber(l).plus(BigNumber(r)); - expect(actual.n).toBe(expected.toNumber()); - expect(actual.eq(FPN.ZERO)).toBe(true); + expect(actualUp.n).toBe(expected.toNumber()); + expect(actualUp.eq(FPN.ZERO)).toBe(true); + expect(actualUp.eq(actualDn)).toBe(true); }); test('l + r -> >0', () => { @@ -1887,6 +1932,14 @@ describe('FPN class tests', () => { expect(actual.n.toFixed(fd)).toBe(expected.toNumber().toFixed(fd)); expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); }); + + test('±b ^ 0 = 1', () => { + const b = 123.45; + const e = 0; + const actual = FPN.of(b).pow(FPN.of(e)); + expect(actual).toEqual(FPN.of(1)); + expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); + }); }); describe('sqrt method tests', () => { @@ -2020,12 +2073,14 @@ describe('FPN class tests', () => { expect(actual.n).toBe(Infinity); }); - test('l * r', () => { + test('l * r - scale test', () => { const l = 0.6; const r = 3; - const actual = FPN.of(l).times(FPN.of(r)); + const actualUp = FPN.of(l, 7n).times(FPN.of(r, 5n)); + const actualDn = FPN.of(l, 5n).times(FPN.of(r, 7n)); const expected = BigNumber(l).times(BigNumber(r)); - expect(actual).toEqual(FPN.of(expected.toNumber())); + expect(actualUp.eq(FPN.of(expected.toNumber()))).toBe(true); + expect(actualUp.eq(actualDn)).toBe(true); }); }); From 567584ca9d53e2b50297e34cd4b6ac911bd6c949 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Tue, 3 Sep 2024 17:43:21 +0100 Subject: [PATCH 46/48] feat: 1113 Fixed Point Number math additional tests --- packages/core/tests/vcdm/FPN.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index 13e949e7b..e9a7bd6b6 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -1778,7 +1778,7 @@ describe('FPN class tests', () => { const l = 123.45; const r = -l; const actualUp = FPN.of(l, 7n).plus(FPN.of(r, 5n)); - const actualDn = FPN.of(l, 7n).plus(FPN.of(r, 5n)); + const actualDn = FPN.of(l, 5n).plus(FPN.of(r, 7n)); const expected = BigNumber(l).plus(BigNumber(r)); expect(actualUp.n).toBe(expected.toNumber()); expect(actualUp.eq(FPN.ZERO)).toBe(true); From 3e7e1a6e4dcf372e0c4e14bd3a47836227abf35c Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Wed, 4 Sep 2024 10:45:13 +0100 Subject: [PATCH 47/48] feat: 1113 Fixed Point Number math additional tests --- packages/core/src/vcdm/FPN.ts | 4 +++- packages/core/tests/vcdm/FPN.unit.test.ts | 13 ++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index 934bc0240..b80b38d76 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -235,7 +235,7 @@ class FPN 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 divisor === 1n ? dividend : (10n ** fd * dividend) / divisor; + return (10n ** fd * dividend) / divisor; } /** @@ -639,6 +639,8 @@ class FPN implements VeChainDataModel { * @return {FPN} - The result of raising this fixed-point number to the power of the given exponent. * * @remarks The precision is the greater of the precision of the two operands. + * @remarks In fixed-precision math, the comparisons between powers of operands having different fractional + * precision can lead to differences. * * @see [bignumber.js exponentiatedBy](https://mikemcl.github.io/bignumber.js/#pow) */ diff --git a/packages/core/tests/vcdm/FPN.unit.test.ts b/packages/core/tests/vcdm/FPN.unit.test.ts index e9a7bd6b6..50153ad11 100644 --- a/packages/core/tests/vcdm/FPN.unit.test.ts +++ b/packages/core/tests/vcdm/FPN.unit.test.ts @@ -1913,17 +1913,20 @@ describe('FPN class tests', () => { expect(actual.n).toBe(expected); }); - test('±b ^ -e', () => { + test('b ^ -e - scale test', () => { const b = 3; const e = -2; - const actual = FPN.of(b).pow(FPN.of(e)); + const actualUp = FPN.of(b, 25n).pow(FPN.of(e, 15n)); + const actualDn = FPN.of(b, 15n).pow(FPN.of(e, 25n)); const expected = BigNumber(b).pow(BigNumber(e)); const fd = 16; // Fractional digits before divergence. - expect(actual.n.toFixed(fd)).toBe(expected.toNumber().toFixed(fd)); - expect(FPN.of(-b).pow(FPN.of(e))).toEqual(actual); + expect(actualUp.n.toFixed(fd)).toBe( + expected.toNumber().toFixed(fd) + ); + expect(actualUp.eq(actualDn)).toBe(true); }); - test('±b ^ +e', () => { + test('±b ^ +e - scale test', () => { const b = 0.7; const e = -2; const actual = FPN.of(b).pow(FPN.of(e)); From 851d22277e1089aefa2de55a003ba5634b225e99 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Wed, 4 Sep 2024 10:50:16 +0100 Subject: [PATCH 48/48] feat: 1113 Fixed Point Number math additional tests --- packages/core/src/vcdm/FPN.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/vcdm/FPN.ts b/packages/core/src/vcdm/FPN.ts index b80b38d76..15b6a09aa 100644 --- a/packages/core/src/vcdm/FPN.ts +++ b/packages/core/src/vcdm/FPN.ts @@ -114,7 +114,7 @@ class FPN implements VeChainDataModel { } /** - * Returns the new Fixed-Point Number (FDN) instance having + * Returns the new Fixed-Point Number (FPN) instance having * * @param {bigint} fd - Number of Fractional Digits (or decimal places). * @param {bigint} sv - Scaled Value.