-
Notifications
You must be signed in to change notification settings - Fork 57
Missing toByteArray #163
Comments
The BigInt proposal intentionally leaves extended functionality for future proposals (see https://github.com/tc39/proposal-bigint#left-for-future-proposals ). This sounds like a candidate for a possible future BigInt library proposal. (I don't know if or where any work has started yet on this.) |
As @jakobkummerow says, the library here is intentionally minimal. We don't have a particular place where we're collecting library proposals. For now, we can use this bug tracker. After the core of BigInt gets to Stage 4, we let's start getting more concrete about what we want to do here for a BigInt standards library. |
+1 on this. For node, I wrote https://github.com/no2chem/bigint-buffer --- it works in the browser too, relying on Buffer.from(val, 'hex') and the polyfill provided by webpack. But yeah, getting real use of BigInts is going to need conversion to/from UInt8Arrays. I'm surprised the proposal made it into stage 3 without it, because it seems like this conversions form a typical use case of BigInts. |
@no2chem It's great that you were able to do this kind of experimental development of libraries with BigInt. Let's continue developing in this mode for a while, and then consider what we want to standardize. |
@jakobkummerow There is no longer a section for When this gets considered in the future, please ensure that both signed and unsigned are supported. |
For now, these functions (TypeScript) should allow you to convert from Uint8Array to bigint and back using two's complement for signed numbers. If anyone is any good at mathing, please double-check my work! Consider this Unlicensed/CC0/public domain/MIT/Apache 2.0/FreeBSD (you choose) licensed for the purpose of using it wherever you want (I truly don't care what you do with it or whether you give any credit). These functions are written for readability, not for performance. There are definitely opportunities for optimizations if you feel like it. export function uint8ArrayToBigint(uint8Array: Uint8Array, numberOfBits: number): bigint {
if (numberOfBits % 8) throw new Error(`Only 8-bit increments are supported when (de)serializing a bigint.`)
const valueAsHexString = uint8ArrayToHexString(uint8Array)
return hexStringToBigint(valueAsHexString, numberOfBits)
}
export function hexStringToBigint(hexString: string, numberOfBits: number): bigint {
if (numberOfBits % 8) throw new Error(`Only 8-bit increments are supported when (de)serializing a bigint.`)
const unsignedInterpretation = BigInt(validateAndNormalizeHexString(hexString))
return twosComplement(unsignedInterpretation, numberOfBits)
}
export function bigintToUint8Array(value: bigint, numberOfBits: number): Uint8Array {
if (numberOfBits % 8) throw new Error(`Only 8-bit increments are supported when (de)serializing a bigint.`)
const valueAsHexString = bigintToHexString(value, numberOfBits)
return hexStringToUint8Array(valueAsHexString)
}
export function bigintToHexString(value: bigint, numberOfBits: number): string {
if (numberOfBits % 8) throw new Error(`Only 8-bit increments are supported when (de)serializing a bigint.`)
const valueToSerialize = twosComplement(value, numberOfBits)
return unsignedBigintToHexString(valueToSerialize, numberOfBits)
}
function validateAndNormalizeHexString(hex: string): string {
const match = new RegExp(`^(?:0x)?([a-fA-F0-9]*)$`).exec(hex)
if (match === null) throw new Error(`Expected a hex string encoded byte array with an optional '0x' prefix but received ${hex}`)
if (match.length % 2) throw new Error(`Hex string encoded byte array must be an even number of charcaters long.`)
return `0x${match[1]}`
}
function uint8ArrayToHexString(array: Uint8Array): string {
const hexStringFromByte = (byte: number): string => ('00' + byte.toString(16)).slice(-2)
const appendByteToString = (value: string, byte: number) => value + hexStringFromByte(byte)
return array.reduce(appendByteToString, '')
}
function hexStringToUint8Array(hex: string): Uint8Array {
const match = new RegExp(`^(?:0x)?([a-fA-F0-9]*)$`).exec(hex)
if (match === null) throw new Error(`Expected a hex string encoded byte array with an optional '0x' prefix but received ${hex}`)
if (match.length % 2) throw new Error(`Hex string encoded byte array must be an even number of charcaters long.`)
const normalized = match[1]
const byteLength = normalized.length / 2
const bytes = new Uint8Array(byteLength)
for (let i = 0; i < byteLength; ++i) {
bytes[i] = (Number.parseInt(`${normalized[i*2]}${normalized[i*2+1]}`, 16))
}
return bytes
}
function unsignedBigintToHexString(value: bigint, bits: number): string {
const byteSize = bits / 8
const hexStringLength = byteSize * 2
return ('0'.repeat(hexStringLength) + value.toString(16)).slice(-hexStringLength)
}
function twosComplement(value: bigint, numberOfBits: number): bigint {
const mask = 2n**(BigInt(numberOfBits) - 1n) - 1n
return (value & mask) - (value & ~mask)
} And here are some tests to show that my functions and tests have the same bugs. import { expect } from 'chai'
import { hexStringToBigint, bigintToHexString, uint8ArrayToBigint, bigintToUint8Array } from './index'
const testCases: Array<[bigint, string | Uint8Array]> = [
[0n, '0000000000000000000000000000000000000000000000000000000000000000'],
[1n, '0000000000000000000000000000000000000000000000000000000000000001'],
[2n, '0000000000000000000000000000000000000000000000000000000000000002'],
[57896044618658097711785492504343953926634992332820282019728792003956564819966n, '7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe'],
[57896044618658097711785492504343953926634992332820282019728792003956564819967n, '7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'],
[-57896044618658097711785492504343953926634992332820282019728792003956564819968n, '8000000000000000000000000000000000000000000000000000000000000000'],
[-57896044618658097711785492504343953926634992332820282019728792003956564819967n, '8000000000000000000000000000000000000000000000000000000000000001'],
[-57896044618658097711785492504343953926634992332820282019728792003956564819966n, '8000000000000000000000000000000000000000000000000000000000000002'],
[-2n, 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe'],
[-1n, 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'],
[0n, '00'],
[1n, '01'],
[2n, '02'],
[126n, '7e'],
[127n, '7f'],
[-128n, '80'],
[-127n, '81'],
[-126n, '82'],
[-2n, 'fe'],
[-1n, 'ff'],
[0n, new Uint8Array([0x00])],
[1n, new Uint8Array([0x01])],
[2n, new Uint8Array([0x02])],
[126n, new Uint8Array([0x7e])],
[127n, new Uint8Array([0x7f])],
[-128n, new Uint8Array([0x80])],
[-127n, new Uint8Array([0x81])],
[-126n, new Uint8Array([0x82])],
[-2n, new Uint8Array([0xfe])],
[-1n, new Uint8Array([0xff])],
]
for (let testCase of testCases) {
const expected = testCase[0]
const size = testCase[1].length * ((typeof testCase[1] === 'string') ? 4 : 8)
const actual = (typeof testCase[1] === 'string') ? hexStringToBigint(testCase[1], size) : uint8ArrayToBigint(testCase[1], size)
expect(actual).to.equal(expected)
}
for (let testCase of testCases) {
const expected = (typeof testCase[1] === 'string') ? testCase[1] : testCase[1].toString()
const size = testCase[1].length * ((typeof testCase[1] === 'string') ? 4 : 8)
const actual = (typeof testCase[1] === 'string') ? bigintToHexString(testCase[0], size) : bigintToUint8Array(testCase[0], size).toString()
expect(actual).to.equal(expected)
} |
@caiolima Why was this closed? #163 (comment) says:
|
@MicahZoltu This proposal is already stage 4 and merged and I'm doing housekeeping of the repository. This repository is going to be archived soon and I'm afraid we won't be able to keep the discussion here, since archived repositories are read-only. AFICT, discussion of future proposals before stage 1 are happening in https://es.discourse.group. This thread is going to be available to be referenced, but it won't be possible to post new comments. |
Created a proposal over there for anyone who comes along later: https://es.discourse.group/t/bigint-enhancements/100 |
A very common representation of
BigInt
in JSON is using Base64Url coded strings. Although doable, the solution offered by for example Java is way easier:https://docs.oracle.com/javase/8/docs/api/java/math/BigInteger.html#toByteArray--
This is the pretty ugly and probably buggy workaround I ended up with:
Expected result:
'{"big":"BwMYyOV8edmCI4444w","small":55}'
Also see: #162 (comment)
The text was updated successfully, but these errors were encountered: