Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add utils for converting between numbers and hex #41

Merged
merged 2 commits into from
Oct 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/__fixtures__/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './bytes';
export * from './coercions';
export * from './json';
export * from './numbers';
52 changes: 52 additions & 0 deletions src/__fixtures__/numbers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export const NUMBER_VALUES = [
{
number: 0,
bigint: BigInt(0),
hex: '0x0',
},
{
number: 1,
bigint: BigInt(1),
hex: '0x1',
},
{
number: 16,
bigint: BigInt(16),
hex: '0x10',
},
{
number: 255,
bigint: BigInt(255),
hex: '0xff',
},
{
number: 256,
bigint: BigInt(256),
hex: '0x100',
},
{
number: 65535,
bigint: BigInt(65535),
hex: '0xffff',
},
{
number: 65536,
bigint: BigInt(65536),
hex: '0x10000',
},
{
number: 4294967295,
bigint: BigInt(4294967295),
hex: '0xffffffff',
},
{
number: 4294967296,
bigint: BigInt(4294967296),
hex: '0x100000000',
},
{
number: 9007199254740991,
bigint: BigInt(9007199254740991),
hex: '0x1fffffffffffff',
},
];
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export * from './hex';
export * from './json';
export * from './logging';
export * from './misc';
export * from './number';
export * from './time';
101 changes: 101 additions & 0 deletions src/number.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { bigIntToHex, hexToBigInt, hexToNumber, numberToHex } from './number';
import { NUMBER_VALUES } from './__fixtures__';

describe('numberToHex', () => {
it.each(NUMBER_VALUES)(
'converts a number to a hex string',
({ number, hex }) => {
expect(numberToHex(number)).toBe(hex);
},
);

it.each([true, false, null, undefined, {}, [], '', '0x', '0x0', BigInt(1)])(
'throws if the value is not a number',
(value) => {
// @ts-expect-error Invalid type.
expect(() => numberToHex(value)).toThrow('Value must be a number.');
},
);

it.each([-1, -1e100, -Infinity, NaN])(
'throws if the value is negative',
(value) => {
expect(() => numberToHex(value)).toThrow(
'Value must be a non-negative number.',
);
},
);

it.each([1.1, 1e100, Infinity])(
FrederikBolding marked this conversation as resolved.
Show resolved Hide resolved
'throws if the value is not a safe integer',
(value) => {
expect(() => numberToHex(value)).toThrow(
'Value is not a safe integer. Use `bigIntToHex` instead.',
);
},
);
});

describe('bigIntToHex', () => {
it.each(NUMBER_VALUES)(
'converts a bigint to a hex string',
({ bigint, hex }) => {
expect(bigIntToHex(bigint)).toBe(hex);
},
);

it.each([true, false, null, undefined, {}, [], '', '0x', '0x0', 1])(
'throws if the value is not a bigint',
(value) => {
// @ts-expect-error Invalid type.
expect(() => bigIntToHex(value)).toThrow('Value must be a bigint.');
},
);

it.each([BigInt(-1), BigInt('-100')])(
'throws if the value is negative',
(value) => {
expect(() => bigIntToHex(value)).toThrow(
'Value must be a non-negative bigint.',
);
},
);
});

describe('hexToNumber', () => {
it.each(NUMBER_VALUES)(
'converts a hex string to a number',
({ number, hex }) => {
expect(hexToNumber(hex)).toBe(number);
},
);

it.each([true, false, null, undefined, 0, 1, '', [], {}, BigInt(1)])(
'throws if the value is not a hexadecimal string',
(value) => {
// @ts-expect-error Invalid type.
expect(() => hexToNumber(value)).toThrow(
'Value must be a hexadecimal string.',
);
},
);
});

