diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1b9b91ac8..189172b71 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# CHANGELOG
+## master
+
+Changes:
+
+- Fix `u8aTo{BigInt, Bn, Number}` for non-negative `i{8, 16, 32...}` inputs
+
+
## 12.4.1 Aug 17, 2023
Changes:
diff --git a/packages/util/src/bi/toU8a.spec.ts b/packages/util/src/bi/toU8a.spec.ts
index 1d07cc2ca..481de69bc 100644
--- a/packages/util/src/bi/toU8a.spec.ts
+++ b/packages/util/src/bi/toU8a.spec.ts
@@ -4,12 +4,29 @@
///
import { arrayRange } from '../array/index.js';
+import { TESTS } from '../bn/toU8a.spec.js';
import { perf } from '../test/index.js';
import { nToU8a } from './index.js';
const ptest = arrayRange(65536).map((v) => [v]);
describe('nToU8a', (): void => {
+ describe('conversion tests', (): void => {
+ for (let i = 0, count = TESTS.length; i < count; i++) {
+ const [isLe, isNegative, numarr, strval] = TESTS[i];
+ 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)
@@ -40,23 +57,5 @@ describe('nToU8a', (): void => {
).toEqual(new Uint8Array([0x56, 0x34, 0x12, 0x00]));
});
- it('converts negative numbers', (): void => {
- expect(
- nToU8a(-1234n, { isNegative: true })
- ).toEqual(new Uint8Array([46, 251]));
- });
-
- it('converts negative numbers (BE)', (): void => {
- expect(
- nToU8a(-1234n, { isLe: false, isNegative: true })
- ).toEqual(new Uint8Array([251, 46]));
- });
-
- it('converts negative numbers (bitLength)', (): void => {
- expect(
- nToU8a(-1234n, { bitLength: 32, isNegative: true })
- ).toEqual(new Uint8Array([46, 251, 255, 255]));
- });
-
perf('nToU8a', 250000, ptest, nToU8a);
});
diff --git a/packages/util/src/bi/toU8a.ts b/packages/util/src/bi/toU8a.ts
index 6055d37a9..fd5e7173f 100644
--- a/packages/util/src/bi/toU8a.ts
+++ b/packages/util/src/bi/toU8a.ts
@@ -14,15 +14,16 @@ const NEG_MASK = BigInt(0xff);
function toU8a (value: bigint, isLe: boolean, isNegative: boolean): Uint8Array {
const arr: number[] = [];
+ const withSigned = isNegative && (value < _0n);
- if (isNegative) {
+ if (withSigned) {
value = (value + _1n) * -_1n;
}
while (value !== _0n) {
const mod = value % DIV;
const val = Number(
- isNegative
+ withSigned
? mod ^ NEG_MASK
: mod
);
diff --git a/packages/util/src/bn/toU8a.spec.ts b/packages/util/src/bn/toU8a.spec.ts
index 2d4882a47..63a31fdaf 100644
--- a/packages/util/src/bn/toU8a.spec.ts
+++ b/packages/util/src/bn/toU8a.spec.ts
@@ -9,7 +9,63 @@ import { BN, bnToU8a } from './index.js';
const ptest = arrayRange(65536).map((v) => [v]);
+// 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']
+];
+
describe('bnToU8a', (): void => {
+ describe('conversion tests', (): void => {
+ for (let i = 0, count = TESTS.length; i < count; i++) {
+ const [isLe, isNegative, numarr, strval] = TESTS[i];
+ 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));
+ });
+ }
+ });
+
it('converts null values to 0x00', (): void => {
expect(
bnToU8a(null)
@@ -40,67 +96,5 @@ describe('bnToU8a', (): void => {
).toEqual(new Uint8Array([0x56, 0x34, 0x12, 0x00]));
});
- describe('signed', (): void => {
- it('converts negative numbers (BE)', (): void => {
- expect(
- bnToU8a(new BN(-1234), { isLe: false, isNegative: true })
- ).toEqual(new Uint8Array([251, 46]));
- });
-
- it('converts negative numbers (LE, i8)', (): void => {
- expect(
- bnToU8a(new BN(-12), { isNegative: true })
- ).toEqual(new Uint8Array([244]));
- });
-
- it('converts negative numbers (LE, i16)', (): void => {
- expect(
- bnToU8a(new BN(-1234), { isNegative: true })
- ).toEqual(new Uint8Array([46, 251]));
- });
-
- it('converts negative numbers (LE, i24)', (): void => {
- expect(
- bnToU8a(new BN(-123456), { isNegative: true })
- ).toEqual(new Uint8Array([192, 29, 254]));
- });
-
- it('converts negative numbers (LE, i32)', (): void => {
- expect(
- bnToU8a(new BN(-123456789), { isNegative: true })
- ).toEqual(new Uint8Array([235, 50, 164, 248]));
- });
-
- it('converts negative numbers (LE, i40)', (): void => {
- expect(
- bnToU8a(new BN(-5678999999), { isNegative: true })
- ).toEqual(new Uint8Array([65, 86, 129, 173, 254]));
- });
-
- it('converts negative numbers (LE, i48)', (): void => {
- expect(
- bnToU8a(new BN(-9999999999999), { isNegative: true })
- ).toEqual(new Uint8Array([1, 96, 141, 177, 231, 246]));
- });
-
- it('converts negative numbers (LE, i64)', (): void => {
- expect(
- bnToU8a(new BN('-999999999999999999'), { isNegative: true })
- ).toEqual(new Uint8Array([1, 0, 156, 88, 76, 73, 31, 242]));
- });
-
- it('converts negative numbers (LE, bitLength)', (): void => {
- expect(
- bnToU8a(new BN(-1234), { bitLength: 32, isNegative: true })
- ).toEqual(new Uint8Array([46, 251, 255, 255]));
- });
-
- it('converts negative numbers (LE, bitLength, check)', (): void => {
- expect(
- bnToU8a(new BN(-123456), { bitLength: 32, isNegative: true })
- ).toEqual(new Uint8Array([192, 29, 254, 255]));
- });
- });
-
perf('bnToU8a', 250000, ptest, bnToU8a);
});
diff --git a/packages/util/src/u8a/toBigInt.spec.ts b/packages/util/src/u8a/toBigInt.spec.ts
index e82213de6..8249e51a2 100644
--- a/packages/util/src/u8a/toBigInt.spec.ts
+++ b/packages/util/src/u8a/toBigInt.spec.ts
@@ -3,6 +3,7 @@
///
+import { TESTS } from '../bn/toU8a.spec.js';
import { perf } from '../test/index.js';
import { u8aToBigInt } from './index.js';
@@ -16,238 +17,41 @@ describe('u8aToBigInt', (): void => {
).toBe('3412');
});
- describe('LE', (): void => {
- describe('unsigned', (): void => {
- it('converts values (u8)', (): void => {
+ describe('conversion tests', (): void => {
+ for (let i = 0, count = TESTS.length; i < count; i++) {
+ const [isLe, isNegative, numarr, strval] = TESTS[i];
+
+ it(`${i}: creates ${strval} (bitLength=${numarr.length * 8}, isLe=${isLe}, isNegative=${isNegative})`, (): void => {
expect(
u8aToBigInt(
- new Uint8Array([0x12]),
- { isLe: true }
- ).toString(16)
- ).toBe('12');
- });
-
- it('converts values (u16)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0x12, 0x34]),
- { isLe: true }
- ).toString(16)
- ).toBe('3412');
- });
-
- it('converts values (u24)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0x12, 0x34, 0x56]),
- { isLe: true }
- ).toString(16)
- ).toBe('563412');
- });
-
- it('converts values (u32)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0x12, 0x34, 0x56, 0x78]),
- { isLe: true }
- ).toString(16)
- ).toBe('78563412');
- });
-
- it('converts values (i32)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0xf2, 0x34, 0x56, 0x78]),
- { isLe: false, isNegative: true }
- ).toString(16)
- ).toBe('-dcba988');
- });
-
- it('converts values (u40)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a]),
- { isLe: true }
- ).toString(16)
- ).toBe('9a78563412');
- });
-
- it('converts values (u48)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc]),
- { isLe: true }
- ).toString(16)
- ).toBe('bc9a78563412');
- });
-
- it('converts values (u128)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78]),
- { isLe: true }
- ).toString(16)
- ).toBe('78563412785634127856341278563412');
+ new Uint8Array(numarr),
+ { isLe, isNegative }
+ ).toString()
+ ).toBe(strval);
});
+ }
+ });
+ describe('length tests', (): void => {
+ [true, false].forEach((isLe) => {
for (let i = 1; i < 32; i++) {
const tu8a = [0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78];
const tstr = tu8a.map((n) => n.toString(16));
- it(`converts values with length ${i}`, (): void => {
+ it(`converts values with bitLength=${i * 8}, isLe=${isLe}`, (): void => {
expect(
u8aToBigInt(
new Uint8Array(tu8a.slice(0, i)),
- { isLe: true }
+ { isLe }
).toString(16)
- ).toBe(tstr.slice(0, i).reverse().join(''));
+ ).toBe(
+ isLe
+ ? tstr.slice(0, i).reverse().join('')
+ : tstr.slice(0, i).join('')
+ );
});
}
});
-
- describe('signed', (): void => {
- it('converts values (i8)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([244]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-12');
- });
-
- it('converts values (i16)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([46, 251]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-1234');
- });
-
- it('converts values (i24)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([192, 29, 254]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-123456');
- });
-
- it('converts values (i32)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([235, 50, 164, 248]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-123456789');
- });
-
- it('converts values (i40)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([65, 86, 129, 173, 254]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-5678999999');
- });
-
- it('converts values (i48)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([1, 96, 141, 177, 231, 246]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-9999999999999');
- });
-
- it('converts values (i64)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([1, 0, 156, 88, 76, 73, 31, 242]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-999999999999999999');
- });
- });
- });
-
- describe('BE', (): void => {
- it('converts values (u8)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0x12]),
- { isLe: false }
- ).toString(16)
- ).toBe('12');
- });
-
- it('converts values (u16)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0x12, 0x34]),
- { isLe: false }
- ).toString(16)
- ).toBe('1234');
- });
-
- it('converts values (u24)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0x12, 0x34, 0x56]),
- { isLe: false }
- ).toString(16)
- ).toBe('123456');
- });
-
- it('converts values (u32)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0x12, 0x34, 0x56, 0x78]),
- { isLe: false }
- ).toString(16)
- ).toBe('12345678');
- });
-
- it('converts values (u40)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a]),
- { isLe: false }
- ).toString(16)
- ).toBe('123456789a');
- });
-
- it('converts values (u48)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc]),
- { isLe: false }
- ).toString(16)
- ).toBe('123456789abc');
- });
-
- it('converts values (u128)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78]),
- { isLe: false }
- ).toString(16)
- ).toBe('12345678123456781234567812345678');
- });
-
- for (let i = 1; i < 32; i++) {
- const tu8a = [0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78];
- const tstr = tu8a.map((n) => n.toString(16));
-
- it(`converts values with length ${i}`, (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array(tu8a.slice(0, i)),
- { isLe: false }
- ).toString(16)
- ).toBe(tstr.slice(0, i).join(''));
- });
- }
});
describe('empty creation', (): void => {
@@ -288,26 +92,6 @@ describe('u8aToBigInt', (): void => {
});
});
- describe('negative', (): void => {
- it('handles negative numbers (little-endian)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([46, 251]),
- { isLe: true, isNegative: true }
- )
- ).toBe(-1234n);
- });
-
- it('handles negative numbers (big-endian)', (): void => {
- expect(
- u8aToBigInt(
- new Uint8Array([251, 46]),
- { isLe: false, isNegative: true }
- )
- ).toBe(-1234n);
- });
- });
-
it('handles overflows correctly (little-endian)', (): void => {
expect(
u8aToBigInt(
@@ -317,12 +101,8 @@ describe('u8aToBigInt', (): void => {
).toBe(256n);
});
- perf('u8aToBigInt (i32)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], (v: Uint8Array) => u8aToBigInt(v, { isNegative: true }));
-
+ perf('u8aToBigInt (i32)', 750_000, [[new Uint8Array([0x9c, 0x9c, 0x9c, 0x9c])]], (v: Uint8Array) => u8aToBigInt(v, { isNegative: true }));
perf('u8aToBigInt (u32)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], u8aToBigInt);
perf('u8aToBigInt (u64)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c])]], u8aToBigInt);
perf('u8aToBigInt (u128)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c])]], u8aToBigInt);
-
- // perf('BigInt (constructor)', 1_000_000, [[12345678]], (v: number) => BigInt(v).toString());
- // perf('BigInt (constructor -> string)', 1_000_000, [[12345678]], (v: number) => BigInt(v).toString());
});
diff --git a/packages/util/src/u8a/toBigInt.ts b/packages/util/src/u8a/toBigInt.ts
index f25ea860e..fcccd5549 100644
--- a/packages/util/src/u8a/toBigInt.ts
+++ b/packages/util/src/u8a/toBigInt.ts
@@ -16,13 +16,14 @@ const U64_MAX = BigInt('0x10000000000000000');
* @summary Creates a BigInt from a Uint8Array object.
*/
export function u8aToBigInt (value: Uint8Array, { isLe = true, isNegative = false }: ToBnOptions = {}): bigint {
- // BE is not the optimal path - LE is the SCALE default
+ // slice + reverse is expensive, however SCALE is LE by default so this is the path
+ // we are most interested in (the BE is added for the sake of being comprehensive)
const u8a = isLe
? value
: value.slice().reverse();
const count = u8a.length;
- if (isNegative) {
+ if (isNegative && count && (u8a[count - 1] & 0x80)) {
switch (count) {
case 0:
return BigInt(0);
diff --git a/packages/util/src/u8a/toBn.spec.ts b/packages/util/src/u8a/toBn.spec.ts
index 553d3fe13..36f9f113b 100644
--- a/packages/util/src/u8a/toBn.spec.ts
+++ b/packages/util/src/u8a/toBn.spec.ts
@@ -3,7 +3,7 @@
///
-// import { BN } from '../bn/index.js';
+import { TESTS } from '../bn/toU8a.spec.js';
import { perf } from '../test/index.js';
import { u8aToBn } from './index.js';
@@ -17,238 +17,41 @@ describe('u8aToBn', (): void => {
).toBe('3412');
});
- describe('LE', (): void => {
- describe('unsigned', (): void => {
- it('converts values (u8)', (): void => {
+ describe('conversion tests', (): void => {
+ for (let i = 0, count = TESTS.length; i < count; i++) {
+ const [isLe, isNegative, numarr, strval] = TESTS[i];
+
+ it(`${i}: creates ${strval} (bitLength=${numarr.length * 8}, isLe=${isLe}, isNegative=${isNegative})`, (): void => {
expect(
u8aToBn(
- new Uint8Array([0x12]),
- { isLe: true }
- ).toString(16)
- ).toBe('12');
- });
-
- it('converts values (u16)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0x12, 0x34]),
- { isLe: true }
- ).toString(16)
- ).toBe('3412');
- });
-
- it('converts values (u24)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0x12, 0x34, 0x56]),
- { isLe: true }
- ).toString(16)
- ).toBe('563412');
- });
-
- it('converts values (u32)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0x12, 0x34, 0x56, 0x78]),
- { isLe: true }
- ).toString(16)
- ).toBe('78563412');
- });
-
- it('converts values (u40)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a]),
- { isLe: true }
- ).toString(16)
- ).toBe('9a78563412');
- });
-
- it('converts values (u48)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc]),
- { isLe: true }
- ).toString(16)
- ).toBe('bc9a78563412');
- });
-
- it('converts values (u128)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78]),
- { isLe: true }
- ).toString(16)
- ).toBe('78563412785634127856341278563412');
+ new Uint8Array(numarr),
+ { isLe, isNegative }
+ ).toString()
+ ).toBe(strval);
});
+ }
+ });
+ describe('length tests', (): void => {
+ [true, false].forEach((isLe) => {
for (let i = 1; i < 32; i++) {
const tu8a = [0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78];
const tstr = tu8a.map((n) => n.toString(16));
- it(`converts values with length ${i}`, (): void => {
+ it(`converts values with bitLength=${i * 8}, isLe=${isLe}`, (): void => {
expect(
u8aToBn(
new Uint8Array(tu8a.slice(0, i)),
- { isLe: true }
+ { isLe }
).toString(16)
- ).toBe(tstr.slice(0, i).reverse().join(''));
+ ).toBe(
+ isLe
+ ? tstr.slice(0, i).reverse().join('')
+ : tstr.slice(0, i).join('')
+ );
});
}
});
-
- describe('signed', (): void => {
- it('converts values (i8)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([244]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-12');
- });
-
- it('converts values (i16)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([46, 251]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-1234');
- });
-
- it('converts values (i24)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([192, 29, 254]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-123456');
- });
-
- it('converts values (i32)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([235, 50, 164, 248]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-123456789');
- });
-
- it('converts values (i40)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([65, 86, 129, 173, 254]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-5678999999');
- });
-
- it('converts values (i48)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([1, 96, 141, 177, 231, 246]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-9999999999999');
- });
-
- it('converts values (i64)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([1, 0, 156, 88, 76, 73, 31, 242]),
- { isLe: true, isNegative: true }
- ).toString()
- ).toBe('-999999999999999999');
- });
- });
- });
-
- describe('BE', (): void => {
- it('converts values (u8)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0x12]),
- { isLe: false }
- ).toString(16)
- ).toBe('12');
- });
-
- it('converts values (u16)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0x12, 0x34]),
- { isLe: false }
- ).toString(16)
- ).toBe('1234');
- });
-
- it('converts values (u24)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0x12, 0x34, 0x56]),
- { isLe: false }
- ).toString(16)
- ).toBe('123456');
- });
-
- it('converts values (u32)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0x12, 0x34, 0x56, 0x78]),
- { isLe: false }
- ).toString(16)
- ).toBe('12345678');
- });
-
- it('converts values (i32)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0xf2, 0x34, 0x56, 0x78]),
- { isLe: false, isNegative: true }
- ).toString(16)
- ).toBe('-dcba988');
- });
-
- it('converts values (u40)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a]),
- { isLe: false }
- ).toString(16)
- ).toBe('123456789a');
- });
-
- it('converts values (u48)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc]),
- { isLe: false }
- ).toString(16)
- ).toBe('123456789abc');
- });
-
- it('converts values (u128)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78]),
- { isLe: false }
- ).toString(16)
- ).toBe('12345678123456781234567812345678');
- });
-
- for (let i = 1; i < 32; i++) {
- const tu8a = [0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78];
- const tstr = tu8a.map((n) => n.toString(16));
-
- it(`converts values with length ${i}`, (): void => {
- expect(
- u8aToBn(
- new Uint8Array(tu8a.slice(0, i)),
- { isLe: false }
- ).toString(16)
- ).toBe(tstr.slice(0, i).join(''));
- });
- }
});
describe('empty creation', (): void => {
@@ -289,26 +92,6 @@ describe('u8aToBn', (): void => {
});
});
- describe('negative', (): void => {
- it('handles negative numbers (little-endian)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([46, 251]),
- { isLe: true, isNegative: true }
- ).toNumber()
- ).toBe(-1234);
- });
-
- it('handles negative numbers (big-endian)', (): void => {
- expect(
- u8aToBn(
- new Uint8Array([251, 46]),
- { isLe: false, isNegative: true }
- ).toNumber()
- ).toBe(-1234);
- });
- });
-
it('handles overflows correctly (little-endian)', (): void => {
expect(
u8aToBn(
@@ -318,12 +101,8 @@ describe('u8aToBn', (): void => {
).toBe(256);
});
- perf('u8aToBn (i32)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], (v: Uint8Array) => u8aToBn(v, { isNegative: true }));
-
+ perf('u8aToBn (i32)', 750_000, [[new Uint8Array([0x9c, 0x9c, 0x9c, 0x9c])]], (v: Uint8Array) => u8aToBn(v, { isNegative: true }));
perf('u8aToBn (u32)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], u8aToBn);
perf('u8aToBn (u64)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c])]], u8aToBn);
perf('u8aToBn (u128)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c])]], u8aToBn);
-
- // perf('BN (constructor)', 1_000_000, [[12345678]], (v: number) => new BN(v));
- // perf('BN (constructor -> string)', 1_000_000, [[12345678]], (v: number) => new BN(v).toString());
});
diff --git a/packages/util/src/u8a/toBn.ts b/packages/util/src/u8a/toBn.ts
index ed09f239a..518d80fff 100644
--- a/packages/util/src/u8a/toBn.ts
+++ b/packages/util/src/u8a/toBn.ts
@@ -24,104 +24,77 @@ import { BN } from '../bn/bn.js';
* ```
*/
export function u8aToBn (value: Uint8Array, { isLe = true, isNegative = false }: ToBnOptions = {}): BN {
- const count = value.length;
+ // slice + reverse is expensive, however SCALE is LE by default so this is the path
+ // we are most interested in (the BE is added for the sake of being comprehensive)
+ const u8a = isLe
+ ? value
+ : value.slice().reverse();
+ const count = u8a.length;
// shortcut for <= u48 values - in this case the manual conversion
// here seems to be more efficient than passing the full array
- if (isNegative) {
- if (isLe) {
- // Most common case i{8, 16, 32} default LE SCALE-encoded
- // For <= 32, we also optimize the xor to a single op
- switch (count) {
- case 0:
- return new BN(0);
-
- case 1:
- return new BN(((value[0] ^ 0x0000_00ff) * -1) - 1);
-
- case 2:
- return new BN((((value[0] + (value[1] << 8)) ^ 0x0000_ffff) * -1) - 1);
-
- case 3:
- return new BN((((value[0] + (value[1] << 8) + (value[2] << 16)) ^ 0x00ff_ffff) * -1) - 1);
-
- case 4:
- // 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 new BN((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) * -1) - 1);
-
- case 5:
- return new BN(((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + ((value[4] ^ 0xff) * 0x1_00_00_00_00)) * -1) - 1);
-
- case 6:
- return new BN(((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + (((value[4] + (value[5] << 8)) ^ 0x0000_ffff) * 0x1_00_00_00_00)) * -1) - 1);
-
- default:
- return new BN(value, 'le').fromTwos(value.length * 8);
- }
- }
-
- if (count === 0) {
- return new BN(0);
- } else if (count > 6) {
- return new BN(value, 'be').fromTwos(value.length * 8);
- }
-
- let result = 0;
-
- for (let i = 0; i < count; i++) {
- result = (result * 0x1_00) + (value[i] ^ 0xff);
- }
-
- return new BN((result * -1) - 1);
- }
-
- if (isLe) {
- // Most common case - u{8, 16, 32} default LE SCALE-encoded
- //
- // There are some slight benefits in unrolling this specific loop,
- // however it comes with diminishing returns since here the actual
- // `new BN` does seem to take up the bulk of the time
+ if (isNegative && count && (u8a[count - 1] & 0x80)) {
+ // Most common case i{8, 16, 32} default LE SCALE-encoded
+ // For <= 32, we also optimize the xor to a single op
switch (count) {
case 0:
return new BN(0);
case 1:
- return new BN(value[0]);
+ return new BN(((u8a[0] ^ 0x0000_00ff) * -1) - 1);
case 2:
- return new BN(value[0] + (value[1] << 8));
+ return new BN((((u8a[0] + (u8a[1] << 8)) ^ 0x0000_ffff) * -1) - 1);
case 3:
- return new BN(value[0] + (value[1] << 8) + (value[2] << 16));
+ return new BN((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16)) ^ 0x00ff_ffff) * -1) - 1);
case 4:
// 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 new BN(value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00));
+ return new BN((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) * -1) - 1);
case 5:
- return new BN(value[0] + (value[1] << 8) + (value[2] << 16) + ((value[3] + (value[4] << 8)) * 0x1_00_00_00));
+ return new BN(((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + ((u8a[4] ^ 0xff) * 0x1_00_00_00_00)) * -1) - 1);
case 6:
- return new BN(value[0] + (value[1] << 8) + (value[2] << 16) + ((value[3] + (value[4] << 8) + (value[5] << 16)) * 0x1_00_00_00));
+ return new BN(((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + (((u8a[4] + (u8a[5] << 8)) ^ 0x0000_ffff) * 0x1_00_00_00_00)) * -1) - 1);
default:
- return new BN(value, 'le');
+ return new BN(u8a, 'le').fromTwos(count * 8);
}
}
- if (count === 0) {
- return new BN(0);
- } else if (count > 6) {
- return new BN(value, 'be');
+ // Most common case - u{8, 16, 32} default LE SCALE-encoded
+ //
+ // There are some slight benefits in unrolling this specific loop,
+ // however it comes with diminishing returns since here the actual
+ // `new BN` does seem to take up the bulk of the time
+ switch (count) {
+ case 0:
+ return new BN(0);
+
+ case 1:
+ return new BN(u8a[0]);
+
+ case 2:
+ return new BN(u8a[0] + (u8a[1] << 8));
+
+ case 3:
+ return new BN(u8a[0] + (u8a[1] << 8) + (u8a[2] << 16));
+
+ case 4:
+ // 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 new BN(u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00));
+
+ case 5:
+ return new BN(u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + ((u8a[3] + (u8a[4] << 8)) * 0x1_00_00_00));
+
+ case 6:
+ return new BN(u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + ((u8a[3] + (u8a[4] << 8) + (u8a[5] << 16)) * 0x1_00_00_00));
+
+ default:
+ return new BN(u8a, 'le');
}
-
- let result = 0;
-
- for (let i = 0; i < count; i++) {
- result = (result * 0x1_00) + value[i];
- }
-
- return new BN(result);
}
diff --git a/packages/util/src/u8a/toNumber.spec.ts b/packages/util/src/u8a/toNumber.spec.ts
index 9946245b4..425f50c41 100644
--- a/packages/util/src/u8a/toNumber.spec.ts
+++ b/packages/util/src/u8a/toNumber.spec.ts
@@ -3,114 +3,26 @@
///
+import { TESTS } from '../bn/toU8a.spec.js';
import { perf } from '../test/index.js';
import { u8aToNumber } from './index.js';
+const TESTS_NUM = TESTS.filter(([,, numarr]) => numarr.length <= 6);
+
describe('u8aToNumber', (): void => {
- describe('unsigned', (): void => {
- it('converts values (u8)', (): void => {
- expect(
- u8aToNumber(
- new Uint8Array([0x12])
- )
- ).toBe(18);
- });
+ describe('conversion tests', (): void => {
+ for (let i = 0, count = TESTS_NUM.length; i < count; i++) {
+ const [isLe, isNegative, numarr, strval] = TESTS_NUM[i];
- it('converts values (u16)', (): void => {
- expect(
- u8aToNumber(
- new Uint8Array([0x12, 0x34])
- )
- ).toBe(13330);
- });
-
- it('converts values (u24)', (): void => {
- expect(
- u8aToNumber(
- new Uint8Array([0x12, 0x34, 0x56])
- )
- ).toBe(5649426);
- });
-
- it('converts values (u32)', (): void => {
- expect(
- u8aToNumber(
- new Uint8Array([0x12, 0x34, 0x56, 0x78])
- )
- ).toBe(2018915346);
- });
-
- it('converts values (u40)', (): void => {
- expect(
- u8aToNumber(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a])
- )
- ).toBe(663443878930);
- });
-
- it('converts values (u48)', (): void => {
- expect(
- u8aToNumber(
- new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc])
- )
- ).toBe(207371629900818);
- });
- });
-
- describe('signed', (): void => {
- it('converts values (i8)', (): void => {
- expect(
- u8aToNumber(
- new Uint8Array([244]),
- { isNegative: true }
- )
- ).toBe(-12);
- });
-
- it('converts values (i16)', (): void => {
- expect(
- u8aToNumber(
- new Uint8Array([46, 251]),
- { isNegative: true }
- )
- ).toBe(-1234);
- });
-
- it('converts values (i24)', (): void => {
- expect(
- u8aToNumber(
- new Uint8Array([192, 29, 254]),
- { isNegative: true }
- )
- ).toBe(-123456);
- });
-
- it('converts values (i32)', (): void => {
- expect(
- u8aToNumber(
- new Uint8Array([235, 50, 164, 248]),
- { isNegative: true }
- )
- ).toBe(-123456789);
- });
-
- it('converts values (i40)', (): void => {
- expect(
- u8aToNumber(
- new Uint8Array([65, 86, 129, 173, 254]),
- { isNegative: true }
- )
- ).toBe(-5678999999);
- });
-
- it('converts values (i48)', (): void => {
- expect(
- u8aToNumber(
- new Uint8Array([1, 96, 141, 177, 231, 246]),
- { isNegative: true }
- )
- ).toBe(-9999999999999);
- });
+ it(`${i}: creates ${strval} (bitLength=${numarr.length * 8}, isLe=${isLe}, isNegative=${isNegative})`, (): void => {
+ expect(
+ u8aToNumber(
+ new Uint8Array(numarr),
+ { isLe, isNegative }
+ ).toString()
+ ).toBe(strval);
+ });
+ }
});
describe('empty creation', (): void => {
@@ -132,6 +44,6 @@ describe('u8aToNumber', (): void => {
});
});
- perf('u8aToNumber (i32)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], (v: Uint8Array) => u8aToNumber(v, { isNegative: true }));
+ perf('u8aToNumber (i32)', 750_000, [[new Uint8Array([0x9c, 0x9c, 0x9c, 0x9c])]], (v: Uint8Array) => u8aToNumber(v, { isNegative: true }));
perf('u8aToNumber (u32)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], u8aToNumber);
});
diff --git a/packages/util/src/u8a/toNumber.ts b/packages/util/src/u8a/toNumber.ts
index f8b48fbc3..b70f314b6 100644
--- a/packages/util/src/u8a/toNumber.ts
+++ b/packages/util/src/u8a/toNumber.ts
@@ -1,44 +1,48 @@
// Copyright 2017-2023 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
-interface ToNumberOptions {
- /**
- * @description Number is signed, apply two's complement
- */
- isNegative?: boolean;
-}
+import type { ToBnOptions } from '../types.js';
/**
* @name u8aToNumber
- * @summary Creates a number from a Uint8Array object. This only operates on LE values as used in SCALE.
+ * @summary Creates a number from a Uint8Array object.
*/
-export function u8aToNumber (value: Uint8Array, { isNegative = false }: ToNumberOptions = {}): number {
- const count = value.length;
+export function u8aToNumber (value: Uint8Array, { isLe = true, isNegative = false }: ToBnOptions = {}): number {
+ // slice + reverse is expensive, however SCALE is LE by default so this is the path
+ // we are most interested in (the BE is added for the sake of being comprehensive)
+ const u8a = isLe
+ ? value
+ : value.slice().reverse();
+ const count = u8a.length;
- if (isNegative) {
+ // When the value is a i{8, 16, 24, 32, 40, 40} values and the top-most bit
+ // indicates a signed value, we use a two's complement conversion. If one of these
+ // flags are not set, we just do a normal unsigned conversion (the same shortcut
+ // applies in both the u8aTo{BigInt, Bn} conversions as well)
+ if (isNegative && count && (u8a[count - 1] & 0x80)) {
switch (count) {
case 0:
return 0;
case 1:
- return (((value[0] ^ 0x0000_00ff) * -1) - 1);
+ return (((u8a[0] ^ 0x0000_00ff) * -1) - 1);
case 2:
- return ((((value[0] + (value[1] << 8)) ^ 0x0000_ffff) * -1) - 1);
+ return ((((u8a[0] + (u8a[1] << 8)) ^ 0x0000_ffff) * -1) - 1);
case 3:
- return ((((value[0] + (value[1] << 8) + (value[2] << 16)) ^ 0x00ff_ffff) * -1) - 1);
+ return ((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16)) ^ 0x00ff_ffff) * -1) - 1);
case 4:
// 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 ((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) * -1) - 1);
+ return ((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) * -1) - 1);
case 5:
- return (((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + ((value[4] ^ 0xff) * 0x1_00_00_00_00)) * -1) - 1);
+ return (((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + ((u8a[4] ^ 0xff) * 0x1_00_00_00_00)) * -1) - 1);
case 6:
- return (((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + (((value[4] + (value[5] << 8)) ^ 0x0000_ffff) * 0x1_00_00_00_00)) * -1) - 1);
+ return (((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + (((u8a[4] + (u8a[5] << 8)) ^ 0x0000_ffff) * 0x1_00_00_00_00)) * -1) - 1);
default:
throw new Error('Value more than 48-bits cannot be reliably converted');
@@ -50,24 +54,24 @@ export function u8aToNumber (value: Uint8Array, { isNegative = false }: ToNumber
return 0;
case 1:
- return value[0];
+ return u8a[0];
case 2:
- return value[0] + (value[1] << 8);
+ return u8a[0] + (u8a[1] << 8);
case 3:
- return value[0] + (value[1] << 8) + (value[2] << 16);
+ return u8a[0] + (u8a[1] << 8) + (u8a[2] << 16);
case 4:
// 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 value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00);
+ return u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00);
case 5:
- return value[0] + (value[1] << 8) + (value[2] << 16) + ((value[3] + (value[4] << 8)) * 0x1_00_00_00);
+ return u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + ((u8a[3] + (u8a[4] << 8)) * 0x1_00_00_00);
case 6:
- return value[0] + (value[1] << 8) + (value[2] << 16) + ((value[3] + (value[4] << 8) + (value[5] << 16)) * 0x1_00_00_00);
+ return u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + ((u8a[3] + (u8a[4] << 8) + (u8a[5] << 16)) * 0x1_00_00_00);
default:
throw new Error('Value more than 48-bits cannot be reliably converted');