Motivation
bigint
s were a great addition to ES, but they often need to be converted to/from other datatypes for transport or integration with legacy systems. At the moment, every user has to re-implement those methods manually, some of which are easy to get wrong (like twos-complement encoding).
Proposal
-
Convert a
bigint
to aUint8Array
of the given number ofbits
.BigInt.toUint8Array(bits: number, signed: boolean = false)
- BigEndian encoding order.
- If the value cannot fit into the specified number of
bits
, the function MUST throw anError
. - If the value is negative, it should be encoded using two's complement.
- If the value is negative and
signed == false
, the function MUST throw anError
-
Convert a
UInt8Array
into abigint
.BigInt(bytes: Uint8Array, signed: boolean = false)
- Interpret the bytes as big endian.
- If
signed
is true then the bytes should be interpreted as two's complement.
-
Convert a
bigint
into a baseradix
string using two's complement for negative numbers and doing sign extension out tobits
.BigInt.toString(radix: number, bits: number, signed: boolean = false)
- If the value is negative and
signed == false
, then the function MUST throw anError
.
- If the value is negative and
-
Convert a
string
into abigint
, interpreting it as a two's complement valueBigInt(value: string, bits?: number, signed: boolean = false)
- If the string is more than
bits / 8
characters long after processing the leading0x
radix indicator, then the function MUST throw anError
.
- If the string is more than
Test Cases
(0n).toUint8Array(8, true) // [0x00]
(0n).toUint8Array(8, false) // [0x00]
(0n).toUint8Array(8) // [0x00]
(0n).toUint8Array(16) // [0x00, 0x00]
(1n).toUint8Array(8, true) // [0x01]
(127n).toUint8Array(8, true) // [0x7f]
(128n).toUint8Array(8, true) // error
(128n).toUint8Array(16, true) // [0x00, 0x80]
(128n).toUint8Array(8, false) // [0x80]
(128n).toUint8Array(8) // [0x80]
(255n).toUint8Array(8, false) // [0xff]
(256n).toUint8Array(8, false) // error
(256n).toUint8Array(16, false) // [0x01, 0x00]
(-129).toUint8Array(8, true) // error
(-129).toUint8Array(16, true) // [0xff, 0x7f]
(-128).toUint8Array(8, true) // [0x80]
(-1n).toUint8Array(8, true) // [0xff]
(-1n).toUint8Array(8, false) // error
BigInt(new Uint8Array([0x80])) // 128n
BigInt(new Uint8Array([0x80]), false) // 128n
BigInt(new Uint8Array([0x80]), true) // -128n
(0n).toString(16, 0) // ''
(0n).toString(16, 8) // '00'
(0n).toString(16, 16) // '0000'
(128n).toString(16, 8, true) // error
(128n).toString(16, 8, false) // '80'
(-128n).toString(16, 8, false) // error
(-128n).toString(16, 8, true) // '80'
(-128n).toString(16, 16, true) // 'ff80'
BigInt('0x80') // 128n
BigInt('0x80', 8) // 128n
BigInt('0x80', 8, false) // 128n
BigInt('0x80', 8, true) // -128n
BigInt('0x80', 16, true) // 128n
BigInt('0xff80', 16, true) // -128n
Considerations
Array instead of Uint8Array:
Array<number>
may be preferable to Uint8Array
as it is a "simpler" type, and thus more likely to be usable. Plus, it is trivial to convert an Array<number>
into a Uint8Array
if necessary. Uint8Array
is more commonly used for data buffers and transport, hence why I went with that, but if others feel strongly about Array<number>
I don't see any problem there.
Endianness
Big Endian is often called "Network Byte Order" due to how common it is for network payloads. Since JavaScript is used largely for "internet" applications, it makes sense to choose that byte order, even though it may be slightly less performant on little endian systems.
Two's Complement
This seems to have won out over ones' complement almost across the board. I don't really see any reason to pick ones' complement over the defacto standard in this case.
FromString length for bits?
We could infer the bits
parameter for BigInt
from string. This way we would only need a single boolean
parameter and it would be up to the caller to provide a properly padded unsigned value, to ensure that the leading bit is not set. This means 0xff
would always decode as -1n
, and if you wanted 255n
then you would need to pass 0x00ff
. In general, I'm not a fan of inferring things like this because it is a vector for unexpected runtime errors, but I am open to being convinced it is a better API.