diff --git a/.changeset/hex-tonumber-empty.md b/.changeset/hex-tonumber-empty.md new file mode 100644 index 00000000..fccf0c0f --- /dev/null +++ b/.changeset/hex-tonumber-empty.md @@ -0,0 +1,5 @@ +--- +"ox": patch +--- + +Fixed `Hex.toNumber` (and `Bytes.toNumber`, which delegates to it) returning `NaN` for empty hex (`0x`) instead of throwing, consistent with `Hex.toBigInt`. diff --git a/src/core/Hex.ts b/src/core/Hex.ts index fcf6e853..fd8a59c6 100644 --- a/src/core/Hex.ts +++ b/src/core/Hex.ts @@ -665,7 +665,12 @@ export declare namespace toBytes { */ export function toNumber(hex: Hex, options: toNumber.Options = {}): number { const { signed, size } = options - if (!signed && !size) return Number(hex) + if (!signed && !size) { + const number = Number(hex) + // `Number('0x')` is `NaN`; defer to `toBigInt` so empty hex throws + // instead of silently returning `NaN` (consistent with `Hex.toBigInt`). + if (!Number.isNaN(number)) return number + } return Number(toBigInt(hex, options)) } diff --git a/src/core/_test/Hex.test.ts b/src/core/_test/Hex.test.ts index 950b5a7c..d3a5e187 100644 --- a/src/core/_test/Hex.test.ts +++ b/src/core/_test/Hex.test.ts @@ -641,6 +641,11 @@ describe('toNumber', () => { '[Hex.SizeOverflowError: Size cannot exceed `32` bytes. Given size: `64` bytes.]', ) }) + + test('error: empty hex', () => { + // Consistent with `Hex.toBigInt('0x')`, which throws. Must not return `NaN`. + expect(() => Hex.toNumber('0x')).toThrow() + }) }) describe('toString', () => {