describe('hexToBigInt', () => {
it.each(NUMBER_VALUES)(
'converts a hex string to a bigint',
({ bigint, hex }) => {
expect(hexToBigInt(hex)).toBe(bigint);
},
);

it.each([true, false, null, undefined, 0, 1, '', [], {}, BigInt(1)])(
'throws if the value is not a hexadecimal string',
(value) => {
// @ts-expect-error Invalid type.
expect(() => hexToBigInt(value)).toThrow(
'Value must be a hexadecimal string.',
);
},
);
});
110 changes: 110 additions & 0 deletions src/number.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { add0x, assertIsHexString } from './hex';
import { assert } from './assert';

/**
* Convert a number to a hexadecimal string. This verifies that the number is a
* non-negative safe integer.
*
* To convert a `bigint` to a hexadecimal string instead, use
* {@link bigIntToHex}.
*
* @example
* ```typescript
* numberToHex(0); // '0x0'
* numberToHex(1); // '0x1'
* numberToHex(16); // '0x10'
* ```
* @param value - The number to convert to a hexadecimal string.
* @returns The hexadecimal string, with the "0x"-prefix.
* @throws If the number is not a non-negative safe integer.
*/
export const numberToHex = (value: number): string => {
assert(typeof value === 'number', 'Value must be a number.');
assert(value >= 0, 'Value must be a non-negative number.');
assert(
Number.isSafeInteger(value),
'Value is not a safe integer. Use `bigIntToHex` instead.',
);

return add0x(value.toString(16));
};

/**
* Convert a `bigint` to a hexadecimal string. This verifies that the `bigint`
* is a non-negative integer.
*
* To convert a number to a hexadecimal string instead, use {@link numberToHex}.
*
* @example
* ```typescript
* bigIntToHex(0n); // '0x0'
* bigIntToHex(1n); // '0x1'
* bigIntToHex(16n); // '0x10'
* ```
* @param value - The `bigint` to convert to a hexadecimal string.
* @returns The hexadecimal string, with the "0x"-prefix.
* @throws If the `bigint` is not a non-negative integer.
*/
export const bigIntToHex = (value: bigint): string => {
assert(typeof value === 'bigint', 'Value must be a bigint.');
assert(value >= 0, 'Value must be a non-negative bigint.');

return add0x(value.toString(16));
};

/**
* Convert a hexadecimal string to a number. This verifies that the string is a
* valid hex string, and that the resulting number is a safe integer. Both
* "0x"-prefixed and unprefixed strings are supported.
*
* To convert a hexadecimal string to a `bigint` instead, use
* {@link hexToBigInt}.
*
* @example
* ```typescript
* hexToNumber('0x0'); // 0
* hexToNumber('0x1'); // 1
* hexToNumber('0x10'); // 16
* ```
* @param value - The hexadecimal string to convert to a number.
* @returns The number.
* @throws If the value is not a valid hexadecimal string, or if the resulting
* number is not a safe integer.
*/
export const hexToNumber = (value: string): number => {
assertIsHexString(value);

// `parseInt` accepts values without the "0x"-prefix, whereas `Number` does
// not. Using this is slightly faster than `Number(add0x(value))`.
const numberValue = parseInt(value, 16);

assert(
Number.isSafeInteger(numberValue),
'Value is not a safe integer. Use `hexToBigInt` instead.',
);

return numberValue;
};

/**
* Convert a hexadecimal string to a `bigint`. This verifies that the string is
* a valid hex string. Both "0x"-prefixed and unprefixed strings are supported.
*
* To convert a hexadecimal string to a number instead, use {@link hexToNumber}.
*
* @example
* ```typescript
* hexToBigInt('0x0'); // 0n
* hexToBigInt('0x1'); // 1n
* hexToBigInt('0x10'); // 16n
* ```
* @param value - The hexadecimal string to convert to a `bigint`.
* @returns The `bigint`.
* @throws If the value is not a valid hexadecimal string.
*/
export const hexToBigInt = (value: string): bigint => {
assertIsHexString(value);

// The `BigInt` constructor requires the "0x"-prefix to parse a hex string.
return BigInt(add0x(value));
};