Lodash toNumber Hexadecimal Prefix Handling

The Lodash _.toNumber method converts values into numbers through a structured pipeline that cleans input strings, normalizes custom objects, and validates base-prefixed numeric representations. While binary (0b) and octal (0o) prefixes are explicitly stripped via string slicing, hexadecimal prefixes (0x or 0X) are processed through a combination of regex pattern validation for invalid signed variants and JavaScript's native numeric coercion mechanism. This article breaks down the internal source code mechanics of Lodash to demonstrate exactly how hexadecimal inputs are validated, parsed, and converted.

Input Sanitization and Normalization

Before inspecting any number format, _.toNumber processes non-primitive inputs and cleans whitespace:

  1. Object Unwrapping: If the input is an object, Lodash attempts to invoke its .valueOf() method. If the result remains an object, it falls back to string coercion using template literals.
  2. Type Checking: If the processed value is not a string, Lodash returns symbols as NaN or coerces primitives directly using unary +value.
  3. Trimming: For string inputs, Lodash applies the reTrim regular expression (/^\s+|\s+$/g) to strip leading and trailing whitespace characters.

The Contrast: Explicit Slicing in Binary and Octal

To understand how hexadecimal values are handled, it helps to observe how binary and octal strings are treated in the Lodash source code:

const reIsBinary = /^0b[01]+$/i;
const reIsOctal = /^0o[0-7]+$/i;

const isBinary = reIsBinary.test(value);
return (isBinary || reIsOctal.test(value))
  ? parseInt(value.slice(2), isBinary ? 2 : 8)
  : (reIsBadHex.test(value) ? NAN : +value);

For binary and octal literals, Lodash systematically removes the two-character prefix (0b or 0o) using value.slice(2) and routes the remaining digits into parseInt with an explicit radix of 2 or 8.

Hexadecimal Handling: The reIsBadHex Filter

Unlike binary and octal numbers, hexadecimal strings are not stripped using .slice(2). Instead, Lodash implements a validation guard against invalid hexadecimal formats using the reIsBadHex regular expression:

const reIsBadHex = /^[-+]0x[0-9a-f]+$/i;

In standard JavaScript syntax, signed hexadecimal string literals such as "-0x1a" or "+0x1a" are not valid numerical representations in modern ECMAScript specifications, causing unary + to return NaN, whereas legacy parsers such as parseInt might have yielded -26.

By testing the trimmed string against reIsBadHex, Lodash guarantees that any signed hexadecimal input explicitly evaluates to NaN across all JavaScript runtimes:

reIsBadHex.test(value) ? NAN : +value

Delegation to the ECMAScript Engine

When a string contains a valid, unsigned hexadecimal prefix (0x or 0X), it bypasses the reIsBadHex branch and reaches the fallback expression: +value.

At this stage, the task of stripping the prefix and computing the base-16 value is systematically handled by the JavaScript engine's internal ToNumber abstract operation:

  1. The runtime parser encounters a string conforming to the ECMAScript HexIntegerLiteral grammar: 0x or 0X followed by one or more hexadecimal digits ([0-9a-fA-F]).
  2. The engine's lexical scanner recognizes the 0x/0X characters as a base-16 indicator rather than part of the numeric value.
  3. The prefix is internally discarded, and the remaining characters are computed using positional base-16 mathematics (\(d \times 16^n\)).

Summary

Lodash's _.toNumber systematically isolates hexadecimal prefix handling from octal and binary conversions. Instead of manually invoking string-slicing methods to remove 0x, Lodash cleans the string, ensures that signed variants are flagged as NaN, and delegates the removal of the 0x prefix directly to the JavaScript engine's native string-to-number coercion.