Check sign flag for i{8, 16, 32, ...} conversions (#1867)
* Check sign flag for i{8, 16, 32, ...} conversions
* Ensure negative path is tested on i32 perf
* CHANGELOG
* Comment check on top-most signed bit
* toBn test array
* toNumber uses exposed toBn tests
* Re-use tests in toBn
* Re-use expectations inside bnToU8a
* Remove explicit BE (swap to non-optimal reverse)
* Adjust
* Remove defunct "only BE" comment
* typo
* Use bnToU8a tests in biToU8a
* Flatten tests
* Additional tests
* Additional tests
This commit is contained in:
@@ -4,12 +4,29 @@
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
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());
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
// 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());
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -3,114 +3,26 @@
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user