Convert a number to a string in the specified radix (toString)

You write code that emits IDs, logs addresses, shows bit flags, or renders user-friendly hex. JavaScript gives you a built-in for all of that: Number.prototype.toString(radix) (and its BigInt sibling). This page gives you the whole picture—from the exact signature and legal bases to the gotchas with negatives, fractions, and round-tripping back to numbers.

The method you call

Call it on a number (or a BigInt) with an optional radix between 2 and 36. If you omit the radix, base-10 is used. When the radix is greater than 10, digits az represent values 10–35, and the letters are lowercase by default. Passing a radix outside the range throws a RangeError.

(3735928559).toString(16);  // "deadbeef"
(255).toString(2);          // "11111111"
(12345).toString();         // "12345"  (defaults to base 10)

Valid radices, conversion rules, and how the spec defines it

Under the hood, the runtime coerces the radix to an integer and then formats the numeric value in that base. The ECMAScript spec explicitly defines the algorithm and clamps the accepted range to 2–36. That’s why 2.9 becomes 2, and why 1 or 37 cause a RangeError.

(42).toString(2.9);  // "101010"  (radix 2 after integer coercion)
(42).toString(1);    // RangeError: radix must be an integer ≥ 2 and ≤ 36

Negative numbers: sign vs. two’s-complement

toString does not emit two’s-complement for negatives; it simply prefixes a minus sign to the base-N representation of the absolute value. If you need a 32-bit two’s-complement view (common when visualizing bit patterns), first coerce to an unsigned 32-bit integer with the unsigned right-shift operator (>>> 0), then stringify in base 2 or 16.

(-13).toString(2);        // "-1101"
(-13 >>> 0).toString(2);  // "11111111111111111111111111110011" (32-bit view)

Non-integers: fractional parts in other bases

toString also formats non-integers in bases other than 10. For example, base-16 uses a radix point just like base-10 uses a decimal point. Keep in mind that many fractions are repeating in non-decimal bases, and the engine prints a finite approximation.

(10.5).toString(2);   // "1010.1"
(Math.PI).toString(16); // "3.243f6a8885a3..."  (finite approximation)

BigInt: same idea, bigger numbers

BigInt.prototype.toString(radix) works the same way but for arbitrary-precision integers. It accepts the same radix range and throws the same RangeError for invalid bases. Bitwise operators like >>> do not work with BigInt, so stick to arithmetic or string operations when formatting huge integers.

(123456789012345678901234567890n).toString(36); // "2lsr3m3x4v4pk1f0u"
(255n).toString(16);                             // "ff"

Round-tripping: back to numbers safely

To convert base-N strings back to numbers, use parseInt(str, radix) for integers, and always supply the radix explicitly. For values outside IEEE-754’s integer precision range, parse into BigInt yourself or use a proposal/polyfill designed for BigInt parsing; parseInt returns a Number.

parseInt("deadbeef", 16); // 3735928559
parseInt("11111111", 2);  // 255

// For huge values, parse to BigInt manually:
const base36 = "2lsr3m3x4v4pk1f0u";
const digits = "0123456789abcdefghijklmnopqrstuvwxyz";
let acc = 0n;
for (const ch of base36) acc = acc * 36n + BigInt(digits.indexOf(ch));
acc.toString(); // "123456789012345678901234567890"

Practical patterns you’ll actually use

Short IDs: base-36 gives compact, URL-friendly strings that still sort lexicographically by magnitude when left-padded.

const id = (Date.now()).toString(36);    // "lq2s3r3"
const padded = id.padStart(8, "0");      // "0lq2s3r3"

Pretty hex with uppercase and fixed width:

function toHex(n, width = 2) {
  return (n >>> 0).toString(16).toUpperCase().padStart(width, "0");
}

toHex(48879, 8);  // "0000BEEF"

Binary flags with spacing for readability:

function toBin32(n) {
  const s = (n >>> 0).toString(2).padStart(32, "0");
  return s.replace(/(.{8})(?=.)/g, "$1 "); // group into bytes
}

toBin32(0b1010_1111); // "00000000 00000000 00000000 10101111"

Common pitfalls and the fixes

If you see RangeError: radix must be an integer …, your base is outside 2–36 or you passed something non-numeric. Coercion rounds non-integers, so 2.5 becomes 2, which can mask mistakes; validate your inputs when a specific base is required. If you expected two’s-complement for negatives, remember that toString uses a leading minus sign—use >>> 0 for a 32-bit unsigned view before converting.

Number("10").toString(37); // RangeError
(42).toString(2.5);        // "101010" (radix coerced to 2)
(-1).toString(16);         // "-1"      (not "ffffffff")
(-1 >>> 0).toString(16);   // "ffffffff"

References

SourceWhat it covers
MDN: Number.prototype.toString()Behavior, default base, digit mapping, examples
MDN: BigInt.prototype.toString()BigInt support, radix range, return value
ECMAScript Language Specification (Number.prototype.toString)Normative algorithm, coercion of radix, valid range
MDN: RangeError: radix must be an integerThe specific error thrown for invalid bases
MDN: Unsigned right shift (>>>)Why >>> 0 yields an unsigned 32-bit view
MDN: parseInt()Parsing strings back to numbers with an explicit radix
TC39 Proposal: Number.fromString / BigInt.fromStringContext on round-tripping BigInt strings (motivation and limitations today)