Number.prototype.toString()

Without an argument it is the ordinary conversion every template literal performs. With a radix it becomes the shortest way to produce hex, binary or any base up to 36.

Number methodES1 (1997)Live demo
Common call
n.toString(16)
Returns
the number as a string in that base
Replaces
manual digit arithmetic
Watch out
radix must be 2–36, and 255.toString() is a syntax error
number.toString([radix])
→ string

Demo

Live evaluation
Try:
Inputs
nnumbera number
radixnumberbase (2–36)
Output
(255).toString(16)
'ff'

Digits above 9 are written as lowercase letters, so base 16 gives 'ff' and base 36 uses the whole alphabet — which is why base 36 is a popular way to shorten numeric ids. Negative numbers get a minus sign rather than a two's-complement representation, so (-255).toString(2) is '-11111111' and not a bit pattern. The inverse is parseInt with the same radix.

Parameters

NameTypeRequiredDescription
radixnumberno (10)The base, 2 to 36. Digits above 9 use lowercase letters. Anything outside the range throws RangeError.

Return value

string — The number written in the given base. Default base 10, which is also what implicit string coercion produces.

Common patterns

Hex colour components
Pad, because single digits are common.
const hex = n => n.toString(16).padStart(2, '0');
Short random ids
Base 36 packs the most into the fewest characters.
Math.random().toString(36).slice(2, 10);
Round trip with parseInt
Same radix both ways.
parseInt(n.toString(16), 16) === n;

Examples

1. Hexadecimal
(255).toString(16)
Returns
'ff'
2. Binary
(255).toString(2)
Returns
'11111111'
3. Negative
(-255).toString(16)
Returns
'-ff'
4. Fractions work
(0.5).toString(2)
Returns
'0.1'
5. Two dots parse
255..toString(16)
Returns
'ff'
6. Bad radix
(255).toString(37)
Returns
RangeError: toString() radix argument must be between 2 and 36

Pitfalls

1. 255.toString(16) is a syntax error
The parser reads the dot as the start of a decimal fraction, so the method name becomes invalid. Wrap the number in parentheses, or use two dots — the first ends the number, the second is the property access.
Will not parse
255.toString(16)
SyntaxError: Invalid or unexpected token
Parenthesise
(255).toString(16)
'ff'
2. Negatives get a minus sign, not a bit pattern
You get a minus sign in front of the magnitude, which is not the bit pattern a negative integer actually has in memory. For a real 32-bit representation use an unsigned shift first.
A minus sign
(-255).toString(2)
'-11111111'
Bit pattern
(-255 >>> 0).toString(2)
'11111111111111111111111100000001'
3. Non-decimal fractions are approximate
Converting a fraction to another base has the same representation problem as decimal does — the digits may not terminate, and the output is the closest the double can express. Only use radix conversion on integers unless you have checked.
Long expansion
(0.1).toString(3)
a long non-terminating expansion
Integers only
(Math.round(0.1 * 1000)).toString(3)
'10201'
4. It is not zero-padded
A byte below 16 produces a single hex digit, so joining colour components without padding silently produces a short, wrong string.
One digit
(5).toString(16)
'5'
Pad it
(5).toString(16).padStart(2, '0')
'05'

When to use

Use it
  • Producing hex, binary or octal representations
  • Compact ids via base 36
  • Debugging bit patterns, with an unsigned shift first
  • Explicit conversion where a template literal would be unclear
Reach for something else
  • Formatting for a user → toLocaleString or Intl.NumberFormat
  • A fixed number of decimals → toFixed
  • Bit patterns of negatives → shift to unsigned first
  • Ordinary coercion → a template literal is shorter

Notes

Complexity
O(d) in the number of output digits
Return
A new string; the number is unchanged
CPython impl
V8: Builtins-number-tostring
Memory
Allocates the result string
Thread-safe
Single-threaded

FAQ

Because the parser treats the dot as a decimal point and then finds a method name where it expected digits. Parentheses around the number fix it, and so does a second dot — 255..toString(16) works because the first dot completes the numeric literal.

(255).toString(16);   // fine
255..toString(16);    // also fine
255 .toString(16);    // fine too, with a space

History

ES1
toString with a radix and valueOf present from the first version.