From e5373c35c8ce67e6d98d6c4a34e180340926b160 Mon Sep 17 00:00:00 2001 From: Jaco Date: Sat, 13 May 2023 10:51:46 +0300 Subject: [PATCH] Unroll u8aToBigInt loops (#1821) * Unroll u8aToBigInt loops * Bump dev * Rolling tests --- CHANGELOG.md | 1 + packages/util/src/u8a/toBigInt.spec.ts | 63 ++++++++++- packages/util/src/u8a/toBigInt.ts | 85 +++++++++++---- packages/util/src/u8a/toBn.spec.ts | 63 ++++++++++- packages/util/src/u8a/toBn.ts | 143 +++++++++++++------------ packages/util/src/u8a/toNumber.spec.ts | 4 +- packages/util/src/u8a/toNumber.ts | 24 ++--- 7 files changed, 269 insertions(+), 114 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d52b6c7215c..ff500b610b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Changes: - Align `u8aToBigInt` tests with `u8aToBn` +- Unroll loops on most-frequently used paths for `u8aToBigInt` ## 12.1.2 May 1, 2023 diff --git a/packages/util/src/u8a/toBigInt.spec.ts b/packages/util/src/u8a/toBigInt.spec.ts index 3585756c659..e82213de6a6 100644 --- a/packages/util/src/u8a/toBigInt.spec.ts +++ b/packages/util/src/u8a/toBigInt.spec.ts @@ -54,6 +54,15 @@ describe('u8aToBigInt', (): void => { ).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( @@ -71,6 +80,29 @@ describe('u8aToBigInt', (): void => { ).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'); + }); + + 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: true } + ).toString(16) + ).toBe(tstr.slice(0, i).reverse().join('')); + }); + } }); describe('signed', (): void => { @@ -193,6 +225,29 @@ describe('u8aToBigInt', (): void => { ).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 => { @@ -262,9 +317,11 @@ describe('u8aToBigInt', (): void => { ).toBe(256n); }); - perf('u8aToBigInt (u32)', 1_000_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], u8aToBigInt); - perf('u8aToBigInt (i32)', 1_000_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], (v: Uint8Array) => u8aToBigInt(v, { isNegative: true })); - perf('u8aToBigInt (u64)', 500_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c])]], u8aToBigInt); + perf('u8aToBigInt (i32)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], (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 07fd4650314..f25ea860e76 100644 --- a/packages/util/src/u8a/toBigInt.ts +++ b/packages/util/src/u8a/toBigInt.ts @@ -9,45 +9,90 @@ import { _1n } from '../bi/consts.js'; const U8_MAX = BigInt(256); const U16_MAX = BigInt(256 * 256); +const U64_MAX = BigInt('0x10000000000000000'); /** * @name u8aToBigInt * @summary Creates a BigInt from a Uint8Array object. */ export function u8aToBigInt (value: Uint8Array, { isLe = true, isNegative = false }: ToBnOptions = {}): bigint { - if (!value || !value.length) { - return BigInt(0); - } - + // BE is not the optimal path - LE is the SCALE default const u8a = isLe ? value - : value.reverse(); - const dvI = new DataView(u8a.buffer, u8a.byteOffset); + : value.slice().reverse(); const count = u8a.length; - const mod = count % 2; - let result = BigInt(0); - // This is mostly written for readability (with the single isNegative shortcut), - // as opposed to performance, e.g. `u8aToBn` does loop unrolling, etc. if (isNegative) { - for (let i = count - 2; i >= mod; i -= 2) { - result = (result * U16_MAX) + BigInt(dvI.getUint16(i, true) ^ 0xffff); + switch (count) { + case 0: + return BigInt(0); + + case 1: + return BigInt(((u8a[0] ^ 0x0000_00ff) * -1) - 1); + + case 2: + return BigInt((((u8a[0] + (u8a[1] << 8)) ^ 0x0000_ffff) * -1) - 1); + + case 4: + return BigInt((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) * -1) - 1); } - if (mod) { - result = (result * U8_MAX) + BigInt(u8a[0] ^ 0xff); + const dvI = new DataView(u8a.buffer, u8a.byteOffset); + + if (count === 8) { + return dvI.getBigInt64(0, true); } - } else { + + let result = BigInt(0); + const mod = count % 2; + for (let i = count - 2; i >= mod; i -= 2) { - result = (result * U16_MAX) + BigInt(dvI.getUint16(i, true)); + result = (result * U16_MAX) + BigInt(dvI.getUint16(i, true) ^ 0xffff); } if (mod) { - result = (result * U8_MAX) + BigInt(u8a[0]); + result = (result * U8_MAX) + BigInt(u8a[0] ^ 0xff); } + + return (result * -_1n) - _1n; + } + + switch (count) { + case 0: + return BigInt(0); + + case 1: + return BigInt(u8a[0]); + + case 2: + return BigInt(u8a[0] + (u8a[1] << 8)); + + case 4: + return BigInt(u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)); } - return isNegative - ? ((result * -_1n) - _1n) - : result; + const dvI = new DataView(u8a.buffer, u8a.byteOffset); + + switch (count) { + case 8: + return dvI.getBigUint64(0, true); + + case 16: + return (dvI.getBigUint64(8, true) * U64_MAX) + dvI.getBigUint64(0, true); + + default: { + let result = BigInt(0); + const mod = count % 2; + + for (let i = count - 2; i >= mod; i -= 2) { + result = (result * U16_MAX) + BigInt(dvI.getUint16(i, true)); + } + + if (mod) { + result = (result * U8_MAX) + BigInt(u8a[0]); + } + + return result; + } + } } diff --git a/packages/util/src/u8a/toBn.spec.ts b/packages/util/src/u8a/toBn.spec.ts index 73c6d616434..553d3fe13ae 100644 --- a/packages/util/src/u8a/toBn.spec.ts +++ b/packages/util/src/u8a/toBn.spec.ts @@ -72,6 +72,29 @@ describe('u8aToBn', (): void => { ).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'); + }); + + 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: true } + ).toString(16) + ).toBe(tstr.slice(0, i).reverse().join('')); + }); + } }); describe('signed', (): void => { @@ -177,6 +200,15 @@ describe('u8aToBn', (): void => { ).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( @@ -194,6 +226,29 @@ describe('u8aToBn', (): void => { ).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 => { @@ -263,9 +318,11 @@ describe('u8aToBn', (): void => { ).toBe(256); }); - perf('u8aToBn (u32)', 1_000_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], u8aToBn); - perf('u8aToBn (i32)', 1_000_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], (v: Uint8Array) => u8aToBn(v, { isNegative: true })); - perf('u8aToBn (u64)', 500_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c])]], u8aToBn); + perf('u8aToBn (i32)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], (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 84e8d5e818d..ed09f239a6f 100644 --- a/packages/util/src/u8a/toBn.ts +++ b/packages/util/src/u8a/toBn.ts @@ -28,95 +28,100 @@ export function u8aToBn (value: Uint8Array, { isLe = true, isNegative = false }: // shortcut for <= u48 values - in this case the manual conversion // here seems to be more efficient than passing the full array - if (count <= 6) { - if (isNegative) { - let result = 0; - - if (isLe) { - // Most common case i{8, 16, 32} default LE SCALE-encoded - // For <= 32, we also optimize the xor to a single op - // (see the comments around unrolling in the next section) - switch (count) { - case 0: - return new BN(0); - - case 1: - result = value[0] ^ 0x0000_00ff; - break; - - case 2: - result = (value[0] + (value[1] << 8)) ^ 0x0000_ffff; - break; - - case 3: - result = (value[0] + (value[1] << 8) + (value[2] << 16)) ^ 0x00ff_ffff; - break; - - 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 - result = (value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff; - break; - - case 5: - result = ((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + ((value[4] ^ 0xff) * 0x1_00_00_00_00); - break; - - default: // 6 - result = ((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); - break; - } - } else { - for (let i = 0; i < count; i++) { - result = (result * 0x1_00) + (value[i] ^ 0xff); - } - } - - return count - ? new BN((result * -1) - 1) - : new BN(0); - } else 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) { + 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]); + return new BN(((value[0] ^ 0x0000_00ff) * -1) - 1); case 2: - return new BN(value[0] + (value[1] << 8)); + 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)); + 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)); + 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] + (value[4] << 8)) * 0x1_00_00_00)); + 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); - default: // 6 - return new BN(value[0] + (value[1] << 8) + (value[2] << 16) + ((value[3] + (value[4] << 8) + (value[5] << 16)) * 0x1_00_00_00)); - } - } else { - let result = 0; + 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); - for (let i = 0; i < count; i++) { - result = (result * 0x1_00) + value[i]; + 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); + } - return new BN(result); + 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 + switch (count) { + case 0: + return new BN(0); + + case 1: + return new BN(value[0]); + + case 2: + return new BN(value[0] + (value[1] << 8)); + + case 3: + return new BN(value[0] + (value[1] << 8) + (value[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(value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)); + + case 5: + return new BN(value[0] + (value[1] << 8) + (value[2] << 16) + ((value[3] + (value[4] << 8)) * 0x1_00_00_00)); + + 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)); + + default: + return new BN(value, 'le'); + } + } + + if (count === 0) { + return new BN(0); + } else if (count > 6) { + return new BN(value, 'be'); + } + + let result = 0; + + for (let i = 0; i < count; i++) { + result = (result * 0x1_00) + value[i]; } - return isNegative - ? new BN(value, isLe ? 'le' : 'be').fromTwos(value.length * 8) - : new BN(value, isLe ? 'le' : 'be'); + return new BN(result); } diff --git a/packages/util/src/u8a/toNumber.spec.ts b/packages/util/src/u8a/toNumber.spec.ts index 538f9f04b9b..9946245b4e3 100644 --- a/packages/util/src/u8a/toNumber.spec.ts +++ b/packages/util/src/u8a/toNumber.spec.ts @@ -132,6 +132,6 @@ describe('u8aToNumber', (): void => { }); }); - perf('u8aToNumber (u32)', 1_000_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], u8aToNumber); - perf('u8aToNumber (i32)', 1_000_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], (v: Uint8Array) => u8aToNumber(v, { isNegative: true })); + perf('u8aToNumber (i32)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], (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 ba03d68dbe6..f8b48fbc349 100644 --- a/packages/util/src/u8a/toNumber.ts +++ b/packages/util/src/u8a/toNumber.ts @@ -10,49 +10,39 @@ interface ToNumberOptions { /** * @name u8aToNumber - * @summary Creates a number from a Uint8Array object. + * @summary Creates a number from a Uint8Array object. This only operates on LE values as used in SCALE. */ export function u8aToNumber (value: Uint8Array, { isNegative = false }: ToNumberOptions = {}): number { const count = value.length; if (isNegative) { - let result = 0; - switch (count) { case 0: return 0; case 1: - result = value[0] ^ 0x0000_00ff; - break; + return (((value[0] ^ 0x0000_00ff) * -1) - 1); case 2: - result = (value[0] + (value[1] << 8)) ^ 0x0000_ffff; - break; + return ((((value[0] + (value[1] << 8)) ^ 0x0000_ffff) * -1) - 1); case 3: - result = (value[0] + (value[1] << 8) + (value[2] << 16)) ^ 0x00ff_ffff; - break; + return ((((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 - result = (value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff; - break; + return ((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) * -1) - 1); case 5: - result = ((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + ((value[4] ^ 0xff) * 0x1_00_00_00_00); - break; + 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); case 6: - result = ((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); - break; + 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); default: throw new Error('Value more than 48-bits cannot be reliably converted'); } - - return (result * -1) - 1; } switch (count) {