mirror of
https://github.com/pezkuwichain/pezkuwi-common.git
synced 2026-04-22 02:07:56 +00:00
Initial rebrand: @polkadot -> @pezkuwi (14 packages)
- Package namespace: @polkadot/* -> @pezkuwi/* - Repository: polkadot-js/common -> pezkuwichain/pezkuwi-common - Author: Pezkuwi Team <team@pezkuwichain.io> Core packages: - @pezkuwi/util (utilities) - @pezkuwi/util-crypto (crypto primitives) - @pezkuwi/keyring (account management) - @pezkuwi/networks (chain metadata) - @pezkuwi/hw-ledger (Ledger hardware wallet) - @pezkuwi/x-* (10 polyfill packages) Total: 14 packages Upstream: polkadot-js/common v14.0.1
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# @pezkuwi/util
|
||||
|
||||
Various useful utility functions that are used across all projects in the [@pezkuwi](https://pezkuwi.js.org) namespace. It provides utility functions with additional safety checks, allowing not only for consistent coding, but also reducing the general boilerplate.
|
||||
|
||||
## Usage
|
||||
|
||||
Installation -
|
||||
|
||||
```
|
||||
yarn add @pezkuwi/util
|
||||
```
|
||||
|
||||
Functions can be imported directly from the package, e.g.
|
||||
|
||||
```js
|
||||
import { isHex } from '@pezkuwi/util';
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||
"bugs": "https://github.com/pezkuwichain/pezkuwi-common/issues",
|
||||
"description": "A collection of useful utilities for @pezkuwi",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"homepage": "https://github.com/pezkuwichain/pezkuwi-common/tree/master/packages/util#readme",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@pezkuwi/util",
|
||||
"repository": {
|
||||
"directory": "packages/util",
|
||||
"type": "git",
|
||||
"url": "https://github.com/pezkuwichain/pezkuwi-common.git"
|
||||
},
|
||||
"sideEffects": [
|
||||
"./packageDetect.js",
|
||||
"./packageDetect.cjs"
|
||||
],
|
||||
"type": "module",
|
||||
"version": "14.0.1",
|
||||
"main": "index.js",
|
||||
"exports": {
|
||||
"./hex/toU8a": {
|
||||
"node": {
|
||||
"require": "./cjs/hex/toU8aBuffer.js",
|
||||
"default": "./hex/toU8aBuffer.js"
|
||||
}
|
||||
},
|
||||
"./u8a/toHex": {
|
||||
"node": {
|
||||
"require": "./cjs/u8a/toHexBuffer.js",
|
||||
"default": "./u8a/toHexBuffer.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@pezkuwi/x-bigint": "14.0.1",
|
||||
"@pezkuwi/x-global": "14.0.1",
|
||||
"@pezkuwi/x-textdecoder": "14.0.1",
|
||||
"@pezkuwi/x-textencoder": "14.0.1",
|
||||
"@types/bn.js": "^5.1.6",
|
||||
"bn.js": "^5.2.1",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pezkuwi/x-randomvalues": "14.0.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { arrayChunk, arrayRange } from '../index.js';
|
||||
import { perf } from '../test/index.js';
|
||||
|
||||
describe('arrayChunk', (): void => {
|
||||
it('chunks with exact', (): void => {
|
||||
expect(
|
||||
arrayChunk([1, 2, 3, 4, 5, 6, 7, 8], 8)
|
||||
).toEqual([[1, 2, 3, 4, 5, 6, 7, 8]]);
|
||||
});
|
||||
|
||||
it('chunks with unequal', (): void => {
|
||||
expect(
|
||||
arrayChunk([1, 2, 3, 4, 5, 6, 7], 3)
|
||||
).toEqual([[1, 2, 3], [4, 5, 6], [7]]);
|
||||
});
|
||||
|
||||
it('chunks with non-empty results', (): void => {
|
||||
expect(
|
||||
arrayChunk([[1, 2], [3, 4], [5, 6], [7, 8]], 2)
|
||||
).toEqual([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]);
|
||||
});
|
||||
|
||||
perf('arrayChunk', 200_000, [[arrayRange(500), 50]], arrayChunk);
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @name arrayChunk
|
||||
* @summary Split T[] into T[][] based on the defind size
|
||||
* @description
|
||||
* Returns a set ao arrays based on the chunksize
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { arrayChunk } from '@pezkuwi/util';
|
||||
*
|
||||
* arrayChunk([1, 2, 3, 4, 5]); // [[1, 2], [3, 4], [5]]
|
||||
* ```
|
||||
*/
|
||||
export function arrayChunk <T> (array: T[], chunkSize: number): T[][] {
|
||||
const outputSize = Math.ceil(array.length / chunkSize);
|
||||
|
||||
// shortcut for the single-split case
|
||||
if (outputSize === 1) {
|
||||
return [array];
|
||||
}
|
||||
|
||||
const output = Array<T[]>(outputSize);
|
||||
|
||||
for (let i = 0; i < outputSize; i++) {
|
||||
const offset = i * chunkSize;
|
||||
|
||||
output[i] = array.slice(offset, offset + chunkSize);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { arrayFilter } from './index.js';
|
||||
|
||||
describe('filterArray', (): void => {
|
||||
it('filters arrays, removing undefined', (): void => {
|
||||
expect(
|
||||
arrayFilter([0, '', null, false, undefined, NaN])
|
||||
).toEqual([0, '', null, false, NaN]);
|
||||
});
|
||||
|
||||
it('filters arrays, removing undefined & null (allowNull = false)', (): void => {
|
||||
expect(
|
||||
arrayFilter([0, '', null, false, undefined, NaN], false)
|
||||
).toEqual([0, '', false, NaN]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @name arrayFilter
|
||||
* @summary Filters undefined and (optionally) null values from an array
|
||||
* @description
|
||||
* Returns a new array with all `undefined` values removed. Optionally, when `allowNulls = false`, it removes the `null` values as well
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { arrayFilter } from '@pezkuwi/util';
|
||||
*
|
||||
* arrayFilter([0, void 0, true, null, false, '']); // [0, true, null, false, '']
|
||||
* arrayFilter([0, void 0, true, null, false, ''], false); // [0, true, false, '']
|
||||
* ```
|
||||
*/
|
||||
export function arrayFilter <T = unknown> (array: readonly (T | null | undefined)[], allowNulls = true): T[] {
|
||||
return array.filter((v): v is T =>
|
||||
v !== undefined &&
|
||||
(allowNulls || v !== null)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { perf } from '../test/index.js';
|
||||
import { arrayFlatten } from './index.js';
|
||||
|
||||
const PERF_ONE = [[1, 2, 3, 4, 5]];
|
||||
const PERF_MUL = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
|
||||
|
||||
describe('arrayFlatten', (): void => {
|
||||
it('flattens arrays', (): void => {
|
||||
expect(
|
||||
arrayFlatten([[0], [1, 2, 3], [4, 5], [6], [], [7, 8]])
|
||||
).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
});
|
||||
|
||||
it('flattens a single entry', (): void => {
|
||||
expect(
|
||||
arrayFlatten([[1, 2, 3, 4, 5]])
|
||||
).toEqual([1, 2, 3, 4, 5]);
|
||||
});
|
||||
|
||||
it('flattens an empty', (): void => {
|
||||
expect(
|
||||
arrayFlatten([])
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
perf('arrayFlatten (single)', 10_000_000, [[PERF_ONE]], arrayFlatten);
|
||||
perf('arrayFlatten (multi)', 700_000, [[PERF_MUL]], arrayFlatten);
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// This is supposed to be a faster concat...
|
||||
// https://dev.to/uilicious/javascript-array-push-is-945x-faster-than-array-concat-1oki
|
||||
|
||||
/**
|
||||
* @name arrayFlatten
|
||||
* @summary Merge T[][] into T[]
|
||||
* @description
|
||||
* Returns a new array with all arrays merged into one
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { arrayFlatten } from '@pezkuwi/util';
|
||||
*
|
||||
* arrayFlatten([[1, 2], [3, 4], [5]]); // [1, 2, 3, 4, 5]
|
||||
* ```
|
||||
*/
|
||||
export function arrayFlatten <T> (arrays: readonly T[][]): T[] {
|
||||
const num = arrays.length;
|
||||
|
||||
// shortcuts for the empty & single-entry case
|
||||
if (num === 0) {
|
||||
return [];
|
||||
} else if (num === 1) {
|
||||
return arrays[0];
|
||||
}
|
||||
|
||||
// pre-allocate based on the combined size
|
||||
let size = 0;
|
||||
|
||||
for (let i = 0; i < num; i++) {
|
||||
size += arrays[i].length;
|
||||
}
|
||||
|
||||
const output = new Array<T>(size);
|
||||
let i = -1;
|
||||
|
||||
for (let j = 0; j < num; j++) {
|
||||
const a = arrays[j];
|
||||
|
||||
// instead of pushing, we just set the entries
|
||||
for (let e = 0, count = a.length; e < count; e++) {
|
||||
output[++i] = a[e];
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @summary Utility methods that operates on arrays
|
||||
*/
|
||||
|
||||
export { arrayChunk } from './chunk.js';
|
||||
export { arrayFilter } from './filter.js';
|
||||
export { arrayFlatten } from './flatten.js';
|
||||
export { arrayRange } from './range.js';
|
||||
export { arrayShuffle } from './shuffle.js';
|
||||
export { arrayUnzip } from './unzip.js';
|
||||
export { arrayZip } from './zip.js';
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { perf } from '../test/index.js';
|
||||
import { arrayRange } from './index.js';
|
||||
|
||||
describe('arrayRange', (): void => {
|
||||
it('does not allow 0 values', (): void => {
|
||||
expect(
|
||||
() => arrayRange(0)
|
||||
).toThrow(/Expected non-zero, positive number as a range size/);
|
||||
});
|
||||
|
||||
it('creates a range of the specified length', (): void => {
|
||||
expect(
|
||||
arrayRange(10)
|
||||
).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
});
|
||||
|
||||
it('creates a range of the specified length, with offset', (): void => {
|
||||
expect(
|
||||
arrayRange(7, 3)
|
||||
).toEqual([3, 4, 5, 6, 7, 8, 9]);
|
||||
});
|
||||
|
||||
perf('arrayRange (100 entries)', 1_000_000, [[100]], arrayRange);
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @name arrayRange
|
||||
* @summary Returns a range of numbers ith the size and the specified offset
|
||||
* @description
|
||||
* Returns a new array of numbers with the specific size. Optionally, when `startAt`, is provided, it generates the range to start at a specific value.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { arrayRange } from '@pezkuwi/util';
|
||||
*
|
||||
* arrayRange(5); // [0, 1, 2, 3, 4]
|
||||
* arrayRange(3, 5); // [5, 6, 7]
|
||||
* ```
|
||||
*/
|
||||
export function arrayRange (size: number, startAt = 0): number[] {
|
||||
if (size <= 0) {
|
||||
throw new Error('Expected non-zero, positive number as a range size');
|
||||
}
|
||||
|
||||
const result = new Array<number>(size);
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
result[i] = i + startAt;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { stringify } from '../stringify.js';
|
||||
import { perf } from '../test/index.js';
|
||||
import { arrayRange, arrayShuffle } from './index.js';
|
||||
|
||||
const ptest = arrayRange(16284);
|
||||
|
||||
describe('arrayShuffle', (): void => {
|
||||
it('returns an empty array as-is', (): void => {
|
||||
expect(
|
||||
arrayShuffle([])
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns a single array as-is', (): void => {
|
||||
expect(
|
||||
arrayShuffle([100])
|
||||
).toEqual([100]);
|
||||
});
|
||||
|
||||
it('shuffles an array', (): void => {
|
||||
const inp = arrayRange(100);
|
||||
const out = arrayShuffle(inp);
|
||||
|
||||
expect(inp).toHaveLength(out.length);
|
||||
expect(
|
||||
inp.filter((v) => !out.includes(v))
|
||||
).toEqual([]);
|
||||
expect(
|
||||
stringify(inp)
|
||||
).not.toEqual(stringify(out));
|
||||
});
|
||||
|
||||
perf('arrayShuffle', 1000, [[ptest]], arrayShuffle);
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @name arrayShuffle
|
||||
* @description Shuffles the input array (unlike sort, this is not done in-place)
|
||||
*/
|
||||
export function arrayShuffle <T> (input: readonly T[]): T[] {
|
||||
const result = input.slice();
|
||||
let curr = result.length;
|
||||
|
||||
// noop for the single entry
|
||||
if (curr === 1) {
|
||||
return result;
|
||||
}
|
||||
|
||||
while (curr !== 0) {
|
||||
// ~~ is more performant than Math.floor
|
||||
const rand = ~~(Math.random() * curr);
|
||||
|
||||
curr--;
|
||||
|
||||
[result[curr], result[rand]] = [result[rand], result[curr]];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { arrayUnzip } from './index.js';
|
||||
|
||||
describe('arrayUnzip', (): void => {
|
||||
it('unzips entries', (): void => {
|
||||
expect(
|
||||
arrayUnzip([['a', 1], ['b', 2], ['c', 3]])
|
||||
).toEqual([['a', 'b', 'c'], [1, 2, 3]]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @name arrayUnzip
|
||||
* @description Splits a single [K, V][] into [K[], V[]]
|
||||
*/
|
||||
export function arrayUnzip <K, V> (entries: readonly [K, V][]): [K[], V[]] {
|
||||
const count = entries.length;
|
||||
const keys = new Array<K>(count);
|
||||
const values = new Array<V>(count);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
[keys[i], values[i]] = entries[i];
|
||||
}
|
||||
|
||||
return [keys, values];
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { arrayZip } from './index.js';
|
||||
|
||||
describe('arrayZip', (): void => {
|
||||
it('zips a simple one', (): void => {
|
||||
expect(
|
||||
arrayZip(['a', 'b', 'c'], [1, 2, 3])
|
||||
).toEqual([['a', 1], ['b', 2], ['c', 3]]);
|
||||
});
|
||||
|
||||
it('zips where values > keys', (): void => {
|
||||
expect(
|
||||
arrayZip(['a', 'b', 'c'], [1, 2, 3, 4])
|
||||
).toEqual([['a', 1], ['b', 2], ['c', 3]]);
|
||||
});
|
||||
|
||||
it('zips where values < keys', (): void => {
|
||||
expect(
|
||||
arrayZip(['a', 'b', 'c'], [1, 2])
|
||||
).toEqual([['a', 1], ['b', 2], ['c', undefined]]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @name arrayZip
|
||||
* @description Combines 2 distinct key/value arrays into a single [K, V] array
|
||||
*/
|
||||
export function arrayZip <K, V> (keys: readonly K[], values: readonly V[]): [K, V][] {
|
||||
const count = keys.length;
|
||||
const result = new Array<[K, V]>(count);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
result[i] = [keys[i], values[i]];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { assert, assertReturn } from './index.js';
|
||||
|
||||
describe('assert', (): void => {
|
||||
it('should not throw an error when test is true', (): void => {
|
||||
assert(true, 'nothing should be thrown');
|
||||
});
|
||||
|
||||
it('should throw an error when test is not true', (): void => {
|
||||
expect(
|
||||
() => assert(false, 'error thrown')
|
||||
).toThrow(/error thrown/);
|
||||
});
|
||||
|
||||
it('should throw an error when message: () => string', (): void => {
|
||||
expect(
|
||||
() => assert(false, (): string => 'message from function')
|
||||
).toThrow(/message from function/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assertReturn', (): void => {
|
||||
it('should not throw an error when result is true', (): void => {
|
||||
expect(assertReturn(true, 'nothing should be thrown')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should not throw an error when result is false', (): void => {
|
||||
expect(assertReturn(false, 'nothing should be thrown')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should throw an error when result is undefined', (): void => {
|
||||
expect(
|
||||
() => assertReturn(undefined, 'something thrown')
|
||||
).toThrow(/something thrown/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { isFunction } from './is/function.js';
|
||||
|
||||
type MessageFn = () => string;
|
||||
|
||||
/**
|
||||
* @name assert
|
||||
* @summary Checks for a valid test, if not Error is thrown.
|
||||
* @description
|
||||
* Checks that `test` is a truthy value. If value is falsy (`null`, `undefined`, `false`, ...), it throws an Error with the supplied `message`. When `test` passes, `true` is returned.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* const { assert } from '@pezkuwi/util';
|
||||
*
|
||||
* assert(true, 'True should be true'); // passes
|
||||
* assert(false, 'False should not be true'); // Error thrown
|
||||
* assert(false, () => 'message'); // Error with 'message'
|
||||
* ```
|
||||
*/
|
||||
export function assert (condition: unknown, message: string | MessageFn): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(
|
||||
isFunction(message)
|
||||
? message()
|
||||
: message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name assertReturn
|
||||
* @description Returns when the value is not undefined/null, otherwise throws assertion error
|
||||
*/
|
||||
export function assertReturn <T> (value: T | undefined | null, message: string | MessageFn): T {
|
||||
assert(value !== undefined && value !== null, message);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name assertUnreachable
|
||||
* @description An assertion helper that ensures all codepaths are followed
|
||||
*/
|
||||
export function assertUnreachable (x: never): never {
|
||||
throw new Error(`This codepath should be unreachable. Unhandled input: ${x as unknown as string}`);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { BigInt } from '@pezkuwi/x-bigint';
|
||||
|
||||
/**
|
||||
* @name _0n
|
||||
* @summary BigInt constant for 0.
|
||||
*/
|
||||
export const _0n = /*#__PURE__*/ BigInt(0);
|
||||
|
||||
/**
|
||||
* @name _1n
|
||||
* @summary BigInt constant for 1.
|
||||
*/
|
||||
export const _1n = /*#__PURE__*/ BigInt(1);
|
||||
|
||||
/**
|
||||
* @name _2n
|
||||
* @summary BigInt constant for 2.
|
||||
*/
|
||||
export const _2n = /*#__PURE__*/ BigInt(2);
|
||||
|
||||
/**
|
||||
* @name _3n
|
||||
* @summary BigInt constant for 3.
|
||||
*/
|
||||
export const _3n = /*#__PURE__*/ BigInt(3);
|
||||
|
||||
/**
|
||||
* @name _4n
|
||||
* @summary BigInt constant for 4.
|
||||
*/
|
||||
export const _4n = /*#__PURE__*/ BigInt(4);
|
||||
|
||||
/**
|
||||
* @name _5n
|
||||
* @summary BigInt constant for 5.
|
||||
*/
|
||||
export const _5n = /*#__PURE__*/ BigInt(5);
|
||||
|
||||
/**
|
||||
* @name _6n
|
||||
* @summary BigInt constant for 6.
|
||||
*/
|
||||
export const _6n = /*#__PURE__*/ BigInt(6);
|
||||
|
||||
/**
|
||||
* @name _7n
|
||||
* @summary BigInt constant for 7.
|
||||
*/
|
||||
export const _7n = /*#__PURE__*/ BigInt(7);
|
||||
|
||||
/**
|
||||
* @name _8n
|
||||
* @summary BigInt constant for 8.
|
||||
*/
|
||||
export const _8n = /*#__PURE__*/ BigInt(8);
|
||||
|
||||
/**
|
||||
* @name _9n
|
||||
* @summary BigInt constant for 9.
|
||||
*/
|
||||
export const _9n = /*#__PURE__*/ BigInt(9);
|
||||
|
||||
/**
|
||||
* @name _10n
|
||||
* @summary BigInt constant for 10.
|
||||
*/
|
||||
export const _10n = /*#__PURE__*/ BigInt(10);
|
||||
|
||||
/**
|
||||
* @name _100n
|
||||
* @summary BigInt constant for 100.
|
||||
*/
|
||||
export const _100n = /*#__PURE__*/ BigInt(100);
|
||||
|
||||
/**
|
||||
* @name _1000n
|
||||
* @summary BigInt constant for 1000.
|
||||
*/
|
||||
export const _1000n = /*#__PURE__*/ BigInt(1_000);
|
||||
|
||||
/**
|
||||
* @name _1Mn
|
||||
* @summary BigInt constant for 1,000,000 (million).
|
||||
*/
|
||||
export const _1Mn = /*#__PURE__*/ BigInt(1_000_000);
|
||||
|
||||
/**
|
||||
* @name _1Bn
|
||||
* @summary BigInt constant for 1,000,000,000 (billion).
|
||||
*/
|
||||
export const _1Bn = /*#__PURE__*/ BigInt(1_000_000_000);
|
||||
|
||||
/**
|
||||
* @name _1Qn
|
||||
* @summary BigInt constant for 1,000,000,000,000,000,000 (quitillion).
|
||||
*/
|
||||
export const _1Qn = _1Bn * _1Bn;
|
||||
|
||||
/**
|
||||
* @name _2pow53n
|
||||
* @summary BigInt constant for MAX_SAFE_INTEGER
|
||||
*/
|
||||
export const _2pow53n = /*#__PURE__*/ BigInt(Number.MAX_SAFE_INTEGER);
|
||||
|
||||
/**
|
||||
* @name _sqrt2pow53n
|
||||
* @summary BigInt constant for Math.sqrt(MAX_SAFE_INTEGER)
|
||||
*/
|
||||
export const _sqrt2pow53n = /*#__PURE__*/ BigInt(94906265);
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/** @internal */
|
||||
export function createCmp <T> (cmp: (a: T, b: T) => boolean): (...items: T[]) => T {
|
||||
return (...items: T[]): T => {
|
||||
const count = items.length;
|
||||
|
||||
if (count === 0) {
|
||||
throw new Error('Must provide one or more arguments');
|
||||
}
|
||||
|
||||
let result = items[0];
|
||||
|
||||
for (let i = 1; i < count; i++) {
|
||||
if (cmp(items[i], result)) {
|
||||
result = items[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @summary Utility methods to convert to and from `bigint` objects
|
||||
*/
|
||||
|
||||
// all named
|
||||
export { nMax, nMin } from './min.js';
|
||||
export { nSqrt } from './sqrt.js';
|
||||
export { nToBigInt } from './toBigInt.js';
|
||||
export { nToHex } from './toHex.js';
|
||||
export { nToU8a } from './toU8a.js';
|
||||
|
||||
// all starred
|
||||
export * from './consts.js';
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { nMax } from './index.js';
|
||||
|
||||
describe('nMax', (): void => {
|
||||
it('finds maximum (sorted)', (): void => {
|
||||
expect(
|
||||
nMax(1n, 2n, 3n)
|
||||
).toEqual(3n);
|
||||
});
|
||||
|
||||
it('finds maximum (unsorted)', (): void => {
|
||||
expect(
|
||||
nMax(2n, 3n, 1n)
|
||||
).toEqual(3n);
|
||||
});
|
||||
|
||||
it('returns a single item', (): void => {
|
||||
expect(
|
||||
nMax(1n)
|
||||
).toEqual(1n);
|
||||
});
|
||||
|
||||
it('fails when no items are available', (): void => {
|
||||
expect(
|
||||
() => nMax()
|
||||
).toThrow(/Must provide one or more arguments/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { nMin } from './index.js';
|
||||
|
||||
describe('nMin', (): void => {
|
||||
it('finds BN minimum', (): void => {
|
||||
expect(
|
||||
nMin(2n, 1n, 3n)
|
||||
).toEqual(1n);
|
||||
});
|
||||
|
||||
it('returns a single item', (): void => {
|
||||
expect(
|
||||
nMin(1n)
|
||||
).toEqual(1n);
|
||||
});
|
||||
|
||||
it('fails when no items are available', (): void => {
|
||||
expect(
|
||||
() => nMin()
|
||||
).toThrow(/Must provide one or more arguments/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { createCmp } from './helpers.js';
|
||||
|
||||
/**
|
||||
* @name nMax
|
||||
* @summary Finds and returns the highest value in an array of bigint.
|
||||
*/
|
||||
export const nMax = /*#__PURE__*/ createCmp<bigint>((a, b) => a > b);
|
||||
|
||||
/**
|
||||
* @name nMin
|
||||
* @summary Finds and returns the lowest value in an array of bigint.
|
||||
*/
|
||||
export const nMin = /*#__PURE__*/ createCmp<bigint>((a, b) => a < b);
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { BN } from '../bn/index.js';
|
||||
import { _sqrt2pow53n, nSqrt } from './index.js';
|
||||
|
||||
// eslint-disable-next-line jest/no-export
|
||||
export const TESTS: [value: string | number | BN | bigint, expected: string | number][] = [
|
||||
[0, 0],
|
||||
[1, 1],
|
||||
[4, 2],
|
||||
[256 * 256, 256],
|
||||
[Number.MAX_SAFE_INTEGER, 94906265],
|
||||
[new BN(Number.MAX_SAFE_INTEGER).iaddn(256), 94906265],
|
||||
[12345678n * 12345679n, 12345678],
|
||||
// test cases below from https://github.com/Aisse-258/bigint-isqrt/blob/f5254b9750841959022461c1353437a07a08f501/test/sqrt-test.js
|
||||
[
|
||||
'54866395443885995655625',
|
||||
'234235768925'
|
||||
],
|
||||
[
|
||||
'82120471531550314555681345949499512621827274120673745141541602816614526075010755373654280259022317599142038423759320355177481886719814621305828811322920076213800348341464996337890625',
|
||||
'9062034624274524065844376014975805577107171799890766992670739972241112960081909332275390625'
|
||||
],
|
||||
[
|
||||
'2068290204957779940494571454815902632050207752627007664813877111611972735314294907791993008455381265730624848057015476004143868408297250386512042711005577373327434315310235583583151773358353534031397385969275475421659036494173765903706963544628565027679444680637920371210258368358706248446698622704138943704561842220533563976838506992543814273403403362954987589637283890281226038798503294069403370486523697009383229578343444337394508531937972775639593763364402389410142290440075517598007930025487176173228232153685095699851764630884360483051570513804913157899314303269924424908606245851172031453725690942288677622973423417902662711606364188470325930406288190754356367655606430407552275285889968860367977288564364154999338284288438279464648663835830140826171534470717958048887869721791862539529444820484648908879364324577951008050851801647916673254009490596379549192296404841454780884358889449367135382779230574781333008561195244620152128505091883408484094800737142399855337799331037607433934520934135669364954550499058123797911762228642423567268540444910163468496805547450495819213412636594319148417857753712526066272727725420886898680432958409892237191284583904066400736417812258327303739879128916456683685232170600928808798462776174857486589598776587550843543720784829419478239563366491666155716208452087857080883906200829769222868347286071365156305396955420561718593130305703074908891135467193627721682044757187980688062367562451981939375400543212890625',
|
||||
'45478458691536369397924851088318871329614555665525810477032568598360481742967756704524034986984134271202405190906906195123751965012732122548540879928668891416468847696038707003466943811913626146981446753434347552196868717612248915137033581428310504778371173112125038060138252719589122657221314190626986951627417768579641354255445988772645883238873672997347648890137913979299598785628581961589739331483895853398160785485300758458294999124984266041998145956341386549681176624661615465976970855592467853824812945820721933491609237311130118125761849911376495970746282571994312288380577879004620824004760109618074670810797544446334767103935754106103208789391060008291027361311679876439695844637933763721093782805837690830230712890625'
|
||||
]
|
||||
];
|
||||
|
||||
describe('nSqrt', (): void => {
|
||||
it('fails on < 0 roots', (): void => {
|
||||
expect(
|
||||
() => nSqrt(-1n)
|
||||
).toThrow(/negative numbers is not supported/);
|
||||
});
|
||||
|
||||
it('has the correct constant for sqrt(Number.MAX_SAFE_INTEGER)', (): void => {
|
||||
expect(
|
||||
BigInt(
|
||||
~~Math.sqrt(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
) === _sqrt2pow53n
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
describe('conversion tests', (): void => {
|
||||
TESTS.forEach(([value, expected], i): void => {
|
||||
it(`#${i}: calcs ${expected}`, (): void => {
|
||||
expect(
|
||||
nSqrt(value) === BigInt(expected)
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '../bn/index.js';
|
||||
import type { ToBigInt, ToBn } from '../types.js';
|
||||
|
||||
import { BigInt } from '@pezkuwi/x-bigint';
|
||||
|
||||
import { _0n, _1n, _2pow53n, _sqrt2pow53n } from './consts.js';
|
||||
import { nToBigInt } from './toBigInt.js';
|
||||
|
||||
/**
|
||||
* @name nSqrt
|
||||
* @summary Calculates the integer square root of a bigint
|
||||
*/
|
||||
export function nSqrt <ExtToBn extends ToBn | ToBigInt> (value: ExtToBn | BN | bigint | string | number | null): bigint {
|
||||
const n = nToBigInt(value);
|
||||
|
||||
if (n < _0n) {
|
||||
throw new Error('square root of negative numbers is not supported');
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/53683995/javascript-big-integer-square-root/
|
||||
// shortcut <= 2^53 - 1 to use the JS utils
|
||||
if (n <= _2pow53n) {
|
||||
// ~~ is more performant that Math.floor
|
||||
return BigInt(~~Math.sqrt(Number(n)));
|
||||
}
|
||||
|
||||
// Use sqrt(MAX_SAFE_INTEGER) as starting point. since we already know the
|
||||
// output will be larger than this, we expect this to be a safe start
|
||||
let x0 = _sqrt2pow53n;
|
||||
|
||||
while (true) {
|
||||
const x1 = ((n / x0) + x0) >> _1n;
|
||||
|
||||
if (x0 === x1 || (x0 === (x1 - _1n))) {
|
||||
return x0;
|
||||
}
|
||||
|
||||
x0 = x1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { BN } from '../bn/index.js';
|
||||
import { nToBigInt } from './index.js';
|
||||
|
||||
describe('nToBigInt', (): void => {
|
||||
it('converts null values to 0x00', (): void => {
|
||||
expect(
|
||||
nToBigInt(null)
|
||||
).toEqual(0n);
|
||||
});
|
||||
|
||||
it('converts 0x values to 0x00', (): void => {
|
||||
expect(
|
||||
nToBigInt('0x')
|
||||
).toEqual(0n);
|
||||
});
|
||||
|
||||
it('converts BN values to bigint', (): void => {
|
||||
expect(
|
||||
nToBigInt(new BN(128))
|
||||
).toEqual(128n);
|
||||
});
|
||||
|
||||
it('converts BigInt values to bigint', (): void => {
|
||||
expect(
|
||||
nToBigInt(128821n)
|
||||
).toEqual(128821n);
|
||||
});
|
||||
|
||||
it('converts number values to bigint', (): void => {
|
||||
expect(
|
||||
nToBigInt(128)
|
||||
).toEqual(128n);
|
||||
});
|
||||
|
||||
it('converts string to bigint', (): void => {
|
||||
expect(
|
||||
nToBigInt('123')
|
||||
).toEqual(123n);
|
||||
});
|
||||
|
||||
it('converts hex to bigint', (): void => {
|
||||
expect(
|
||||
nToBigInt('0x0123')
|
||||
).toEqual(0x123n);
|
||||
});
|
||||
|
||||
it('converts Compact to bigint (via toBn)', (): void => {
|
||||
expect(
|
||||
nToBigInt({
|
||||
something: 'test',
|
||||
toBn: () => new BN(1234)
|
||||
})
|
||||
).toEqual(1234n);
|
||||
});
|
||||
|
||||
it('converts Compact to bigint (via toBigInt)', (): void => {
|
||||
expect(
|
||||
nToBigInt({
|
||||
something: 'test',
|
||||
toBigInt: () => 1234n
|
||||
})
|
||||
).toEqual(1234n);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '../bn/bn.js';
|
||||
import type { ToBigInt, ToBn } from '../types.js';
|
||||
|
||||
import { BigInt } from '@pezkuwi/x-bigint';
|
||||
|
||||
import { hexToBigInt } from '../hex/toBigInt.js';
|
||||
import { isBn } from '../is/bn.js';
|
||||
import { isHex } from '../is/hex.js';
|
||||
import { isToBigInt } from '../is/toBigInt.js';
|
||||
import { isToBn } from '../is/toBn.js';
|
||||
|
||||
/**
|
||||
* @name nToBigInt
|
||||
* @summary Creates a bigInt value from a BN, bigint, string (base 10 or hex) or number input.
|
||||
*/
|
||||
export function nToBigInt <ExtToBn extends ToBigInt | ToBn> (value?: ExtToBn | BN | bigint | string | number | null): bigint {
|
||||
return typeof value === 'bigint'
|
||||
? value
|
||||
: !value
|
||||
? BigInt(0)
|
||||
: isHex(value)
|
||||
? hexToBigInt(value.toString())
|
||||
: isBn(value)
|
||||
? BigInt(value.toString())
|
||||
: isToBigInt(value)
|
||||
? value.toBigInt()
|
||||
: isToBn(value)
|
||||
? BigInt(value.toBn().toString())
|
||||
: BigInt(value);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { nToHex } from './index.js';
|
||||
|
||||
describe('nToHex', (): void => {
|
||||
it('converts null values to 0x00', (): void => {
|
||||
expect(
|
||||
nToHex(null)
|
||||
).toBe('0x00');
|
||||
});
|
||||
|
||||
it('converts null values to 0x00000000 (with bitLength)', (): void => {
|
||||
expect(
|
||||
nToHex(null, { bitLength: 32 })
|
||||
).toBe('0x00000000');
|
||||
});
|
||||
|
||||
it('converts values to a prefixed hex representation', (): void => {
|
||||
expect(
|
||||
nToHex(128)
|
||||
).toBe('0x80');
|
||||
});
|
||||
|
||||
it('converts values to a prefixed hex representation (bitLength)', (): void => {
|
||||
expect(
|
||||
nToHex(128n, { bitLength: 16 })
|
||||
).toBe('0x0080');
|
||||
});
|
||||
|
||||
it('converts values to a prefixed hex representation (bitLength + le)', (): void => {
|
||||
expect(
|
||||
nToHex(128, { bitLength: 16, isLe: true })
|
||||
).toBe('0x8000');
|
||||
});
|
||||
|
||||
it('converts values to a prefixed hex representation (LE)', (): void => {
|
||||
expect(
|
||||
nToHex(128, { bitLength: 16, isLe: true })
|
||||
).toBe('0x8000');
|
||||
});
|
||||
|
||||
it('handles negative numbers', (): void => {
|
||||
expect(
|
||||
nToHex(-1234, { isNegative: true })
|
||||
).toBe('0xfb2e');
|
||||
});
|
||||
|
||||
it('handles negative numbers (with bitLength)', (): void => {
|
||||
expect(
|
||||
nToHex(-1234, { bitLength: 32, isNegative: true })
|
||||
).toBe('0xfffffb2e');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '../bn/bn.js';
|
||||
import type { HexString, NumberOptions, ToBigInt, ToBn } from '../types.js';
|
||||
|
||||
import { u8aToHex } from '../u8a/index.js';
|
||||
import { nToU8a } from './toU8a.js';
|
||||
|
||||
/**
|
||||
* @name nToHex
|
||||
* @summary Creates a hex value from a bigint object.
|
||||
*/
|
||||
export function nToHex <ExtToBn extends ToBn | ToBigInt> (value?: ExtToBn | BN | bigint | number | null, { bitLength = -1, isLe = false, isNegative = false }: NumberOptions = {}): HexString {
|
||||
return u8aToHex(nToU8a(value || 0, { bitLength, isLe, isNegative }));
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { arrayRange } from '../array/index.js';
|
||||
import { perf } from '../test/index.js';
|
||||
import { nToU8a } from './index.js';
|
||||
|
||||
// eslint-disable-next-line jest/no-export
|
||||
export const TESTS: [isLe: boolean, isNegative: boolean, numarr: number[], strval: string][] = [
|
||||
// LE, positive numbers
|
||||
[true, false, [0x12], '18'],
|
||||
[true, false, [0x12, 0x34], '13330'],
|
||||
[true, false, [0x12, 0x34, 0x56], '5649426'],
|
||||
[true, false, [0x12, 0x34, 0x56, 0x78], '2018915346'],
|
||||
[true, false, [0x12, 0x34, 0x56, 0x78, 0x9a], '663443878930'],
|
||||
[true, false, [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc], '207371629900818'],
|
||||
[true, false, [0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78], '159954953172672629770948536149615195154'],
|
||||
// LE, positive numbers (w/ signed flag)
|
||||
[true, true, [12], '12'],
|
||||
[true, true, [210, 4], '1234'],
|
||||
[true, true, [64, 226, 1], '123456'],
|
||||
[true, true, [21, 205, 91, 7], '123456789'],
|
||||
[true, true, [203, 36, 104, 12, 8], '34567890123'],
|
||||
[true, true, [255, 159, 114, 78, 24, 9], '9999999999999'],
|
||||
// LE, negative numbers
|
||||
[true, true, [244], '-12'],
|
||||
[true, true, [46, 251], '-1234'],
|
||||
[true, true, [192, 29, 254], '-123456'],
|
||||
[true, true, [255, 255, 255, 255], '-1'],
|
||||
[true, true, [254, 255, 255, 255], '-2'],
|
||||
[true, true, [235, 50, 164, 248], '-123456789'],
|
||||
[true, true, [0, 0, 0, 128], '-2147483648'],
|
||||
[true, true, [0, 0, 0, 240], '-268435456'],
|
||||
[true, true, [65, 86, 129, 173, 254], '-5678999999'],
|
||||
[true, true, [1, 96, 141, 177, 231, 246], '-9999999999999'],
|
||||
[true, true, [1, 0, 156, 88, 76, 73, 31, 242], '-999999999999999999'],
|
||||
// BE
|
||||
[false, false, [0x12], '18'],
|
||||
[false, false, [0x12, 0x34], '4660'],
|
||||
[false, false, [0x12, 0x34, 0x56], '1193046'],
|
||||
[false, false, [0x12, 0x34, 0x56, 0x78], '305419896'],
|
||||
[false, true, [0xf2, 0x34, 0x56, 0x78], '-231451016'],
|
||||
[false, false, [0x12, 0x34, 0x56, 0x78, 0x9a], '78187493530'],
|
||||
[false, false, [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc], '20015998343868'],
|
||||
[false, false, [0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78], '24197857161011715162171839636988778104']
|
||||
];
|
||||
|
||||
const ptest = arrayRange(65536).map((v) => [v]);
|
||||
|
||||
describe('nToU8a', (): void => {
|
||||
describe('conversion tests', (): void => {
|
||||
TESTS.forEach(([isLe, isNegative, numarr, strval], i): void => {
|
||||
const bitLength = numarr.length * 8;
|
||||
|
||||
it(`#${i}: converts from ${strval} (bitLength=${bitLength}, isLe=${isLe}, isNegative=${isNegative})`, (): void => {
|
||||
expect(
|
||||
nToU8a(
|
||||
BigInt(strval),
|
||||
{ bitLength, isLe, isNegative }
|
||||
)
|
||||
).toEqual(new Uint8Array(numarr));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('converts null values to 0x00', (): void => {
|
||||
expect(
|
||||
nToU8a(null)
|
||||
).toEqual(new Uint8Array(1));
|
||||
});
|
||||
|
||||
it('converts null values to 0x00000000 (bitLength)', (): void => {
|
||||
expect(
|
||||
nToU8a(null, { bitLength: 32 })
|
||||
).toEqual(new Uint8Array([0, 0, 0, 0]));
|
||||
});
|
||||
|
||||
it('converts values to a prefixed hex representation', (): void => {
|
||||
expect(
|
||||
nToU8a(0x123456n, { isLe: false })
|
||||
).toEqual(new Uint8Array([0x12, 0x34, 0x56]));
|
||||
});
|
||||
|
||||
it('converts values to a prefixed hex representation (bitLength)', (): void => {
|
||||
expect(
|
||||
nToU8a(0x123456n, { bitLength: 32, isLe: false })
|
||||
).toEqual(new Uint8Array([0x00, 0x12, 0x34, 0x56]));
|
||||
});
|
||||
|
||||
it('converts using little endian (as set)', (): void => {
|
||||
expect(
|
||||
nToU8a(0x123456n, { bitLength: 32, isLe: true })
|
||||
).toEqual(new Uint8Array([0x56, 0x34, 0x12, 0x00]));
|
||||
});
|
||||
|
||||
perf('nToU8a', 250000, ptest, nToU8a);
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '../bn/bn.js';
|
||||
import type { NumberOptions, ToBigInt, ToBn } from '../types.js';
|
||||
|
||||
import { BigInt } from '@pezkuwi/x-bigint';
|
||||
|
||||
import { _0n, _1n } from './consts.js';
|
||||
import { nToBigInt } from './toBigInt.js';
|
||||
|
||||
const DIV = BigInt(256);
|
||||
const NEG_MASK = BigInt(0xff);
|
||||
|
||||
function toU8a (value: bigint, isLe: boolean, isNegative: boolean): Uint8Array {
|
||||
const arr: number[] = [];
|
||||
const withSigned = isNegative && (value < _0n);
|
||||
|
||||
if (withSigned) {
|
||||
value = (value + _1n) * -_1n;
|
||||
}
|
||||
|
||||
while (value !== _0n) {
|
||||
const mod = value % DIV;
|
||||
const val = Number(
|
||||
withSigned
|
||||
? mod ^ NEG_MASK
|
||||
: mod
|
||||
);
|
||||
|
||||
if (isLe) {
|
||||
arr.push(val);
|
||||
} else {
|
||||
arr.unshift(val);
|
||||
}
|
||||
|
||||
value = (value - mod) / DIV;
|
||||
}
|
||||
|
||||
return Uint8Array.from(arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name nToU8a
|
||||
* @summary Creates a Uint8Array object from a bigint.
|
||||
*/
|
||||
export function nToU8a <ExtToBn extends ToBn | ToBigInt> (value?: ExtToBn | BN | bigint | number | null, { bitLength = -1, isLe = true, isNegative = false }: NumberOptions = {}): Uint8Array {
|
||||
const valueBi = nToBigInt(value);
|
||||
|
||||
if (valueBi === _0n) {
|
||||
return bitLength === -1
|
||||
? new Uint8Array(1)
|
||||
: new Uint8Array(Math.ceil((bitLength || 0) / 8));
|
||||
}
|
||||
|
||||
const u8a = toU8a(valueBi, isLe, isNegative);
|
||||
|
||||
if (bitLength === -1) {
|
||||
return u8a;
|
||||
}
|
||||
|
||||
const byteLength = Math.ceil((bitLength || 0) / 8);
|
||||
const output = new Uint8Array(byteLength);
|
||||
|
||||
if (isNegative) {
|
||||
output.fill(0xff);
|
||||
}
|
||||
|
||||
output.set(u8a, isLe ? 0 : byteLength - u8a.length);
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import BN from 'bn.js';
|
||||
|
||||
export { BN };
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { BN, BN_HUNDRED, BN_ONE, BN_TEN, BN_THOUSAND, BN_ZERO } from './index.js';
|
||||
|
||||
describe('consts', (): void => {
|
||||
it('BN_ZERO equals 0', (): void => {
|
||||
expect(
|
||||
BN_ZERO
|
||||
).toEqual(new BN(0));
|
||||
});
|
||||
|
||||
it('BN_ONE equals 1', (): void => {
|
||||
expect(
|
||||
BN_ONE
|
||||
).toEqual(new BN(1));
|
||||
});
|
||||
|
||||
it('BN_TEN equals 10', (): void => {
|
||||
expect(
|
||||
BN_TEN
|
||||
).toEqual(new BN(10));
|
||||
});
|
||||
|
||||
it('BN_HUNDRED equals 100', (): void => {
|
||||
expect(
|
||||
BN_HUNDRED
|
||||
).toEqual(new BN(100));
|
||||
});
|
||||
|
||||
it('BN_THOUSAND equals 1000', (): void => {
|
||||
expect(
|
||||
BN_THOUSAND
|
||||
).toEqual(new BN(1000));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { BN } from './bn.js';
|
||||
|
||||
/**
|
||||
* @name BN_ZERO
|
||||
* @summary BN constant for 0.
|
||||
*/
|
||||
export const BN_ZERO: BN = /*#__PURE__*/ new BN(0);
|
||||
|
||||
/**
|
||||
* @name BN_ONE
|
||||
* @summary BN constant for 1.
|
||||
*/
|
||||
export const BN_ONE: BN = /*#__PURE__*/ new BN(1);
|
||||
|
||||
/**
|
||||
* @name BN_TWO
|
||||
* @summary BN constant for 2.
|
||||
*/
|
||||
export const BN_TWO: BN = /*#__PURE__*/ new BN(2);
|
||||
|
||||
/**
|
||||
* @name BN_THREE
|
||||
* @summary BN constant for 3.
|
||||
*/
|
||||
export const BN_THREE: BN = /*#__PURE__*/ new BN(3);
|
||||
|
||||
/**
|
||||
* @name BN_FOUR
|
||||
* @summary BN constant for 4.
|
||||
*/
|
||||
export const BN_FOUR: BN = /*#__PURE__*/ new BN(4);
|
||||
|
||||
/**
|
||||
* @name BN_FIVE
|
||||
* @summary BN constant for 5.
|
||||
*/
|
||||
export const BN_FIVE: BN = /*#__PURE__*/ new BN(5);
|
||||
|
||||
/**
|
||||
* @name BN_SIX
|
||||
* @summary BN constant for 6.
|
||||
*/
|
||||
export const BN_SIX: BN = /*#__PURE__*/ new BN(6);
|
||||
|
||||
/**
|
||||
* @name BN_SEVEN
|
||||
* @summary BN constant for 7.
|
||||
*/
|
||||
export const BN_SEVEN: BN = /*#__PURE__*/ new BN(7);
|
||||
|
||||
/**
|
||||
* @name BN_EIGHT
|
||||
* @summary BN constant for 8.
|
||||
*/
|
||||
export const BN_EIGHT: BN = /*#__PURE__*/ new BN(8);
|
||||
|
||||
/**
|
||||
* @name BN_NINE
|
||||
* @summary BN constant for 9.
|
||||
*/
|
||||
export const BN_NINE: BN = /*#__PURE__*/ new BN(9);
|
||||
|
||||
/**
|
||||
* @name BN_TEN
|
||||
* @summary BN constant for 10.
|
||||
*/
|
||||
export const BN_TEN: BN = /*#__PURE__*/ new BN(10);
|
||||
|
||||
/**
|
||||
* @name BN_HUNDRED
|
||||
* @summary BN constant for 100.
|
||||
*/
|
||||
export const BN_HUNDRED: BN = /*#__PURE__*/ new BN(100);
|
||||
|
||||
/**
|
||||
* @name BN_THOUSAND
|
||||
* @summary BN constant for 1,000.
|
||||
*/
|
||||
export const BN_THOUSAND: BN = /*#__PURE__*/ new BN(1_000);
|
||||
|
||||
/**
|
||||
* @name BN_MILLION
|
||||
* @summary BN constant for 1,000,000.
|
||||
*/
|
||||
export const BN_MILLION: BN = /*#__PURE__*/ new BN(1_000_000);
|
||||
|
||||
/**
|
||||
* @name BN_BILLION
|
||||
* @summary BN constant for 1,000,000,000.
|
||||
*/
|
||||
export const BN_BILLION: BN = /*#__PURE__*/ new BN(1_000_000_000);
|
||||
|
||||
/**
|
||||
* @name BN_QUINTILL
|
||||
* @summary BN constant for 1,000,000,000,000,000,000.
|
||||
*/
|
||||
export const BN_QUINTILL: BN = BN_BILLION.mul(BN_BILLION);
|
||||
|
||||
/**
|
||||
* @name BN_MAX_INTEGER
|
||||
* @summary BN constant for MAX_SAFE_INTEGER
|
||||
*/
|
||||
export const BN_MAX_INTEGER: BN = /*#__PURE__*/ new BN(Number.MAX_SAFE_INTEGER);
|
||||
|
||||
/**
|
||||
* @name BN_SQRT_MAX_INTEGER
|
||||
* @summary BN constant for Math.sqrt(MAX_SAFE_INTEGER)
|
||||
*/
|
||||
export const BN_SQRT_MAX_INTEGER: BN = /*#__PURE__*/ new BN(94906265);
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { isFunction } from '../is/function.js';
|
||||
import { bnFromHex } from './index.js';
|
||||
|
||||
describe('bnFromHex', (): void => {
|
||||
it('exists as a function', (): void => {
|
||||
expect(
|
||||
isFunction(bnFromHex)
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { hexToBn as bnFromHex } from '../hex/toBn.js';
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @summary Utility methods to convert to and from `BN` objects
|
||||
*/
|
||||
|
||||
// all named
|
||||
export { BN } from './bn.js';
|
||||
export { bnFromHex } from './fromHex.js';
|
||||
export { bnMax, bnMin } from './min.js';
|
||||
export { bnSqrt } from './sqrt.js';
|
||||
export { bnToBn } from './toBn.js';
|
||||
export { bnToHex } from './toHex.js';
|
||||
export { bnToU8a } from './toU8a.js';
|
||||
|
||||
// all starred
|
||||
export * from './consts.js';
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { BN, bnMax } from './index.js';
|
||||
|
||||
describe('bnMax', (): void => {
|
||||
it('finds BN maximum (sorted)', (): void => {
|
||||
expect(
|
||||
bnMax(new BN(1), new BN(2), new BN(3))
|
||||
).toEqual(new BN(3));
|
||||
});
|
||||
|
||||
it('finds BN maximum (unsorted)', (): void => {
|
||||
expect(
|
||||
bnMax(new BN(2), new BN(3), new BN(1))
|
||||
).toEqual(new BN(3));
|
||||
});
|
||||
|
||||
it('returns a single item', (): void => {
|
||||
expect(
|
||||
bnMax(new BN(1))
|
||||
).toEqual(new BN(1));
|
||||
});
|
||||
|
||||
it('fails when no items are available', (): void => {
|
||||
expect(
|
||||
() => bnMax()
|
||||
).toThrow(/Must provide one or more arguments/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { BN, bnMin } from './index.js';
|
||||
|
||||
describe('bnMin', (): void => {
|
||||
it('finds BN minimum', (): void => {
|
||||
expect(
|
||||
bnMin(new BN(2), new BN(1), new BN(3))
|
||||
).toEqual(new BN(1));
|
||||
});
|
||||
|
||||
it('returns a single item', (): void => {
|
||||
expect(
|
||||
bnMin(new BN(1))
|
||||
).toEqual(new BN(1));
|
||||
});
|
||||
|
||||
it('fails when no items are available', (): void => {
|
||||
expect(
|
||||
() => bnMin()
|
||||
).toThrow(/Must provide one or more arguments/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from './bn.js';
|
||||
|
||||
import { createCmp } from '../bi/helpers.js';
|
||||
|
||||
/**
|
||||
* @name bnMax
|
||||
* @summary Finds and returns the highest value in an array of BNs.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import BN from 'bn.js';
|
||||
* import { bnMax } from '@pezkuwi/util';
|
||||
*
|
||||
* bnMax([new BN(1), new BN(3), new BN(2)]).toString(); // => '3'
|
||||
* ```
|
||||
*/
|
||||
export const bnMax = /*#__PURE__*/ createCmp<BN>((a, b) => a.gt(b));
|
||||
|
||||
/**
|
||||
* @name bnMin
|
||||
* @summary Finds and returns the smallest value in an array of BNs.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import BN from 'bn.js';
|
||||
* import { bnMin } from '@pezkuwi/util';
|
||||
*
|
||||
* bnMin([new BN(1), new BN(3), new BN(2)]).toString(); // => '1'
|
||||
* ```
|
||||
*/
|
||||
export const bnMin = /*#__PURE__*/ createCmp<BN>((a, b) => a.lt(b));
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { TESTS } from '../bi/sqrt.spec.js';
|
||||
import { BN, BN_SQRT_MAX_INTEGER, bnSqrt } from './index.js';
|
||||
|
||||
describe('bnSqrt', (): void => {
|
||||
it('fails on < 0 roots', (): void => {
|
||||
expect(
|
||||
() => bnSqrt(new BN(-1))
|
||||
).toThrow(/negative numbers is not supported/);
|
||||
});
|
||||
|
||||
it('has the correct constant for sqrt(Number.MAX_SAFE_INTEGER)', (): void => {
|
||||
expect(
|
||||
BN_SQRT_MAX_INTEGER.eq(
|
||||
new BN(
|
||||
~~Math.sqrt(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
)
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
describe('conversion tests', (): void => {
|
||||
TESTS.forEach(([value, expected], i): void => {
|
||||
it(`#${i}: calcs ${expected}`, (): void => {
|
||||
expect(
|
||||
bnSqrt(value).eq(new BN(expected))
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ToBn } from '../types.js';
|
||||
|
||||
import { BN } from './bn.js';
|
||||
import { BN_MAX_INTEGER, BN_ONE, BN_SQRT_MAX_INTEGER } from './consts.js';
|
||||
import { bnToBn } from './toBn.js';
|
||||
|
||||
/**
|
||||
* @name bnSqrt
|
||||
* @summary Calculates the integer square root of a BN
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import BN from 'bn.js';
|
||||
* import { bnSqrt } from '@pezkuwi/util';
|
||||
*
|
||||
* bnSqrt(new BN(16)).toString(); // => '4'
|
||||
* ```
|
||||
*/
|
||||
export function bnSqrt <ExtToBn extends ToBn> (value: ExtToBn | BN | bigint | string | number | null): BN {
|
||||
const n = bnToBn(value);
|
||||
|
||||
if (n.isNeg()) {
|
||||
throw new Error('square root of negative numbers is not supported');
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/53683995/javascript-big-integer-square-root/
|
||||
// shortcut <= 2^53 - 1 to use the JS utils
|
||||
if (n.lte(BN_MAX_INTEGER)) {
|
||||
// ~~ More performant version of Math.floor
|
||||
return new BN(~~Math.sqrt(n.toNumber()));
|
||||
}
|
||||
|
||||
// Use sqrt(MAX_SAFE_INTEGER) as starting point. since we already know the
|
||||
// output will be larger than this, we expect this to be a safe start
|
||||
let x0 = BN_SQRT_MAX_INTEGER.clone();
|
||||
|
||||
while (true) {
|
||||
const x1 = n.div(x0).iadd(x0).ishrn(1);
|
||||
|
||||
if (x0.eq(x1) || x0.eq(x1.sub(BN_ONE))) {
|
||||
return x0;
|
||||
}
|
||||
|
||||
x0 = x1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { BN, bnToBn } from './index.js';
|
||||
|
||||
describe('bnToBn', (): void => {
|
||||
it('converts null values to 0x00', (): void => {
|
||||
expect(
|
||||
bnToBn(null).toNumber()
|
||||
).toEqual(0);
|
||||
});
|
||||
|
||||
it('converts BN values to BN', (): void => {
|
||||
expect(
|
||||
bnToBn(new BN(128)).toNumber()
|
||||
).toEqual(128);
|
||||
});
|
||||
|
||||
it('converts BigInt values to BN', (): void => {
|
||||
expect(
|
||||
bnToBn(128821n).toNumber()
|
||||
).toEqual(128821);
|
||||
});
|
||||
|
||||
it('converts number values to BN', (): void => {
|
||||
expect(
|
||||
bnToBn(128).toNumber()
|
||||
).toEqual(128);
|
||||
});
|
||||
|
||||
it('converts string to BN', (): void => {
|
||||
expect(
|
||||
bnToBn('123').toNumber()
|
||||
).toEqual(123);
|
||||
});
|
||||
|
||||
it('converts hex to BN', (): void => {
|
||||
expect(
|
||||
bnToBn('0x0123').toNumber()
|
||||
).toEqual(0x123);
|
||||
});
|
||||
|
||||
it('converts Compact to BN', (): void => {
|
||||
expect(
|
||||
bnToBn({
|
||||
something: 'test',
|
||||
toBn: (): BN => new BN(1234)
|
||||
}).toNumber()
|
||||
).toEqual(1234);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ToBigInt, ToBn } from '../types.js';
|
||||
|
||||
import { hexToBn } from '../hex/toBn.js';
|
||||
import { isBigInt } from '../is/bigInt.js';
|
||||
import { isHex } from '../is/hex.js';
|
||||
import { isToBigInt } from '../is/toBigInt.js';
|
||||
import { isToBn } from '../is/toBn.js';
|
||||
import { BN } from './bn.js';
|
||||
|
||||
/**
|
||||
* @name bnToBn
|
||||
* @summary Creates a BN value from a BN, bigint, string (base 10 or hex) or number input.
|
||||
* @description
|
||||
* `null` inputs returns a `0x0` result, BN values returns the value, numbers returns a BN representation.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import BN from 'bn.js';
|
||||
* import { bnToBn } from '@pezkuwi/util';
|
||||
*
|
||||
* bnToBn(0x1234); // => BN(0x1234)
|
||||
* bnToBn(new BN(0x1234)); // => BN(0x1234)
|
||||
* ```
|
||||
*/
|
||||
export function bnToBn <ExtToBn extends ToBigInt | ToBn> (value?: ExtToBn | BN | bigint | string | number | null): BN {
|
||||
return value
|
||||
? BN.isBN(value)
|
||||
? value
|
||||
: isHex(value)
|
||||
? hexToBn(value.toString())
|
||||
: isBigInt(value)
|
||||
? new BN(value.toString())
|
||||
: isToBn(value)
|
||||
? value.toBn()
|
||||
: isToBigInt(value)
|
||||
? new BN(value.toBigInt().toString())
|
||||
: new BN(value)
|
||||
: new BN(0);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { BN, bnToHex } from './index.js';
|
||||
|
||||
describe('bnToHex', (): void => {
|
||||
it('converts null values to 0x00', (): void => {
|
||||
expect(
|
||||
bnToHex(null)
|
||||
).toBe('0x00');
|
||||
});
|
||||
|
||||
it('converts null values to 0x00000000 (with bitLength)', (): void => {
|
||||
expect(
|
||||
bnToHex(null, { bitLength: 32 })
|
||||
).toBe('0x00000000');
|
||||
});
|
||||
|
||||
it('converts BN values to a prefixed hex representation', (): void => {
|
||||
expect(
|
||||
bnToHex(new BN(128))
|
||||
).toBe('0x80');
|
||||
});
|
||||
|
||||
it('converts BN values to a prefixed hex representation (bitLength)', (): void => {
|
||||
expect(
|
||||
bnToHex(new BN(128), { bitLength: 16 })
|
||||
).toBe('0x0080');
|
||||
});
|
||||
|
||||
it('converts BN values to a prefixed hex representation (LE)', (): void => {
|
||||
expect(
|
||||
bnToHex(new BN(128), { bitLength: 16, isLe: true })
|
||||
).toBe('0x8000');
|
||||
});
|
||||
|
||||
it('handles negative numbers', (): void => {
|
||||
expect(
|
||||
bnToHex(new BN(-1234), { isNegative: true })
|
||||
).toBe('0xfb2e');
|
||||
});
|
||||
|
||||
it('handles negative numbers (with bitLength)', (): void => {
|
||||
expect(
|
||||
bnToHex(new BN(-1234), { bitLength: 32, isNegative: true })
|
||||
).toBe('0xfffffb2e');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HexString, NumberOptions, ToBn } from '../types.js';
|
||||
import type { BN } from './bn.js';
|
||||
|
||||
import { u8aToHex } from '../u8a/index.js';
|
||||
import { bnToU8a } from './toU8a.js';
|
||||
|
||||
/**
|
||||
* @name bnToHex
|
||||
* @summary Creates a hex value from a BN.js bignumber object.
|
||||
* @description
|
||||
* `null` inputs returns a `0x` result, BN values return the actual value as a `0x` prefixed hex value. Anything that is not a BN object throws an error. With `bitLength` set, it fixes the number to the specified length.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import BN from 'bn.js';
|
||||
* import { bnToHex } from '@pezkuwi/util';
|
||||
*
|
||||
* bnToHex(new BN(0x123456)); // => '0x123456'
|
||||
* ```
|
||||
*/
|
||||
export function bnToHex <ExtToBn extends ToBn> (value?: ExtToBn | BN | bigint | number | null, { bitLength = -1, isLe = false, isNegative = false }: NumberOptions = {}): HexString {
|
||||
return u8aToHex(bnToU8a(value, { bitLength, isLe, isNegative }));
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { arrayRange } from '../array/index.js';
|
||||
import { TESTS } from '../bi/toU8a.spec.js';
|
||||
import { perf } from '../test/index.js';
|
||||
import { BN, bnToU8a } from './index.js';
|
||||
|
||||
const ptest = arrayRange(65536).map((v) => [v]);
|
||||
|
||||
describe('bnToU8a', (): void => {
|
||||
it('converts null values to 0x00', (): void => {
|
||||
expect(
|
||||
bnToU8a(null)
|
||||
).toEqual(new Uint8Array(1));
|
||||
});
|
||||
|
||||
it('converts null values to 0x00000000 (bitLength)', (): void => {
|
||||
expect(
|
||||
bnToU8a(null, { bitLength: 32 })
|
||||
).toEqual(new Uint8Array([0, 0, 0, 0]));
|
||||
});
|
||||
|
||||
it('converts BN values to a prefixed hex representation', (): void => {
|
||||
expect(
|
||||
bnToU8a(new BN(0x123456), { isLe: false })
|
||||
).toEqual(new Uint8Array([0x12, 0x34, 0x56]));
|
||||
});
|
||||
|
||||
it('converts BN values to a prefixed hex representation (bitLength)', (): void => {
|
||||
expect(
|
||||
bnToU8a(new BN(0x123456), { bitLength: 32, isLe: false })
|
||||
).toEqual(new Uint8Array([0x00, 0x12, 0x34, 0x56]));
|
||||
});
|
||||
|
||||
it('converts using little endian (as set)', (): void => {
|
||||
expect(
|
||||
bnToU8a(new BN(0x123456), { bitLength: 32, isLe: true })
|
||||
).toEqual(new Uint8Array([0x56, 0x34, 0x12, 0x00]));
|
||||
});
|
||||
|
||||
describe('conversion tests', (): void => {
|
||||
TESTS.forEach(([isLe, isNegative, numarr, strval], i): void => {
|
||||
const bitLength = numarr.length * 8;
|
||||
|
||||
it(`#${i}: converts from ${strval} (bitLength=${bitLength}, isLe=${isLe}, isNegative=${isNegative})`, (): void => {
|
||||
expect(
|
||||
bnToU8a(
|
||||
new BN(strval),
|
||||
{ bitLength, isLe, isNegative }
|
||||
)
|
||||
).toEqual(new Uint8Array(numarr));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
perf('bnToU8a', 250000, ptest, bnToU8a);
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { NumberOptions, ToBn } from '../types.js';
|
||||
import type { BN } from './bn.js';
|
||||
|
||||
import { bnToBn } from './toBn.js';
|
||||
|
||||
const DEFAULT_OPTS: NumberOptions = { bitLength: -1, isLe: true, isNegative: false };
|
||||
|
||||
/**
|
||||
* @name bnToU8a
|
||||
* @summary Creates a Uint8Array object from a BN.
|
||||
* @description
|
||||
* `null`/`undefined`/`NaN` inputs returns an empty `Uint8Array` result. `BN` input values return the actual bytes value converted to a `Uint8Array`. Optionally convert using little-endian format if `isLE` is set.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { bnToU8a } from '@pezkuwi/util';
|
||||
*
|
||||
* bnToU8a(new BN(0x1234)); // => [0x12, 0x34]
|
||||
* ```
|
||||
*/
|
||||
export function bnToU8a <ExtToBn extends ToBn> (value?: ExtToBn | BN | bigint | number | null, { bitLength = -1, isLe = true, isNegative = false } = DEFAULT_OPTS): Uint8Array {
|
||||
const valueBn = bnToBn(value);
|
||||
const byteLength = bitLength === -1
|
||||
? Math.ceil(valueBn.bitLength() / 8)
|
||||
: Math.ceil((bitLength || 0) / 8);
|
||||
|
||||
if (!value) {
|
||||
return bitLength === -1
|
||||
? new Uint8Array(1)
|
||||
: new Uint8Array(byteLength);
|
||||
}
|
||||
|
||||
const output = new Uint8Array(byteLength);
|
||||
const bn = isNegative
|
||||
? valueBn.toTwos(byteLength * 8)
|
||||
: valueBn;
|
||||
|
||||
output.set(bn.toArray(isLe ? 'le' : 'be', byteLength), 0);
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @summary Utility methods to convert to and from `Buffer` objects
|
||||
*/
|
||||
|
||||
export { bufferToU8a } from './toU8a.js';
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { bufferToU8a } from './index.js';
|
||||
|
||||
describe('bufferToU8a', (): void => {
|
||||
it('returns an empty buffer when null provided', (): void => {
|
||||
expect(
|
||||
bufferToU8a(null)
|
||||
).toEqual(new Uint8Array());
|
||||
});
|
||||
|
||||
it('returns a Uint8Buffer with the correct values', (): void => {
|
||||
expect(
|
||||
bufferToU8a(Buffer.from([128, 0, 10]))
|
||||
).toEqual(new Uint8Array([128, 0, 10]));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @name bufferToU8a
|
||||
* @summary Creates a Uint8Array value from a Buffer object.
|
||||
* @description
|
||||
* `null` inputs returns an empty result, `Buffer` values return the actual value as a `Uint8Array`. Anything that is not a `Buffer` object throws an error.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { bufferToU8a } from '@pezkuwi/util';
|
||||
*
|
||||
* bufferToU8a(Buffer.from([1, 2, 3]));
|
||||
* ```
|
||||
*/
|
||||
export function bufferToU8a (buffer?: Uint8Array | number[] | null): Uint8Array {
|
||||
return new Uint8Array(buffer || []);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @summary Utility methods for this package are split into groups
|
||||
*/
|
||||
|
||||
// all named
|
||||
export { packageInfo } from './packageInfo.js';
|
||||
|
||||
// all starred
|
||||
export * from './array/index.js';
|
||||
export * from './assert.js';
|
||||
export * from './bi/index.js';
|
||||
export * from './bn/index.js';
|
||||
export * from './buffer/index.js';
|
||||
export * from './compact/index.js';
|
||||
export * from './detectPackage.js';
|
||||
export * from './extractTime.js';
|
||||
export * from './float/index.js';
|
||||
export * from './format/index.js';
|
||||
export * from './has.js';
|
||||
export * from './hex/index.js';
|
||||
export * from './is/index.js';
|
||||
export * from './lazy.js';
|
||||
export * from './logger.js';
|
||||
export * from './memoize.js';
|
||||
export * from './nextTick.js';
|
||||
export * from './noop.js';
|
||||
export * from './number/index.js';
|
||||
export * from './object/index.js';
|
||||
export * from './promisify.js';
|
||||
export * from './string/index.js';
|
||||
export * from './stringify.js';
|
||||
export * from './u8a/index.js';
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { compactAddLength } from './index.js';
|
||||
|
||||
describe('compactAddLength', (): void => {
|
||||
it('correctly adds the length prefix', (): void => {
|
||||
expect(
|
||||
compactAddLength(Uint8Array.from([12, 13]))
|
||||
).toEqual(Uint8Array.from([2 << 2, 12, 13]));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { u8aConcatStrict } from '../u8a/index.js';
|
||||
import { compactToU8a } from './toU8a.js';
|
||||
|
||||
/**
|
||||
* @name compactAddLength
|
||||
* @description Adds a length prefix to the input value
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { compactAddLength } from '@pezkuwi/util';
|
||||
*
|
||||
* console.log(compactAddLength(new Uint8Array([0xde, 0xad, 0xbe, 0xef]))); // Uint8Array([4 << 2, 0xde, 0xad, 0xbe, 0xef])
|
||||
* ```
|
||||
*/
|
||||
export function compactAddLength (input: Uint8Array): Uint8Array {
|
||||
return u8aConcatStrict([
|
||||
compactToU8a(input.length),
|
||||
input
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BitLength } from './types.js';
|
||||
|
||||
/** @internal */
|
||||
export const DEFAULT_BITLENGTH: BitLength = 32;
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { BN } from '../bn/bn.js';
|
||||
import { hexToU8a } from '../hex/toU8a.js';
|
||||
import { perf } from '../test/index.js';
|
||||
import { compactFromU8a, compactFromU8aLim } from './index.js';
|
||||
|
||||
describe('compactFromU8a', (): void => {
|
||||
it('decoded u8 value', (): void => {
|
||||
expect(
|
||||
compactFromU8a(new Uint8Array([0b11111100]))
|
||||
).toEqual([1, new BN(63)]);
|
||||
});
|
||||
|
||||
it('decodes from same u16 encoded value', (): void => {
|
||||
expect(
|
||||
compactFromU8a(new Uint8Array([0b11111101, 0b00000111])).toString()
|
||||
).toEqual(
|
||||
[2, new BN(511)].toString()
|
||||
);
|
||||
});
|
||||
|
||||
it('decodes from same u32 encoded value (short)', (): void => {
|
||||
expect(
|
||||
compactFromU8a(new Uint8Array([254, 255, 3, 0])).toString()
|
||||
).toEqual(
|
||||
[4, new BN(0xffff)].toString()
|
||||
);
|
||||
});
|
||||
|
||||
it('decodes from same u32 encoded value (short, max)', (): void => {
|
||||
expect(
|
||||
compactFromU8a(new Uint8Array([254, 255, 255, 255])).toString()
|
||||
).toEqual(
|
||||
[4, new BN(1073741823)].toString()
|
||||
);
|
||||
});
|
||||
|
||||
it('decodes from same u32 encoded value (full)', (): void => {
|
||||
expect(
|
||||
compactFromU8a(new Uint8Array([3, 249, 255, 255, 255]))
|
||||
).toEqual([5, new BN(0xfffffff9)]);
|
||||
});
|
||||
|
||||
it('decodes from same u32 as u64 encoded value (full, default)', (): void => {
|
||||
expect(
|
||||
compactFromU8a(new Uint8Array([3 + ((4 - 4) << 2), 249, 255, 255, 255]))
|
||||
).toEqual([5, new BN(0xfffffff9)]);
|
||||
});
|
||||
|
||||
it('decodes an actual value', (): void => {
|
||||
expect(
|
||||
compactFromU8a(
|
||||
hexToU8a('0x0b00407a10f35a')
|
||||
)
|
||||
).toEqual([7, new BN('5af3107a4000', 16)]);
|
||||
});
|
||||
|
||||
it('decodes an actual value (Buffer)', (): void => {
|
||||
expect(
|
||||
compactFromU8a(
|
||||
Buffer.from('0b00407a10f35a', 'hex')
|
||||
)
|
||||
).toEqual([7, new BN('5af3107a4000', 16)]);
|
||||
});
|
||||
|
||||
it('decodes an actual value (100000000)', (): void => {
|
||||
expect(
|
||||
compactFromU8a(
|
||||
hexToU8a('0x0284d717')
|
||||
)[1].toString()
|
||||
).toEqual('100000000');
|
||||
});
|
||||
|
||||
perf('compactFromU8a (u8)', 1_000_000, [[new Uint8Array([63 << 2])]], compactFromU8a);
|
||||
perf('compactFromU8a (u16)', 1_000_000, [[new Uint8Array([0b11111101, 0b00000111])]], compactFromU8a);
|
||||
perf('compactFromU8a (u32)', 1_000_000, [[new Uint8Array([254, 255, 3, 0])]], compactFromU8a);
|
||||
perf('compactFromU8aLim (u32)', 1_000_000, [[new Uint8Array([254, 255, 3, 0])]], compactFromU8aLim);
|
||||
perf('compactFromU8a (u48)', 1_000_000, [[hexToU8a('0x0b00407a10f35a')]], compactFromU8a);
|
||||
perf('compactFromU8a (u96)', 250_000, [[new Uint8Array([23, 52, 0x40, 0x7a, 0x10, 0xf3, 0x5a, 0, 0, 18])]], compactFromU8a);
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { U8aLike } from '../types.js';
|
||||
|
||||
import { BN } from '../bn/index.js';
|
||||
import { u8aToBn, u8aToU8a } from '../u8a/index.js';
|
||||
|
||||
/**
|
||||
* @name compactFromU8a
|
||||
* @description Retrives the offset and encoded length from a compact-prefixed value
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { compactFromU8a } from '@pezkuwi/util';
|
||||
*
|
||||
* const [offset, length] = compactFromU8a(new Uint8Array([254, 255, 3, 0]));
|
||||
*
|
||||
* console.log('value offset=', offset, 'length=', length); // 4, 0xffff
|
||||
* ```
|
||||
*/
|
||||
export function compactFromU8a (input: U8aLike): [number, BN] {
|
||||
const u8a = u8aToU8a(input);
|
||||
|
||||
// The u8a is manually converted here for 1, 2 & 4 lengths, it is 2x faster
|
||||
// than doing an additional call to u8aToBn (as with variable length)
|
||||
switch (u8a[0] & 0b11) {
|
||||
case 0b00:
|
||||
return [1, new BN(u8a[0] >>> 2)];
|
||||
|
||||
case 0b01:
|
||||
return [2, new BN((u8a[0] + (u8a[1] << 8)) >>> 2)];
|
||||
|
||||
case 0b10:
|
||||
// for the 3rd byte, we don't << 24 - since JS converts all bitwise operators to
|
||||
// 32-bit, in the case where the top-most bit is set this yields a negative value
|
||||
return [4, new BN((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) >>> 2)];
|
||||
|
||||
// 0b11
|
||||
default: {
|
||||
// add 5 to shifted (4 for base length, 1 for this byte)
|
||||
const offset = (u8a[0] >>> 2) + 5;
|
||||
|
||||
// we unroll the loop
|
||||
switch (offset) {
|
||||
// there still could be 4 bytes data, similar to 0b10 above (with offsets)
|
||||
case 5:
|
||||
// for the 3rd byte, we don't << 24 - since JS converts all bitwise operators to
|
||||
// 32-bit, in the case where the top-most bit is set this yields a negative value
|
||||
return [5, new BN(u8a[1] + (u8a[2] << 8) + (u8a[3] << 16) + (u8a[4] * 0x1_00_00_00))];
|
||||
|
||||
case 6:
|
||||
return [6, new BN(u8a[1] + (u8a[2] << 8) + (u8a[3] << 16) + ((u8a[4] + (u8a[5] << 8)) * 0x1_00_00_00))];
|
||||
|
||||
// 6 bytes data is the maximum, 48 bits (56 would overflow)
|
||||
case 7:
|
||||
return [7, new BN(u8a[1] + (u8a[2] << 8) + (u8a[3] << 16) + ((u8a[4] + (u8a[5] << 8) + (u8a[6] << 16)) * 0x1_00_00_00))];
|
||||
|
||||
// for anything else, use the non-unrolled version
|
||||
default:
|
||||
return [offset, u8aToBn(u8a.subarray(1, offset))];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name compactFromU8aLim
|
||||
* @description A limited version of [[compactFromU8a]], accepting only Uint8Array inputs for values <= 48 bits
|
||||
*/
|
||||
export function compactFromU8aLim (u8a: Uint8Array): [number, number] {
|
||||
// The u8a is manually converted here for 1, 2 & 4 lengths, it is 2x faster
|
||||
// than doing an additional call to u8aToBn (as with variable length)
|
||||
switch (u8a[0] & 0b11) {
|
||||
case 0b00:
|
||||
return [1, u8a[0] >>> 2];
|
||||
|
||||
case 0b01:
|
||||
return [2, (u8a[0] + (u8a[1] << 8)) >>> 2];
|
||||
|
||||
case 0b10:
|
||||
// for the 3rd byte, we don't << 24 - since JS converts all bitwise operators to
|
||||
// 32-bit, in the case where the top-most bit is set this yields a negative value
|
||||
return [4, (u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) >>> 2];
|
||||
|
||||
// 0b11
|
||||
default: {
|
||||
// add 5 to shifted (4 for base length, 1 for this byte)
|
||||
// we unroll the loop
|
||||
switch ((u8a[0] >>> 2) + 5) {
|
||||
// there still could be 4 bytes data, similar to 0b10 above (with offsets)
|
||||
case 5:
|
||||
return [5, u8a[1] + (u8a[2] << 8) + (u8a[3] << 16) + (u8a[4] * 0x1_00_00_00)];
|
||||
|
||||
case 6:
|
||||
return [6, u8a[1] + (u8a[2] << 8) + (u8a[3] << 16) + ((u8a[4] + (u8a[5] << 8)) * 0x1_00_00_00)];
|
||||
|
||||
// 6 bytes data is the maximum, 48 bits (56 would overflow)
|
||||
case 7:
|
||||
return [7, u8a[1] + (u8a[2] << 8) + (u8a[3] << 16) + ((u8a[4] + (u8a[5] << 8) + (u8a[6] << 16)) * 0x1_00_00_00)];
|
||||
|
||||
// for anything else, we are above the actual MAX_SAFE_INTEGER - bail out
|
||||
default:
|
||||
throw new Error('Compact input is > Number.MAX_SAFE_INTEGER');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Encoding and decoding of parity-codec compact numbers. The codec is created
|
||||
* to take up the least amount of space for a specific number. It performs the
|
||||
* same function as Length, however differs in that it uses a variable number of
|
||||
* bytes to do the actual encoding. From the Rust implementation for compact
|
||||
* encoding:
|
||||
*
|
||||
* 0b00 00 00 00 / 00 00 00 00 / 00 00 00 00 / 00 00 00 00
|
||||
* (0 ... 2**6 - 1) (u8)
|
||||
* xx xx xx 00
|
||||
* (2**6 ... 2**14 - 1) (u8, u16) low LH high
|
||||
* yL yL yL 01 / yH yH yH yL
|
||||
* (2**14 ... 2**30 - 1) (u16, u32) low LMMH high
|
||||
* zL zL zL 10 / zM zM zM zL / zM zM zM zM / zH zH zH zM
|
||||
* (2**30 ... 2**536 - 1) (u32, u64, u128, U256, U512, U520) straight LE-encoded
|
||||
* nn nn nn 11 [ / zz zz zz zz ]{4 + n}
|
||||
*
|
||||
* Note: we use *LOW BITS* of the LSB in LE encoding to encode the 2 bit key.
|
||||
*/
|
||||
|
||||
export { compactAddLength } from './addLength.js';
|
||||
export { compactFromU8a, compactFromU8aLim } from './fromU8a.js';
|
||||
export { compactStripLength } from './stripLength.js';
|
||||
export { compactToU8a } from './toU8a.js';
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { compactStripLength } from './index.js';
|
||||
|
||||
describe('compactStripLength', (): void => {
|
||||
it('correctly removes the length prefix', (): void => {
|
||||
expect(
|
||||
compactStripLength(Uint8Array.from([2 << 2, 12, 13]))
|
||||
).toEqual([
|
||||
3,
|
||||
Uint8Array.from([12, 13])
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { compactFromU8a } from './fromU8a.js';
|
||||
|
||||
/**
|
||||
* @name compactStripLength
|
||||
* @description Removes the length prefix, returning both the total length (including the value + compact encoding) and the decoded value with the correct length
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { compactStripLength } from '@pezkuwi/util';
|
||||
*
|
||||
* console.log(compactStripLength(new Uint8Array([2 << 2, 0xde, 0xad]))); // [2, Uint8Array[0xde, 0xad]]
|
||||
* ```
|
||||
*/
|
||||
export function compactStripLength (input: Uint8Array): [number, Uint8Array] {
|
||||
const [offset, length] = compactFromU8a(input);
|
||||
const total = offset + length.toNumber();
|
||||
|
||||
return [
|
||||
total,
|
||||
input.subarray(offset, total)
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { BN } from '../bn/index.js';
|
||||
import { compactToU8a } from './index.js';
|
||||
|
||||
const TESTS: [output: string | Uint8Array, input: BN | number][] = [
|
||||
// Rust tests
|
||||
// Copied from https://github.com/paritytech/parity-codec/blob/master/src/codec.rs
|
||||
['00', new BN('0')],
|
||||
['fc', new BN('63')],
|
||||
['01 01', new BN('64')],
|
||||
['fd ff', new BN('16383')],
|
||||
['02 00 01 00', new BN('16384')],
|
||||
['fe ff ff ff', new BN('1073741823')],
|
||||
['03 00 00 00 40', new BN('1073741824')],
|
||||
['03 ff ff ff ff', new BN(`${1}${'0'.repeat(32)}`, 2).subn(1)],
|
||||
['07 00 00 00 00 01', new BN(`${1}${'0'.repeat(32)}`, 2)],
|
||||
['0b 00 00 00 00 00 01', new BN(`${1}${'0'.repeat(40)}`, 2)],
|
||||
['0f 00 00 00 00 00 00 01', new BN(`${1}${'0'.repeat(48)}`, 2)],
|
||||
['0f ff ff ff ff ff ff ff', new BN(`${1}${'0'.repeat(56)}`, 2).subn(1)],
|
||||
['13 00 00 00 00 00 00 00 01', new BN(`${1}${'0'.repeat(56)}`, 2)],
|
||||
['13 ff ff ff ff ff ff ff ff', new BN(`${1}${'0'.repeat(64)}`, 2).subn(1)],
|
||||
// own tests
|
||||
[new Uint8Array([18 << 2]), 18],
|
||||
[new Uint8Array([0b11111100]), 63],
|
||||
[new Uint8Array([0xbd, 0x01]), 111],
|
||||
[new Uint8Array([0b11111101, 0b00000111]), 511],
|
||||
[new Uint8Array([253, 127]), 0x1fff],
|
||||
[new Uint8Array([254, 255, 3, 0]), 0xffff],
|
||||
[new Uint8Array([3 + ((4 - 4) << 2), 249, 255, 255, 255]), 0xfffffff9],
|
||||
[new Uint8Array([3 + ((6 - 4) << 2), 0x00, 0x40, 0x7a, 0x10, 0xf3, 0x5a]), new BN('00005af3107a4000', 16)],
|
||||
[new Uint8Array([23, 52, 0x40, 0x7a, 0x10, 0xf3, 0x5a, 0, 0, 18]), new BN('1200005af3107a4034', 16)]
|
||||
];
|
||||
|
||||
describe('encode', (): void => {
|
||||
it('does not modify the original', (): void => {
|
||||
const original = new BN(123456);
|
||||
|
||||
expect(compactToU8a(original)).toEqual(new Uint8Array([2, 137, 7, 0]));
|
||||
expect(original.toString()).toEqual('123456');
|
||||
});
|
||||
|
||||
describe('conversion tests', (): void => {
|
||||
TESTS.forEach(([output, input], i): void => {
|
||||
it(`#${i}: encodes ${input.toString()}`, (): void => {
|
||||
expect(
|
||||
compactToU8a(input)
|
||||
).toEqual(
|
||||
output instanceof Uint8Array
|
||||
? output
|
||||
: Uint8Array.from(output.split(' ').map((s) => parseInt(s, 16)))
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { BN, BN_ONE, BN_TWO, bnToBn, bnToU8a } from '../bn/index.js';
|
||||
import { u8aConcatStrict } from '../u8a/index.js';
|
||||
|
||||
const MAX_U8 = BN_TWO.pow(new BN(8 - 2)).isub(BN_ONE);
|
||||
const MAX_U16 = BN_TWO.pow(new BN(16 - 2)).isub(BN_ONE);
|
||||
const MAX_U32 = BN_TWO.pow(new BN(32 - 2)).isub(BN_ONE);
|
||||
const BL_16 = { bitLength: 16 };
|
||||
const BL_32 = { bitLength: 32 };
|
||||
|
||||
/**
|
||||
* @name compactToU8a
|
||||
* @description Encodes a number into a compact representation
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { compactToU8a } from '@pezkuwi/util';
|
||||
*
|
||||
* console.log(compactToU8a(511, 32)); // Uint8Array([0b11111101, 0b00000111])
|
||||
* ```
|
||||
*/
|
||||
export function compactToU8a (value: BN | bigint | number): Uint8Array {
|
||||
const bn = bnToBn(value);
|
||||
|
||||
if (bn.lte(MAX_U8)) {
|
||||
return new Uint8Array([bn.toNumber() << 2]);
|
||||
} else if (bn.lte(MAX_U16)) {
|
||||
return bnToU8a(bn.shln(2).iadd(BN_ONE), BL_16);
|
||||
} else if (bn.lte(MAX_U32)) {
|
||||
return bnToU8a(bn.shln(2).iadd(BN_TWO), BL_32);
|
||||
}
|
||||
|
||||
const u8a = bnToU8a(bn);
|
||||
let length = u8a.length;
|
||||
|
||||
// adjust to the minimum number of bytes
|
||||
while (u8a[length - 1] === 0) {
|
||||
length--;
|
||||
}
|
||||
|
||||
if (length < 4) {
|
||||
throw new Error('Invalid length, previous checks match anything less than 2^30');
|
||||
}
|
||||
|
||||
return u8aConcatStrict([
|
||||
// subtract 4 as minimum (also catered for in decoding)
|
||||
new Uint8Array([((length - 4) << 2) + 0b11]),
|
||||
u8a.subarray(0, length)
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export type BitLength = 8 | 16 | 32 | 64 | 128 | 256;
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { detectPackage, POLKADOTJS_DISABLE_ESM_CJS_WARNING_FLAG } from './detectPackage.js';
|
||||
|
||||
describe('detectPackage', (): void => {
|
||||
const PKG = '@polkadot/util';
|
||||
const VER1 = '9.8.0-beta.45';
|
||||
const VER2 = '9.7.1';
|
||||
const VER3 = '9.6.1';
|
||||
const PATH = '/Users/jaco/Projects/polkadot-js/api/node_modules/@polkadot/util';
|
||||
|
||||
const MISMATCH = `@polkadot/util has multiple versions, ensure that there is only one installed.
|
||||
Either remove and explicitly install matching versions or dedupe using your package manager.
|
||||
The following conflicting packages were found:
|
||||
\tesm ${VER1}\t<unknown>
|
||||
\tesm ${VER2} \tnode_modules/@polkadot/api/node_modules/@polkadot/util`;
|
||||
|
||||
it('should not log the first time', (): void => {
|
||||
const spy = jest.spyOn(console, 'warn');
|
||||
|
||||
detectPackage({ name: PKG, path: 'auto', type: 'esm', version: VER1 }, PATH);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('should log the second time', (): void => {
|
||||
const spy = jest.spyOn(console, 'warn');
|
||||
|
||||
detectPackage({ name: PKG, path: '/Users/jaco/Projects/polkadot-js/api/node_modules/@polkadot/api/node_modules/@polkadot/util', type: 'esm', version: VER2 });
|
||||
expect(spy).toHaveBeenCalledWith(MISMATCH);
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('should allow for function use', (): void => {
|
||||
const spy = jest.spyOn(console, 'warn');
|
||||
|
||||
detectPackage({ name: PKG, path: 'node_modules/@polkadot/util', type: 'cjs', version: VER3 }, () => PATH);
|
||||
expect(spy).toHaveBeenCalledWith(`${MISMATCH}
|
||||
\tcjs ${VER3} \tnode_modules/@polkadot/util`);
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('detectPackageDeps', (): void => {
|
||||
const DEP0 = { name: '@polkadot/keyring', path: 'auto', type: 'esm', version: '1.1.1' };
|
||||
const DEP1 = { name: '@polkadot/util', path: 'auto', type: 'esm', version: '1.1.2' };
|
||||
const DEP2 = { name: '@polkadot/util-crypto', path: 'auto', type: 'esm', version: '1.1.3' };
|
||||
const DEP3 = { name: '@polkadot/networks', path: 'auto', type: 'esm', version: '1.1.1' };
|
||||
|
||||
it('should not log when no mismatches are found', (): void => {
|
||||
const spy = jest.spyOn(console, 'warn');
|
||||
|
||||
detectPackage({ name: '@polkadot/one', path: 'auto', type: 'esm', version: '1.1.1' }, false, [DEP0, DEP3]);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('should log when mismatches are found', (): void => {
|
||||
const spy = jest.spyOn(console, 'warn');
|
||||
|
||||
detectPackage({ name: '@polkadot/two', path: 'auto', type: 'esm', version: '1.1.1' }, false, [DEP0, DEP1, DEP2, DEP3]);
|
||||
expect(spy).toHaveBeenCalledWith(`@polkadot/two requires direct dependencies exactly matching version 1.1.1.
|
||||
Either remove and explicitly install matching versions or dedupe using your package manager.
|
||||
The following conflicting packages were found:
|
||||
\t1.1.2\t@polkadot/util
|
||||
\t1.1.3\t@polkadot/util-crypto`);
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('detectPackageEsmCjsNoWarnings', (): void => {
|
||||
const PKG = '@polkadot/wasm-crypto';
|
||||
const VER1 = '9.8.0-beta.45';
|
||||
const PATH = '/Users/jaco/Projects/polkadot-js/api/node_modules/@polkadot/api/node_modules/@polkadot/wasm-crypto';
|
||||
|
||||
it('should not log when there are concurrent esm and cjs versions of the same package with the same version number and warnings are disabled', (): void => {
|
||||
const spy = jest.spyOn(console, 'warn');
|
||||
const pkgEsm = { name: PKG, path: PATH, type: 'esm', version: VER1 };
|
||||
const pkgCjs = { name: PKG, path: `${PATH}/cjs`, type: 'cjs', version: VER1 };
|
||||
|
||||
process.env[POLKADOTJS_DISABLE_ESM_CJS_WARNING_FLAG] = '1';
|
||||
detectPackage(pkgEsm, false, []);
|
||||
detectPackage(pkgCjs, false, []);
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('detectPackageEsmCjs', (): void => {
|
||||
const PKG = '@polkadot/wasm-crypto-wasm';
|
||||
const VER1 = '9.8.0-beta.45';
|
||||
const PATH = '/Users/jaco/Projects/polkadot-js/api/node_modules/@polkadot/api/node_modules/@polkadot/wasm-crypto-wasm';
|
||||
|
||||
it('should log when there are concurrent esm and cjs versions of the same package with the same version number and warnings are not disabled', (): void => {
|
||||
const spy = jest.spyOn(console, 'warn');
|
||||
const pkgEsm = { name: PKG, path: PATH, type: 'esm', version: VER1 };
|
||||
const pkgCjs = { name: PKG, path: `${PATH}/cjs`, type: 'cjs', version: VER1 };
|
||||
|
||||
process.env[POLKADOTJS_DISABLE_ESM_CJS_WARNING_FLAG] = undefined;
|
||||
detectPackage(pkgEsm, false, []);
|
||||
detectPackage(pkgCjs, false, []);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { xglobal } from '@pezkuwi/x-global';
|
||||
|
||||
import { isFunction } from './is/function.js';
|
||||
|
||||
type This = typeof globalThis;
|
||||
|
||||
interface VersionPath {
|
||||
path: string;
|
||||
type: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
interface PackageInfo extends VersionPath {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface PjsChecks extends This {
|
||||
__polkadotjs: Record<string, VersionPath[]>;
|
||||
}
|
||||
|
||||
type PjsGlobal = This & PjsChecks & Record<string, unknown>;
|
||||
type FnString = () => string | undefined;
|
||||
|
||||
const DEDUPE = 'Either remove and explicitly install matching versions or dedupe using your package manager.\nThe following conflicting packages were found:';
|
||||
|
||||
export const POLKADOTJS_DISABLE_ESM_CJS_WARNING_FLAG = 'POLKADOTJS_DISABLE_ESM_CJS_WARNING';
|
||||
|
||||
/** @internal */
|
||||
function getEntry (name: string): VersionPath[] {
|
||||
const _global = xglobal as PjsGlobal;
|
||||
|
||||
if (!_global.__polkadotjs) {
|
||||
_global.__polkadotjs = {};
|
||||
}
|
||||
|
||||
if (!_global.__polkadotjs[name]) {
|
||||
_global.__polkadotjs[name] = [];
|
||||
}
|
||||
|
||||
return _global.__polkadotjs[name];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function formatDisplay <T extends { version: string }> (all: T[], fmt: (version: string, data: T) => string[]): string {
|
||||
let max = 0;
|
||||
|
||||
for (let i = 0, count = all.length; i < count; i++) {
|
||||
max = Math.max(max, all[i].version.length);
|
||||
}
|
||||
|
||||
return all
|
||||
.map((d) => `\t${fmt(d.version.padEnd(max), d).join('\t')}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function formatInfo (version: string, { name }: PackageInfo): string[] {
|
||||
return [
|
||||
version,
|
||||
name
|
||||
];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function formatVersion (version: string, { path, type }: VersionPath): string[] {
|
||||
let extracted: string;
|
||||
|
||||
if (path && path.length >= 5) {
|
||||
const nmIndex = path.indexOf('node_modules');
|
||||
|
||||
extracted = nmIndex === -1
|
||||
? path
|
||||
: path.substring(nmIndex);
|
||||
} else {
|
||||
extracted = '<unknown>';
|
||||
}
|
||||
|
||||
return [
|
||||
`${`${type || ''}`.padStart(3)} ${version}`,
|
||||
extracted
|
||||
];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function getPath (infoPath?: string, pathOrFn?: FnString | string | false | null): string {
|
||||
if (infoPath) {
|
||||
return infoPath;
|
||||
} else if (isFunction(pathOrFn)) {
|
||||
try {
|
||||
return pathOrFn() || '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
return pathOrFn || '';
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function warn <T extends { version: string }> (pre: string, all: T[], fmt: (version: string, data: T) => string[]): void {
|
||||
console.warn(`${pre}\n${DEDUPE}\n${formatDisplay(all, fmt)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name detectPackage
|
||||
* @summary Checks that a specific package is only imported once
|
||||
* @description A `@polkadot/*` version detection utility, checking for one occurrence of a package in addition to checking for dependency versions.
|
||||
*/
|
||||
export function detectPackage ({ name, path, type, version }: PackageInfo, pathOrFn?: FnString | string | false | null, deps: PackageInfo[] = []): void {
|
||||
if (!name.startsWith('@polkadot')) {
|
||||
throw new Error(`Invalid package descriptor ${name}`);
|
||||
}
|
||||
|
||||
const entry = getEntry(name);
|
||||
|
||||
entry.push({ path: getPath(path, pathOrFn), type, version });
|
||||
|
||||
// if we have more than one entry at DIFFERENT version types then warn. If there is
|
||||
// more than one entry at the same version and ESM/CJS dual warnings are disabled,
|
||||
// then do not display warnings
|
||||
const entriesSameVersion = entry.every((e) => e.version === version);
|
||||
const esmCjsWarningDisabled = xglobal.process?.env?.[POLKADOTJS_DISABLE_ESM_CJS_WARNING_FLAG] === '1';
|
||||
const multipleEntries = entry.length !== 1;
|
||||
const disableWarnings = esmCjsWarningDisabled && entriesSameVersion;
|
||||
|
||||
if (multipleEntries && !disableWarnings) {
|
||||
warn(`${name} has multiple versions, ensure that there is only one installed.`, entry, formatVersion);
|
||||
} else {
|
||||
const mismatches = deps.filter((d) => d && d.version !== version);
|
||||
|
||||
if (mismatches.length) {
|
||||
warn(`${name} requires direct dependencies exactly matching version ${version}.`, mismatches, formatInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { extractTime } from './index.js';
|
||||
|
||||
describe('extractTime', (): void => {
|
||||
const milliseconds = 1e9 + 123;
|
||||
|
||||
it('extracts time components correctly', (): void => {
|
||||
expect(extractTime(milliseconds))
|
||||
.toEqual({
|
||||
days: 11,
|
||||
hours: 13,
|
||||
milliseconds: 123,
|
||||
minutes: 46,
|
||||
seconds: 40
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Time } from './types.js';
|
||||
|
||||
const MIN_MS = 60 * 1000;
|
||||
const HR_MS = MIN_MS * 60;
|
||||
const DAY_MS = HR_MS * 24;
|
||||
const ZERO: Time = { days: 0, hours: 0, milliseconds: 0, minutes: 0, seconds: 0 };
|
||||
|
||||
/** @internal */
|
||||
function add (a: Partial<Time>, b: Time): Time {
|
||||
return {
|
||||
days: (a.days || 0) + b.days,
|
||||
hours: (a.hours || 0) + b.hours,
|
||||
milliseconds: (a.milliseconds || 0) + b.milliseconds,
|
||||
minutes: (a.minutes || 0) + b.minutes,
|
||||
seconds: (a.seconds || 0) + b.seconds
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function extractSecs (ms: number): Time {
|
||||
const s = ms / 1000;
|
||||
|
||||
if (s < 60) {
|
||||
const seconds = ~~s;
|
||||
|
||||
return add({ seconds }, extractTime(ms - (seconds * 1000)));
|
||||
}
|
||||
|
||||
const m = s / 60;
|
||||
|
||||
if (m < 60) {
|
||||
const minutes = ~~m;
|
||||
|
||||
return add({ minutes }, extractTime(ms - (minutes * MIN_MS)));
|
||||
}
|
||||
|
||||
const h = m / 60;
|
||||
|
||||
if (h < 24) {
|
||||
const hours = ~~h;
|
||||
|
||||
return add({ hours }, extractTime(ms - (hours * HR_MS)));
|
||||
}
|
||||
|
||||
const days = ~~(h / 24);
|
||||
|
||||
return add({ days }, extractTime(ms - (days * DAY_MS)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @name extractTime
|
||||
* @summary Convert a quantity of seconds to Time array representing accumulated {days, minutes, hours, seconds, milliseconds}
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { extractTime } from '@pezkuwi/util';
|
||||
*
|
||||
* const { days, minutes, hours, seconds, milliseconds } = extractTime(6000); // 0, 0, 10, 0, 0
|
||||
* ```
|
||||
*/
|
||||
export function extractTime (milliseconds?: number): Time {
|
||||
return !milliseconds
|
||||
? ZERO
|
||||
: milliseconds < 1000
|
||||
? add({ milliseconds }, ZERO)
|
||||
: extractSecs(milliseconds);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { floatToU8a } from './toU8a.js';
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { u8aToHex } from '../u8a/index.js';
|
||||
import { floatToU8a } from './index.js';
|
||||
|
||||
class ExtNumber extends Number {
|
||||
foo = 'bar';
|
||||
}
|
||||
|
||||
class ExtString extends String {
|
||||
foo = 'bar';
|
||||
}
|
||||
|
||||
// NOTE Hex value outputs created via online conversion tool:
|
||||
// https://www.h-schmidt.net/FloatConverter/IEEE754.html
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
const TESTS: [isLe: boolean | undefined, bitLength: 32 | 64 | undefined, input: String | string | number | Number, output: string][] = [
|
||||
[undefined, undefined, +0.0, '0x00000000'],
|
||||
[undefined, undefined, -0.0, '0x00000080'],
|
||||
[undefined, undefined, 123.456, '0x79e9f642'],
|
||||
[undefined, undefined, '123.456', '0x79e9f642'],
|
||||
[undefined, undefined, new ExtNumber(123.456), '0x79e9f642'],
|
||||
[undefined, undefined, new ExtString(123.456), '0x79e9f642'],
|
||||
[true, 32, new ExtString(123.456), '0x79e9f642'],
|
||||
[true, undefined, Number.NaN, '0x0000c07f'],
|
||||
[false, undefined, -0.0, '0x80000000'],
|
||||
[undefined, 64, +0.0, '0x0000000000000000'],
|
||||
[true, 64, -0.0, '0x0000000000000080'],
|
||||
[false, 64, -0.0, '0x8000000000000000'],
|
||||
[undefined, 64, Number.NaN, '0x000000000000f87f']
|
||||
];
|
||||
|
||||
describe('floatToU8a', (): void => {
|
||||
it('throws on invalid bitLength', (): void => {
|
||||
expect(
|
||||
() => floatToU8a(123, { bitLength: 48 as 32 })
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
describe('conversion tests', (): void => {
|
||||
TESTS.forEach(([isLe, bitLength, input, output], i): void => {
|
||||
it(`#${i}: correctly encodes ${typeof input === 'number' ? input : input.toString()} (typeof=${typeof input})`, (): void => {
|
||||
expect(
|
||||
u8aToHex(floatToU8a(input, { bitLength, isLe }))
|
||||
).toEqual(output);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
interface Options {
|
||||
bitLength?: 32 | 64;
|
||||
isLe?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name floatToU8a
|
||||
* @description Converts a float into a U8a representation (While we don't use BE in SCALE
|
||||
* we still allow for either representation, although, as elsewhere, isLe is default)
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export function floatToU8a (value: String | string | number | Number = 0.0, { bitLength = 32, isLe = true }: Options = {}): Uint8Array {
|
||||
if (bitLength !== 32 && bitLength !== 64) {
|
||||
throw new Error('Invalid bitLength provided, expected 32 or 64');
|
||||
}
|
||||
|
||||
const result = new Uint8Array(bitLength / 8);
|
||||
const dv = new DataView(result.buffer, result.byteOffset);
|
||||
|
||||
if (bitLength === 32) {
|
||||
dv.setFloat32(0, Number(value), isLe);
|
||||
} else {
|
||||
dv.setFloat64(0, Number(value), isLe);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { BN } from '../bn/index.js';
|
||||
import { formatBalance } from './index.js';
|
||||
|
||||
const FMT_DEFAULTS = {
|
||||
decimals: 0,
|
||||
unit: 'Unit'
|
||||
};
|
||||
|
||||
describe('formatBalance', (): void => {
|
||||
const TESTVAL = new BN('123456789000');
|
||||
|
||||
// We mess around with the global setDefaults inside some tests,
|
||||
// ensure we restore it to the defaults straight after each run
|
||||
afterEach((): void => {
|
||||
formatBalance.setDefaults(FMT_DEFAULTS);
|
||||
});
|
||||
|
||||
it('returns options for dropdown', (): void => {
|
||||
formatBalance.setDefaults({ decimals: 0, unit: 'TEST' });
|
||||
|
||||
expect(
|
||||
formatBalance.getOptions()
|
||||
).toEqual([
|
||||
{ power: 0, text: 'TEST', value: '-' },
|
||||
{ power: 3, text: 'Kilo', value: 'k' },
|
||||
{ power: 6, text: 'Mill', value: 'M' },
|
||||
{ power: 9, text: 'Bill', value: 'B' },
|
||||
{ power: 12, text: 'Tril', value: 'T' },
|
||||
{ power: 15, text: 'Peta', value: 'P' },
|
||||
{ power: 18, text: 'Exa', value: 'E' },
|
||||
{ power: 21, text: 'Zeta', value: 'Z' },
|
||||
{ power: 24, text: 'Yotta', value: 'Y' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('can set defaults from array values', (): void => {
|
||||
formatBalance.setDefaults({ decimals: [12, 24], unit: ['Multi', 'Unit'] });
|
||||
|
||||
expect(
|
||||
formatBalance(TESTVAL)
|
||||
).toEqual('123.4567 mMulti');
|
||||
});
|
||||
|
||||
describe('SI formatting', (): void => {
|
||||
it('formats empty to 0', (): void => {
|
||||
expect(formatBalance()).toEqual('0');
|
||||
expect(formatBalance('0')).toEqual('0');
|
||||
});
|
||||
|
||||
// this is after an issue/test from actual values with forceUnit
|
||||
it('formats 1000 (BN) (decimals = 12, withAll, withZero = false)', (): void => {
|
||||
expect(
|
||||
formatBalance(new BN(1000), { decimals: 12, forceUnit: '-', withAll: true, withSi: false, withZero: true })
|
||||
).toEqual('0.000000001000');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=15)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 15, withSi: true })
|
||||
).toEqual('123.4567 µUnit');
|
||||
});
|
||||
|
||||
it('formats 123,456 (decimals=0)', (): void => {
|
||||
expect(
|
||||
formatBalance(123456, { decimals: 0, withSi: true })
|
||||
).toEqual('123.4560 kUnit');
|
||||
});
|
||||
|
||||
it('formats BigInt numbers', (): void => {
|
||||
expect(
|
||||
formatBalance(123456789000n, { decimals: 15, withSi: true })
|
||||
).toEqual('123.4567 µUnit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=15, old style)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 15, withSi: true })
|
||||
).toEqual('123.4567 µUnit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=10, withAll=true)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 10, forceUnit: '-', withAll: true, withSi: true })
|
||||
).toEqual('12.3456789000 Unit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=10, withAll=true, withZero=false)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 10, forceUnit: '-', withAll: true, withSi: true, withZero: false })
|
||||
).toEqual('12.3456789 Unit');
|
||||
});
|
||||
|
||||
it('formats 123,000,000,000 (decimals=9, withAll=true, withZero=false)', (): void => {
|
||||
expect(
|
||||
formatBalance('123000000000', { decimals: 9, forceUnit: '-', withAll: true, withSi: true, withZero: false })
|
||||
).toEqual('123 Unit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=15, Compact)', (): void => {
|
||||
const compact = {
|
||||
something: 'else',
|
||||
toBn: (): BN => TESTVAL,
|
||||
unwrap: (): BN => TESTVAL
|
||||
};
|
||||
|
||||
expect(
|
||||
formatBalance(compact, { decimals: 15, withSi: true })
|
||||
).toEqual('123.4567 µUnit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=12)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 12, withSi: true })
|
||||
).toEqual('123.4567 mUnit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=9)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 9, withSi: true })
|
||||
).toEqual('123.4567 Unit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=9, no unit)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 9, withSi: true, withUnit: false })
|
||||
).toEqual('123.4567');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=9, unit given)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 9, withSi: true, withUnit: 'FOO' })
|
||||
).toEqual('123.4567 FOO');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=12, no SI)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 12, withSi: false })
|
||||
).toEqual('123.4567');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=12, full SI)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 12, withSiFull: true })
|
||||
).toEqual('123.4567 milli Unit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=12, full SI, no unit)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 12, withSiFull: true, withUnit: false })
|
||||
).toEqual('123.4567 milli');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=9, full SI)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 9, withSiFull: true })
|
||||
).toEqual('123.4567 Unit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=6)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 6, withSi: true })
|
||||
).toEqual('123.4567 kUnit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=6, unit specified)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 6, withSi: true, withUnit: 'BAR' })
|
||||
).toEqual('123.4567 kBAR');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=0, unit specified)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 0, withSi: true, withUnit: 'BAR' })
|
||||
).toEqual('123.4567 BBAR');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 * 10 (decimals=12)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL.muln(10), { decimals: 12, withSi: true })
|
||||
).toEqual('1.2345 Unit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 * 100 (decimals=12)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL.muln(100), { decimals: 12, withSi: true })
|
||||
).toEqual('12.3456 Unit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 * 1000 (decimals=12)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL.muln(1000), { decimals: 12, withSi: true })
|
||||
).toEqual('123.4567 Unit');
|
||||
});
|
||||
|
||||
it('formats -123,456,789,000 (decimals=15)', (): void => {
|
||||
expect(
|
||||
formatBalance(new BN('-123456789000'), { decimals: 15, withSi: true })
|
||||
).toEqual('-123.4567 µUnit');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Forced formatting', (): void => {
|
||||
it('formats 123,456,789,000 (decimals=12, forceUnit=base)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 12, forceUnit: '-' })
|
||||
).toEqual('0.1234 Unit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=9, forceUnit=base)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 9, forceUnit: '-' })
|
||||
).toEqual('123.4567 Unit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=7, forceUnit=base)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 7, forceUnit: '-' })
|
||||
).toEqual('12,345.6789 Unit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=15, forceUnit=micro)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 15, forceUnit: 'µ' })
|
||||
).toEqual('123.4567 µUnit');
|
||||
});
|
||||
|
||||
it('formats 123,456 (decimals=0, locale=sl)', (): void => {
|
||||
expect(
|
||||
formatBalance(123456, { decimals: 0, locale: 'sl', withSi: true })
|
||||
).toEqual('123,4560 kUnit');
|
||||
});
|
||||
|
||||
it('formats BigInt numbers (locale=sl)', (): void => {
|
||||
expect(
|
||||
formatBalance(123456789000n, { decimals: 15, locale: 'sl', withSi: true })
|
||||
).toEqual('123,4567 µUnit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (decimals=7, forceUnit=base locale=sl)', (): void => {
|
||||
expect(
|
||||
formatBalance(TESTVAL, { decimals: 7, forceUnit: '-', locale: 'sl' })
|
||||
).toEqual('12.345,6789 Unit');
|
||||
});
|
||||
});
|
||||
|
||||
describe('calcSi', (): void => {
|
||||
it('exposes calcSi on formatBalance', (): void => {
|
||||
expect(
|
||||
formatBalance.calcSi('12345').value
|
||||
).toEqual('k');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findSi', (): void => {
|
||||
it('finds the SI value', (): void => {
|
||||
expect(
|
||||
formatBalance.findSi('k')
|
||||
).toEqual({ power: 3, text: 'Kilo', value: 'k' });
|
||||
});
|
||||
|
||||
it('returns default on not found', (): void => {
|
||||
expect(
|
||||
formatBalance.findSi('blah')
|
||||
).toEqual({ power: 0, text: 'Unit', value: '-' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaults', (): void => {
|
||||
it('returns defaults', (): void => {
|
||||
expect(
|
||||
formatBalance.getDefaults()
|
||||
).toEqual(FMT_DEFAULTS);
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (defaultDecimals=12)', (): void => {
|
||||
formatBalance.setDefaults({ decimals: 12 });
|
||||
|
||||
expect(
|
||||
formatBalance(TESTVAL)
|
||||
).toEqual('123.4567 mUnit');
|
||||
});
|
||||
|
||||
it('formats 123,456,789,000 (defaultUnit=TEST)', (): void => {
|
||||
formatBalance.setDefaults({ decimals: 12, unit: 'TEST' });
|
||||
|
||||
expect(
|
||||
formatBalance(TESTVAL)
|
||||
).toEqual('123.4567 mTEST');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '../bn/bn.js';
|
||||
import type { SiDef, ToBn } from '../types.js';
|
||||
|
||||
import { bnToBn } from '../bn/toBn.js';
|
||||
import { isBoolean } from '../is/boolean.js';
|
||||
import { formatDecimal } from './formatDecimal.js';
|
||||
import { getSeparator } from './getSeparator.js';
|
||||
import { calcSi, findSi, SI, SI_MID } from './si.js';
|
||||
|
||||
interface Defaults {
|
||||
decimals: number;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
interface SetDefaults {
|
||||
decimals?: number[] | number;
|
||||
unit?: string[] | string;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
/**
|
||||
* @description The number of decimals
|
||||
*/
|
||||
decimals?: number;
|
||||
/**
|
||||
* @description Format the number with this specific unit
|
||||
*/
|
||||
forceUnit?: string;
|
||||
/**
|
||||
* @description Returns value using all available decimals
|
||||
*/
|
||||
withAll?: boolean;
|
||||
/**
|
||||
* @description Format with SI, i.e. m/M/etc. (default = true)
|
||||
*/
|
||||
withSi?: boolean;
|
||||
/**
|
||||
* @description Format with full SI, i.e. mili/Mega/etc.
|
||||
*/
|
||||
withSiFull?: boolean;
|
||||
/**
|
||||
* @description Add the unit (useful in Balance formats)
|
||||
*/
|
||||
withUnit?: boolean | string;
|
||||
/**
|
||||
* @description Returns all trailing zeros, otherwise removes (default = true)
|
||||
*/
|
||||
withZero?: boolean;
|
||||
/**
|
||||
* @description The locale to use
|
||||
*/
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
interface BalanceFormatter {
|
||||
<ExtToBn extends ToBn> (input?: number | string | BN | bigint | ExtToBn, options?: Options): string;
|
||||
calcSi (text: string, decimals?: number): SiDef;
|
||||
findSi (type: string): SiDef;
|
||||
getDefaults (): Defaults;
|
||||
getOptions (decimals?: number): SiDef[];
|
||||
setDefaults (defaults: SetDefaults): void;
|
||||
}
|
||||
|
||||
const DEFAULT_DECIMALS = 0;
|
||||
const DEFAULT_UNIT = SI[SI_MID].text;
|
||||
|
||||
let defaultDecimals = DEFAULT_DECIMALS;
|
||||
let defaultUnit = DEFAULT_UNIT;
|
||||
|
||||
// Formats a string/number with <prefix>.<postfix><type> notation
|
||||
function _formatBalance <ExtToBn extends ToBn> (input?: number | string | BN | bigint | ExtToBn, { decimals = defaultDecimals, forceUnit, locale = 'en', withAll = false, withSi = true, withSiFull = false, withUnit = true, withZero = true }: Options = {}): string {
|
||||
// we only work with string inputs here - convert anything
|
||||
// into the string-only value
|
||||
let text = bnToBn(input).toString();
|
||||
|
||||
if (text.length === 0 || text === '0') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
// strip the negative sign so we can work with clean groupings, re-add this in the
|
||||
// end when we return the result (from here on we work with positive numbers)
|
||||
let sign = '';
|
||||
|
||||
if (text[0].startsWith('-')) {
|
||||
sign = '-';
|
||||
text = text.substring(1);
|
||||
}
|
||||
|
||||
// We start at midpoint (8) minus 1 - this means that values display as
|
||||
// 123.4567 instead of 0.1234 k (so we always have the most relevant).
|
||||
const si = calcSi(text, decimals, forceUnit);
|
||||
const mid = text.length - (decimals + si.power);
|
||||
const pre = mid <= 0 ? '0' : text.substring(0, mid);
|
||||
|
||||
// get the post from the midpoint onward and then first add max decimals
|
||||
// before trimming to the correct (calculated) amount of decimals again
|
||||
let post = text
|
||||
.padStart(mid < 0 ? decimals : 1, '0')
|
||||
.substring(mid < 0 ? 0 : mid)
|
||||
.padEnd(withAll ? Math.max(decimals, 4) : 4, '0')
|
||||
.substring(0, withAll ? Math.max(4, decimals + si.power) : 4);
|
||||
|
||||
// remove all trailing 0's (if required via flag)
|
||||
if (!withZero) {
|
||||
let end = post.length - 1;
|
||||
|
||||
// This looks inefficient, however it is better to do the checks and
|
||||
// only make one final slice than it is to do it in multiples
|
||||
do {
|
||||
if (post[end] === '0') {
|
||||
end--;
|
||||
}
|
||||
} while (post[end] === '0');
|
||||
|
||||
post = post.substring(0, end + 1);
|
||||
}
|
||||
|
||||
// the display unit
|
||||
const unit = isBoolean(withUnit)
|
||||
? SI[SI_MID].text
|
||||
: withUnit;
|
||||
|
||||
// format the units for display based on the flags
|
||||
const units = withSi || withSiFull
|
||||
? si.value === '-'
|
||||
? withUnit
|
||||
? ` ${unit}`
|
||||
: ''
|
||||
: ` ${withSiFull ? `${si.text}${withUnit ? ' ' : ''}` : si.value}${withUnit ? unit : ''}`
|
||||
: '';
|
||||
|
||||
const { decimal, thousand } = getSeparator(locale);
|
||||
|
||||
return `${sign}${formatDecimal(pre, thousand)}${post && `${decimal}${post}`}${units}`;
|
||||
}
|
||||
|
||||
export const formatBalance = _formatBalance as BalanceFormatter;
|
||||
|
||||
formatBalance.calcSi = (text: string, decimals: number = defaultDecimals): SiDef =>
|
||||
calcSi(text, decimals);
|
||||
|
||||
formatBalance.findSi = findSi;
|
||||
|
||||
formatBalance.getDefaults = (): Defaults => {
|
||||
return {
|
||||
decimals: defaultDecimals,
|
||||
unit: defaultUnit
|
||||
};
|
||||
};
|
||||
|
||||
// get allowable options to display in a dropdown
|
||||
formatBalance.getOptions = (decimals: number = defaultDecimals): SiDef[] => {
|
||||
return SI.filter(({ power }): boolean =>
|
||||
power < 0
|
||||
? (decimals + power) >= 0
|
||||
: true
|
||||
);
|
||||
};
|
||||
|
||||
// Sets the default decimals to use for formatting (ui-wide)
|
||||
formatBalance.setDefaults = ({ decimals, unit }: SetDefaults): void => {
|
||||
defaultDecimals = (
|
||||
Array.isArray(decimals)
|
||||
? decimals[0]
|
||||
: decimals
|
||||
) ?? defaultDecimals;
|
||||
defaultUnit = (
|
||||
Array.isArray(unit)
|
||||
? unit[0]
|
||||
: unit
|
||||
) ?? defaultUnit;
|
||||
|
||||
SI[SI_MID].text = defaultUnit;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { formatDate } from './index.js';
|
||||
|
||||
describe('formatDate', (): void => {
|
||||
it('formats a known date into the correct format', (): void => {
|
||||
const date = new Date(Date.UTC(2020, 1, 15, 11, 14, 34));
|
||||
|
||||
// make this test-locale agnostic
|
||||
date.setTime(date.getTime() + date.getTimezoneOffset() * 60 * 1000);
|
||||
|
||||
expect(formatDate(date)).toEqual('2020-02-15 11:14:34');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/** @internal */
|
||||
function zeroPad (value: number): string {
|
||||
return value.toString().padStart(2, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* @name formatDate
|
||||
* @description Formats a date in CCYY-MM-DD HH:MM:SS format
|
||||
*/
|
||||
export function formatDate (date: Date): string {
|
||||
const year = date.getFullYear().toString();
|
||||
const month = zeroPad((date.getMonth() + 1));
|
||||
const day = zeroPad(date.getDate());
|
||||
const hour = zeroPad(date.getHours());
|
||||
const minute = zeroPad(date.getMinutes());
|
||||
const second = zeroPad(date.getSeconds());
|
||||
|
||||
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { formatDecimal } from './index.js';
|
||||
|
||||
describe('formatDecimal', (): void => {
|
||||
it('formats decimals in number groupings', (): void => {
|
||||
expect(formatDecimal('12345')).toEqual('12,345');
|
||||
});
|
||||
|
||||
it('formats decimal-only in number groupings', (): void => {
|
||||
expect(formatDecimal('test6789')).toEqual('6,789');
|
||||
});
|
||||
|
||||
it('returns input for non-decimal', (): void => {
|
||||
expect(formatDecimal('test')).toEqual('test');
|
||||
});
|
||||
|
||||
it('returns non-sensical negative text', (): void => {
|
||||
expect(formatDecimal('-test')).toEqual('-test');
|
||||
});
|
||||
|
||||
it('formats negative numbers', (): void => {
|
||||
expect(formatDecimal('-123456')).toEqual('-123,456');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// eslint-disable-next-line prefer-regex-literals
|
||||
const NUMBER_REGEX = new RegExp('(\\d+?)(?=(\\d{3})+(?!\\d)|$)', 'g');
|
||||
|
||||
/**
|
||||
* @name formatDecimal
|
||||
* @description Formats a number into string format with thousand separators
|
||||
*/
|
||||
export function formatDecimal (value: string, separator = ','): string {
|
||||
// We can do this by adjusting the regx, however for the sake of clarity
|
||||
// we rather strip and re-add the negative sign in the output
|
||||
const isNegative = value[0].startsWith('-');
|
||||
const matched = isNegative
|
||||
? value.substring(1).match(NUMBER_REGEX)
|
||||
: value.match(NUMBER_REGEX);
|
||||
|
||||
return matched
|
||||
? `${isNegative ? '-' : ''}${matched.join(separator)}`
|
||||
: value;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { BN } from '../bn/index.js';
|
||||
import { formatElapsed } from './index.js';
|
||||
|
||||
describe('formatElapsed', (): void => {
|
||||
const start = 12345678;
|
||||
const now = new Date(12345678);
|
||||
|
||||
it('formats a Date', (): void => {
|
||||
expect(
|
||||
formatElapsed(now, new Date(start + 9700))
|
||||
).toEqual('9.7s');
|
||||
});
|
||||
|
||||
it('formats a BN', (): void => {
|
||||
expect(
|
||||
formatElapsed(now, new BN(start + 42700))
|
||||
).toEqual('42s');
|
||||
});
|
||||
|
||||
it('formats a Compact', (): void => {
|
||||
expect(
|
||||
formatElapsed(now, {
|
||||
toBn: (): BN => new BN(start + (5.3 * 60000))
|
||||
})
|
||||
).toEqual('5m');
|
||||
});
|
||||
|
||||
it('formats a number', (): void => {
|
||||
expect(
|
||||
formatElapsed(now, start + (42 * 60 * 60000))
|
||||
).toEqual('42h');
|
||||
});
|
||||
|
||||
it('formats defaults', (): void => {
|
||||
expect(
|
||||
formatElapsed()
|
||||
).toEqual('0.0s');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '../bn/bn.js';
|
||||
import type { ToBn } from '../types.js';
|
||||
|
||||
import { bnToBn } from '../bn/toBn.js';
|
||||
|
||||
/** @internal */
|
||||
function formatValue (elapsed: number): string {
|
||||
if (elapsed < 15) {
|
||||
return `${elapsed.toFixed(1)}s`;
|
||||
} else if (elapsed < 60) {
|
||||
return `${elapsed | 0}s`;
|
||||
} else if (elapsed < 3600) {
|
||||
return `${elapsed / 60 | 0}m`;
|
||||
}
|
||||
|
||||
return `${elapsed / 3600 | 0}h`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name formatElapsed
|
||||
* @description Formats an elapsed value into s, m, h or day segments
|
||||
*/
|
||||
export function formatElapsed <ExtToBn extends ToBn> (now?: Date | null, value?: bigint | BN | ExtToBn | Date | number | null): string {
|
||||
const tsNow = now?.getTime() || 0;
|
||||
const tsValue = value instanceof Date
|
||||
? value.getTime()
|
||||
: bnToBn(value).toNumber();
|
||||
|
||||
return (tsNow && tsValue)
|
||||
? formatValue(Math.max(Math.abs(tsNow - tsValue), 0) / 1000)
|
||||
: '0.0s';
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { BN } from '../bn/index.js';
|
||||
import { formatNumber } from './index.js';
|
||||
|
||||
describe('formatNumber', (): void => {
|
||||
it('formats empty', (): void => {
|
||||
expect(
|
||||
formatNumber()
|
||||
).toEqual('0');
|
||||
});
|
||||
|
||||
it('formats negative numbers', (): void => {
|
||||
expect(
|
||||
formatNumber(-123456)
|
||||
).toEqual('-123,456');
|
||||
});
|
||||
|
||||
it('formats BN numbers', (): void => {
|
||||
expect(
|
||||
formatNumber(new BN(12345))
|
||||
).toEqual('12,345');
|
||||
});
|
||||
|
||||
it('formats BigInt numbers', (): void => {
|
||||
expect(
|
||||
formatNumber(123456789n)
|
||||
).toEqual('123,456,789');
|
||||
});
|
||||
|
||||
it('formats Compact<BN>', (): void => {
|
||||
expect(
|
||||
formatNumber({
|
||||
toBn: (): BN => new BN(12345),
|
||||
unwrap: (): BN => new BN(0)
|
||||
})
|
||||
).toEqual('12,345');
|
||||
});
|
||||
|
||||
it('formats negative numbers (locale=sl)', (): void => {
|
||||
expect(
|
||||
formatNumber(-123456, { locale: 'sl' })
|
||||
).toEqual('-123.456');
|
||||
});
|
||||
|
||||
it('formats BN numbers (locale=sl)', (): void => {
|
||||
expect(
|
||||
formatNumber(new BN(12345), { locale: 'sl' })
|
||||
).toEqual('12.345');
|
||||
});
|
||||
|
||||
it('formats BigInt numbers (locale=sl)', (): void => {
|
||||
expect(
|
||||
formatNumber(123456789n, { locale: 'sl' })
|
||||
).toEqual('123.456.789');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '../bn/bn.js';
|
||||
import type { ToBn } from '../types.js';
|
||||
|
||||
import { bnToBn } from '../bn/toBn.js';
|
||||
import { formatDecimal } from './formatDecimal.js';
|
||||
import { getSeparator } from './getSeparator.js';
|
||||
|
||||
interface Options {
|
||||
/**
|
||||
* @description The locale to use
|
||||
*/
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name formatNumber
|
||||
* @description Formats a number into string format with thousand separators
|
||||
*/
|
||||
export function formatNumber <ExtToBn extends ToBn> (
|
||||
value?: ExtToBn | BN | bigint | number | null,
|
||||
{ locale = 'en' }: Options = {}
|
||||
): string {
|
||||
const { thousand } = getSeparator(locale);
|
||||
|
||||
return formatDecimal(bnToBn(value).toString(), thousand);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { getSeparator } from './getSeparator.js';
|
||||
|
||||
describe('getSeparator', (): void => {
|
||||
const normalizeSeparator = (separator: string) =>
|
||||
separator === '\u202F' ? ' ' : separator;
|
||||
|
||||
const testLocales = [
|
||||
{ decimal: '.', locale: 'en', thousand: ',' },
|
||||
{ decimal: '.', locale: 'en-gb', thousand: ',' },
|
||||
{ decimal: ',', locale: 'sl', thousand: '.' },
|
||||
{ decimal: ',', locale: 'sl-si', thousand: '.' },
|
||||
{ decimal: ',', locale: 'it-it', thousand: '.' },
|
||||
{ decimal: '.', locale: 'ja-jp', thousand: ',' },
|
||||
{ decimal: '.', locale: 'hi-IN', thousand: ',' },
|
||||
{ decimal: '.', locale: undefined, thousand: ',' } // Fallback test
|
||||
];
|
||||
|
||||
testLocales.forEach(({ decimal, locale, thousand }) => {
|
||||
it(`uses ${locale || 'system default'} locale`, (): void => {
|
||||
expect(
|
||||
getSeparator(locale)
|
||||
).toEqual({ decimal, thousand });
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back for invalid locale', (): void => {
|
||||
expect(
|
||||
getSeparator('invalid-locale')
|
||||
).toEqual({ decimal: '.', thousand: ',' }); // Default fallback
|
||||
});
|
||||
|
||||
it('matches system locale dynamically', (): void => {
|
||||
const n = 1000.1;
|
||||
const expected = {
|
||||
decimal: n.toLocaleString().substring(5, 6),
|
||||
thousand: n.toLocaleString().substring(1, 2)
|
||||
};
|
||||
|
||||
expect(getSeparator()).toEqual(expected);
|
||||
});
|
||||
|
||||
it('handles locales with unique separators', (): void => {
|
||||
const result = getSeparator('fr-FR');
|
||||
|
||||
expect(normalizeSeparator(result.thousand)).toBe(' ');
|
||||
expect(result.decimal).toBe(',');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Get the decimal and thousand separator of a locale
|
||||
* @param locale
|
||||
* @returns {decimal: string, thousand: string}
|
||||
*/
|
||||
export function getSeparator (locale?: string):
|
||||
{ thousand: string, decimal: string } {
|
||||
return {
|
||||
decimal: (0.1).toLocaleString(locale, { useGrouping: false }).charAt(1),
|
||||
thousand: (1000).toLocaleString(locale, { useGrouping: true }).replace(/\d/g, '').charAt(0)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { formatBalance } from './formatBalance.js';
|
||||
export { formatDate } from './formatDate.js';
|
||||
export { formatDecimal } from './formatDecimal.js';
|
||||
export { formatElapsed } from './formatElapsed.js';
|
||||
export { formatNumber } from './formatNumber.js';
|
||||
export { calcSi, findSi } from './si.js';
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SiDef } from '../types.js';
|
||||
|
||||
/** @internal */
|
||||
export const SI_MID = 8;
|
||||
|
||||
/** @internal */
|
||||
export const SI: SiDef[] = [
|
||||
{ power: -24, text: 'yocto', value: 'y' },
|
||||
{ power: -21, text: 'zepto', value: 'z' },
|
||||
{ power: -18, text: 'atto', value: 'a' },
|
||||
{ power: -15, text: 'femto', value: 'f' },
|
||||
{ power: -12, text: 'pico', value: 'p' },
|
||||
{ power: -9, text: 'nano', value: 'n' },
|
||||
{ power: -6, text: 'micro', value: 'µ' },
|
||||
{ power: -3, text: 'milli', value: 'm' },
|
||||
{ power: 0, text: 'Unit', value: '-' }, // position 8
|
||||
{ power: 3, text: 'Kilo', value: 'k' },
|
||||
{ power: 6, text: 'Mill', value: 'M' }, // Mega, M
|
||||
{ power: 9, text: 'Bill', value: 'B' }, // Giga, G
|
||||
{ power: 12, text: 'Tril', value: 'T' }, // Tera, T
|
||||
{ power: 15, text: 'Peta', value: 'P' },
|
||||
{ power: 18, text: 'Exa', value: 'E' },
|
||||
{ power: 21, text: 'Zeta', value: 'Z' },
|
||||
{ power: 24, text: 'Yotta', value: 'Y' }
|
||||
];
|
||||
|
||||
// Given a SI type (e.g. k, m, Y) find the SI definition
|
||||
/** @internal */
|
||||
export function findSi (type: string): SiDef {
|
||||
// use a loop here, better RN support (which doesn't have [].find)
|
||||
for (let i = 0, count = SI.length; i < count; i++) {
|
||||
if (SI[i].value === type) {
|
||||
return SI[i];
|
||||
}
|
||||
}
|
||||
|
||||
return SI[SI_MID];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function calcSi (text: string, decimals: number, forceUnit?: string): SiDef {
|
||||
if (forceUnit) {
|
||||
return findSi(forceUnit);
|
||||
}
|
||||
|
||||
const siDefIndex = (SI_MID - 1) + Math.ceil((text.length - decimals) / 3);
|
||||
|
||||
return SI[siDefIndex] || SI[siDefIndex < 0 ? 0 : SI.length - 1];
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hasBuffer } from './has.js';
|
||||
|
||||
describe('hasBuffer', (): void => {
|
||||
it('has Buffer (Jest + Node.js)', (): void => {
|
||||
expect(hasBuffer).toEqual(typeof Buffer !== 'undefined');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BufferClass } from './types.js';
|
||||
|
||||
import { BigInt } from '@pezkuwi/x-bigint';
|
||||
import { xglobal } from '@pezkuwi/x-global';
|
||||
|
||||
// Since we run in very different environments, we have to ensure we have all
|
||||
// the types used here for detection (some of these may require Node definitions,
|
||||
// which are not available in Deno/browser)
|
||||
declare const __dirname: unknown;
|
||||
declare const module: unknown;
|
||||
declare const require: unknown;
|
||||
|
||||
/** true if the environment has proper BigInt support */
|
||||
export const hasBigInt = typeof BigInt === 'function' && typeof BigInt.asIntN === 'function';
|
||||
|
||||
/** true if the environment is CJS */
|
||||
export const hasCjs = typeof require === 'function' && typeof module !== 'undefined';
|
||||
|
||||
/** true if the environment has __dirname available */
|
||||
export const hasDirname = typeof __dirname !== 'undefined';
|
||||
|
||||
/** true if the environment is ESM */
|
||||
export const hasEsm = !hasCjs;
|
||||
|
||||
/** true if the environment has WebAssembly available */
|
||||
export const hasWasm = typeof WebAssembly !== 'undefined';
|
||||
|
||||
// NOTE We check the following on globalThis, avoiding specific polyfill detection
|
||||
// that some bundlers such as parcel would add (this is a check, not a use)
|
||||
|
||||
/** true if the environment has support for Buffer (typically Node.js) */
|
||||
export const hasBuffer = typeof xglobal.Buffer === 'function' && typeof (xglobal.Buffer as unknown as BufferClass).isBuffer === 'function';
|
||||
|
||||
/** true if the environment has process available (typically Node.js) */
|
||||
export const hasProcess = typeof xglobal.process === 'object';
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hexAddPrefix } from './index.js';
|
||||
|
||||
describe('hexAddPrefix', (): void => {
|
||||
it('does not add when prefix is available', (): void => {
|
||||
expect(
|
||||
hexAddPrefix('0x0123')
|
||||
).toEqual('0x0123');
|
||||
});
|
||||
|
||||
it('adds the prefix when it is not available', (): void => {
|
||||
expect(
|
||||
hexAddPrefix('0123')
|
||||
).toEqual('0x0123');
|
||||
});
|
||||
|
||||
it('adds extra 0 when length % 2 === 1', (): void => {
|
||||
expect(
|
||||
hexAddPrefix('123')
|
||||
).toEqual('0x0123');
|
||||
});
|
||||
|
||||
it('returns null as 0x', (): void => {
|
||||
expect(
|
||||
hexAddPrefix(null)
|
||||
).toEqual('0x');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HexString } from '../types.js';
|
||||
|
||||
import { hexHasPrefix } from './hasPrefix.js';
|
||||
|
||||
/**
|
||||
* @name hexAddPrefix
|
||||
* @summary Adds the `0x` prefix to string values.
|
||||
* @description
|
||||
* Returns a `0x` prefixed string from the input value. If the input is already prefixed, it is returned unchanged.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { hexAddPrefix } from '@pezkuwi/util';
|
||||
*
|
||||
* console.log('With prefix', hexAddPrefix('0a0b12')); // => 0x0a0b12
|
||||
* ```
|
||||
*/
|
||||
export function hexAddPrefix (value?: string | null): HexString {
|
||||
return value && hexHasPrefix(value)
|
||||
? value
|
||||
: `0x${value && value.length % 2 === 1 ? '0' : ''}${value || ''}`;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hexFixLength } from './index.js';
|
||||
|
||||
describe('hexFixLength', (): void => {
|
||||
it('returns bitLength === -1 as-is', (): void => {
|
||||
expect(
|
||||
hexFixLength('0x12345678')
|
||||
).toEqual('0x12345678');
|
||||
});
|
||||
|
||||
it('does not change when bitlength === length', (): void => {
|
||||
expect(
|
||||
hexFixLength('0x12345678', 32)
|
||||
).toEqual('0x12345678');
|
||||
});
|
||||
|
||||
it('trims values when bitLength > length', (): void => {
|
||||
expect(
|
||||
hexFixLength('0x12345678', 16)
|
||||
).toEqual('0x5678');
|
||||
});
|
||||
|
||||
it('returns as-is when bitLength < length', (): void => {
|
||||
expect(
|
||||
hexFixLength('0x1234', 32)
|
||||
).toEqual('0x1234');
|
||||
});
|
||||
|
||||
it('adds zeros when bitLength < length (withPadded)', (): void => {
|
||||
expect(
|
||||
hexFixLength('0x1234', 32, true)
|
||||
).toEqual('0x00001234');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HexString } from '../types.js';
|
||||
|
||||
import { hexAddPrefix } from './addPrefix.js';
|
||||
import { hexStripPrefix } from './stripPrefix.js';
|
||||
|
||||
/**
|
||||
* @name hexFixLength
|
||||
* @summary Shifts a hex string to a specific bitLength
|
||||
* @description
|
||||
* Returns a `0x` prefixed string with the specified number of bits contained in the return value. (If bitLength is -1, length checking is not done). Values with more bits are trimmed to the specified length. Input values with less bits are returned as-is by default. When `withPadding` is set, shorter values are padded with `0`.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { hexFixLength } from '@pezkuwi/util';
|
||||
*
|
||||
* console.log('fixed', hexFixLength('0x12', 16)); // => 0x12
|
||||
* console.log('fixed', hexFixLength('0x12', 16, true)); // => 0x0012
|
||||
* console.log('fixed', hexFixLength('0x0012', 8)); // => 0x12
|
||||
* ```
|
||||
*/
|
||||
export function hexFixLength (value: string, bitLength = -1, withPadding = false): HexString {
|
||||
const strLength = Math.ceil(bitLength / 4);
|
||||
const hexLength = strLength + 2;
|
||||
|
||||
return hexAddPrefix(
|
||||
(bitLength === -1 || value.length === hexLength || (!withPadding && value.length < hexLength))
|
||||
? hexStripPrefix(value)
|
||||
: (value.length > hexLength)
|
||||
? hexStripPrefix(value).slice(-1 * strLength)
|
||||
: `${'0'.repeat(strLength)}${hexStripPrefix(value)}`.slice(-1 * strLength)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hexHasPrefix } from './index.js';
|
||||
|
||||
describe('hexHasPrefix', (): void => {
|
||||
it('returns true when hex prefix is found', (): void => {
|
||||
expect(
|
||||
hexHasPrefix('0x1234')
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when no prefix attached', (): void => {
|
||||
expect(
|
||||
hexHasPrefix('123')
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false when null value supplied', (): void => {
|
||||
expect(
|
||||
hexHasPrefix(null)
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false when non-string value supplied', (): void => {
|
||||
expect(
|
||||
hexHasPrefix(false as unknown as string)
|
||||
).toEqual(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HexString } from '../types.js';
|
||||
|
||||
import { isHex } from '../is/hex.js';
|
||||
|
||||
/**
|
||||
* @name hexHasPrefix
|
||||
* @summary Tests for the existence of a `0x` prefix.
|
||||
* @description
|
||||
* Checks for a valid hex input value and if the start matched `0x`
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { hexHasPrefix } from '@pezkuwi/util';
|
||||
*
|
||||
* console.log('has prefix', hexHasPrefix('0x1234')); // => true
|
||||
* ```
|
||||
*/
|
||||
export function hexHasPrefix (value?: string | null): value is HexString {
|
||||
return !!value && isHex(value, -1);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @summary Internal utilities to create and test for hex values
|
||||
*/
|
||||
|
||||
export { hexAddPrefix } from './addPrefix.js';
|
||||
export { hexFixLength } from './fixLength.js';
|
||||
export { hexHasPrefix } from './hasPrefix.js';
|
||||
export { hexStripPrefix } from './stripPrefix.js';
|
||||
export { hexToBigInt } from './toBigInt.js';
|
||||
export { hexToBn } from './toBn.js';
|
||||
export { hexToNumber } from './toNumber.js';
|
||||
export { hexToString } from './toString.js';
|
||||
export { hexToU8a } from './toU8a.js';
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hexStripPrefix } from './index.js';
|
||||
|
||||
describe('hexStripPrefix', (): void => {
|
||||
it('returns an empty string when null value supplied', (): void => {
|
||||
expect(
|
||||
hexStripPrefix(null)
|
||||
).toEqual('');
|
||||
});
|
||||
|
||||
it('returns an empty string when 0x value supplied', (): void => {
|
||||
expect(
|
||||
hexStripPrefix('0x')
|
||||
).toEqual('');
|
||||
});
|
||||
|
||||
it('strips the prefix from hex strings', (): void => {
|
||||
expect(
|
||||
hexStripPrefix('0x1223')
|
||||
).toEqual('1223');
|
||||
});
|
||||
|
||||
it('strips the prefix from hex strings (non 2 lnegth)', (): void => {
|
||||
expect(
|
||||
hexStripPrefix('0x123')
|
||||
).toEqual('123');
|
||||
});
|
||||
|
||||
it('returns un-prefixed hex as-is', (): void => {
|
||||
expect(
|
||||
hexStripPrefix('abcd1223')
|
||||
).toEqual('abcd1223');
|
||||
});
|
||||
|
||||
it('throws when invalid hex', (): void => {
|
||||
expect(
|
||||
() => hexStripPrefix('0x0x01ab')
|
||||
).toThrow(/Expected hex value to convert/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { REGEX_HEX_NOPREFIX, REGEX_HEX_PREFIXED } from '../is/hex.js';
|
||||
|
||||
/**
|
||||
* @name hexStripPrefix
|
||||
* @summary Strips any leading `0x` prefix.
|
||||
* @description
|
||||
* Tests for the existence of a `0x` prefix, and returns the value without the prefix. Un-prefixed values are returned as-is.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { hexStripPrefix } from '@pezkuwi/util';
|
||||
*
|
||||
* console.log('stripped', hexStripPrefix('0x1234')); // => 1234
|
||||
* ```
|
||||
*/
|
||||
export function hexStripPrefix (value?: string | null): string {
|
||||
if (!value || value === '0x') {
|
||||
return '';
|
||||
} else if (REGEX_HEX_PREFIXED.test(value)) {
|
||||
return value.substring(2);
|
||||
} else if (REGEX_HEX_NOPREFIX.test(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new Error(`Expected hex value to convert, found '${value}'`);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hexToBigInt } from './index.js';
|
||||
|
||||
describe('hexToBigInt', (): void => {
|
||||
it('converts prefixed hex values to BN', (): void => {
|
||||
expect(
|
||||
hexToBigInt('0x81')
|
||||
).toEqual(0x81n);
|
||||
});
|
||||
|
||||
it('converts null values to BN(0)', (): void => {
|
||||
expect(
|
||||
hexToBigInt(null)
|
||||
).toEqual(0n);
|
||||
});
|
||||
|
||||
it('converts 0x values to BN(0)', (): void => {
|
||||
expect(
|
||||
hexToBigInt('0x')
|
||||
).toEqual(0n);
|
||||
});
|
||||
|
||||
it('should convert with Big Endian by default', (): void => {
|
||||
expect(
|
||||
hexToBigInt('0x0100123456')
|
||||
).toEqual(0x0100123456n);
|
||||
});
|
||||
|
||||
it('converts 0x values to BN(0) (LE)', (): void => {
|
||||
expect(
|
||||
hexToBigInt('0x', { isLe: true })
|
||||
).toEqual(0n);
|
||||
});
|
||||
|
||||
it('converts little-endian', (): void => {
|
||||
expect(
|
||||
hexToBigInt('0x4500000000000000', { isLe: true })
|
||||
).toEqual(69n);
|
||||
});
|
||||
|
||||
it('handles negative numbers (LE)', (): void => {
|
||||
expect(
|
||||
hexToBigInt('0x2efb', { isLe: true, isNegative: true })
|
||||
).toEqual(-1234n);
|
||||
});
|
||||
|
||||
it('handles negative numbers (BE)', (): void => {
|
||||
expect(
|
||||
hexToBigInt('0xfb2e', { isLe: false, isNegative: true })
|
||||
).toEqual(-1234n);
|
||||
});
|
||||
|
||||
it('handles negative numbers (LE, 128)', (): void => {
|
||||
expect(
|
||||
hexToBigInt('0x00009c584c491ff2ffffffffffffffff', { isLe: true, isNegative: true })
|
||||
).toEqual(-1000000000000000000n);
|
||||
});
|
||||
|
||||
it('handles starting zeros correctly (BE)', (): void => {
|
||||
expect(
|
||||
hexToBigInt('0x0000000000000100', { isLe: false })
|
||||
).toEqual(256n);
|
||||
});
|
||||
|
||||
it('handles starting zeros correctly (LE)', (): void => {
|
||||
expect(
|
||||
hexToBigInt('0x0001000000000000', { isLe: true })
|
||||
).toEqual(256n);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ToBnOptions } from '../types.js';
|
||||
|
||||
import { BigInt } from '@pezkuwi/x-bigint';
|
||||
|
||||
import { u8aToBigInt } from '../u8a/toBigInt.js';
|
||||
import { hexToU8a } from './toU8a.js';
|
||||
|
||||
/**
|
||||
* @name hexToBigInt
|
||||
* @summary Creates a BigInt instance object from a hex string.
|
||||
*/
|
||||
export function hexToBigInt (value?: string | null, { isLe = false, isNegative = false }: ToBnOptions = {}): bigint {
|
||||
return !value || value === '0x'
|
||||
? BigInt(0)
|
||||
: u8aToBigInt(hexToU8a(value), { isLe, isNegative });
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hexToBn } from './index.js';
|
||||
|
||||
describe('hexToBn', (): void => {
|
||||
it('converts prefixed hex values to BN', (): void => {
|
||||
expect(
|
||||
hexToBn('0x81').toString(16)
|
||||
).toBe('81');
|
||||
});
|
||||
|
||||
it('converts null values to BN(0)', (): void => {
|
||||
expect(
|
||||
hexToBn(null).toNumber()
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
it('converts 0x values to BN(0)', (): void => {
|
||||
expect(
|
||||
hexToBn('0x').toNumber()
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
it('should convert with Big Endian by default', (): void => {
|
||||
expect(
|
||||
hexToBn('0x0100').toNumber()
|
||||
).toBe(256);
|
||||
});
|
||||
|
||||
it('converts 0x values to BN(0) (LE)', (): void => {
|
||||
expect(
|
||||
hexToBn('0x', { isLe: true }).toNumber()
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
it('converts little-endian', (): void => {
|
||||
expect(
|
||||
hexToBn('0x4500000000000000', { isLe: true }).toNumber()
|
||||
).toBe(69);
|
||||
});
|
||||
|
||||
it('handles negative numbers (LE)', (): void => {
|
||||
expect(
|
||||
hexToBn('0x2efb', { isLe: true, isNegative: true }).toNumber()
|
||||
).toBe(-1234);
|
||||
});
|
||||
|
||||
it('handles negative numbers (BE)', (): void => {
|
||||
expect(
|
||||
hexToBn('0xfb2e', { isLe: false, isNegative: true }).toNumber()
|
||||
).toBe(-1234);
|
||||
});
|
||||
|
||||
it('handles negative numbers (LE, 128)', (): void => {
|
||||
expect(
|
||||
hexToBn('0x00009c584c491ff2ffffffffffffffff', { isLe: true, isNegative: true }).toString()
|
||||
).toEqual('-1000000000000000000');
|
||||
});
|
||||
|
||||
it('handles starting zeros correctly (BE)', (): void => {
|
||||
expect(
|
||||
hexToBn('0x0000000000000100', { isLe: false }).toNumber()
|
||||
).toBe(256);
|
||||
});
|
||||
|
||||
it('handles starting zeros correctly (LE)', (): void => {
|
||||
expect(
|
||||
hexToBn('0x0001000000000000', { isLe: true }).toNumber()
|
||||
).toBe(256);
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user