How to write numeric literals

A numeric literal is the way you write a number directly in your source code. In JavaScript that covers ordinary decimal numbers, numbers written in other bases (binary, octal, hexadecimal), and BigInt integers. JavaScript’s parser turns these character sequences into actual numeric values as part of the lexical grammar, long before your code runs. Critically, numeric literals themselves are always unsigned; a leading - or + is a separate unary operator applied to the literal afterward.

// Unsigned literal + unary operator
-123.45  // parsed as unary minus applied to the literal 123.45
+0xFF    // parsed as unary plus applied to the literal 0xFF

Decimal literals

Write base-10 numbers with digits 0–9. You can include a fractional part and an exponent for scientific notation.

42
3.14159
0.5
5e3      // 5000
1.25e-3  // 0.00125

Exponents use e or E followed by an optional sign and at least one digit. The decimal point is optional if you use an exponent, but you can’t have two decimal points and you can’t put an exponent on a BigInt (we’ll get there soon).

0.25e2     // 25
2.E3       // 2000 (fractional part omitted)
.5e1       // 5   (leading 0 omitted)

Integers in other bases: binary, octal, and hexadecimal

JavaScript supports integer literals in bases 2, 8, and 16 using standard prefixes:

0b1010        // binary 10 (decimal 10)
0o755         // octal 755 (decimal 493)
0xFF_A9       // hex FF A9 (decimal 65449)

Binary uses 0b/0B with only 0 and 1. Octal uses 0o/0O with digits 0–7. Hex uses 0x/0X and digits 0–9 plus A–F/a–f. These syntaxes work in both strict and non-strict code.

There’s also a historical, legacy octal form that uses a leading 0 like 0755. That form is forbidden in strict mode and should be avoided entirely in modern code. Stick to 0o755.

"use strict";
// 0755;   // SyntaxError in strict mode — legacy octal
0o755;      // Preferred and valid everywhere

When you see a leading 0 without x, o, or b after it and any digit greater than 7 appears, the literal is treated as decimal (or a syntax error in strict mode for some patterns). Avoid ambiguity by not using padded integers like 0005; write 5 instead.

BigInt literals

BigInt represents whole numbers of arbitrary size. Append n to an integer literal to produce a BigInt value. You can use decimal, binary, octal, or hex forms—still integers only.

9007199254740993n        // larger than Number.MAX_SAFE_INTEGER
0b1_0000_0000_0000_0000n // 65536n in binary
0o7_000_000_000_000n     // octal BigInt
0x1f_ff_ff_ff_ff_ffn     // hex BigInt

BigInt literals can’t have a decimal point or an exponent, and you can’t mix them directly with Number in arithmetic without explicit conversion. Use BigInt() for intentional conversion from integers; fractional numbers can’t be converted.

1.5n            // ❌ SyntaxError: BigInt with decimal
1e3n            // ❌ SyntaxError: BigInt with exponent
BigInt(42)      // 42n
BigInt(42.1)    // RangeError (not an integer)

Numeric separators (underscores) for readability

You can insert underscores between digits to group them for readability in any numeric base and with BigInt. They don’t change the value; they’re ignored by the parser.

1_000_000                 // one million
3.141_592_653_589_793     // grouped fractional part
0b1101_0101_1001_0001     // binary grouping
0xFF_FF_00_00             // hex grouping
1_000_000_000_000_000n    // BigInt with separators

A few rules keep things unambiguous. You can’t put an underscore at the start or end of the number, you can’t have two in a row, and you can’t put one right next to the decimal point, right next to the exponent marker e/E or its sign, or in the base prefix.

100__000        // ❌ two in a row
_1000           // ❌ starts with underscore
1000_           // ❌ ends with underscore
1_.5            // ❌ next to decimal point
1._5            // ❌ next to decimal point
1e_3            // ❌ next to exponent marker
1e+_3           // ❌ next to exponent sign
0x_FF           // ❌ right after the prefix
0b1010_         // ❌ ends with underscore

If you need a human-friendly string version of a number with separators, format it yourself (toLocaleString, custom formatter, etc.). Parsers like Number() do not accept underscores inside strings.

Number("1_000")  // NaN — underscores are only for *literals*, not strings

Signed numbers are expressions, not literals

You often write -8 or +3.5, but the sign isn’t part of the literal. It’s a separate unary operator applied to the literal that follows. This matters in a few places such as grammar edge cases and minified code. If you’re tokenizing or doing static analysis, treat - and + as operators, not as part of the numeric token.

const a = -0xFF;     // unary minus + hex literal
const b = +1_000;    // unary plus + decimal literal with separators

Infinity and NaN aren’t numeric literals

Infinity and NaN are identifiers bound to special numeric values, not literal syntax. Use them when that’s what you mean, but don’t confuse them with number tokens.

Infinity     // a global property whose value is a Number
NaN          // “not a number” — also a Number value

Putting it all together with examples

Here are practical patterns that come up frequently in day-to-day code. Each line shows a literal you’d actually write and the value JavaScript produces.

// Decimal, fractional, and exponential forms
const rate   = 0.075;     // 0.075
const nano   = 1e-9;      // 0.000000001
const bigExp = 12.5e6;    // 12500000

// Other bases
const mask   = 0b1111_0000; // 240
const mode   = 0o644;        // 420
const color  = 0xFF_FF_00;   // 16776960

// BigInt variants
const files  = 1_000_000_000_000n;
const flags  = 0b1_0101n;
const guid   = 0xDEAD_BEEF_F00Dn;

// Signed forms are expressions
const neg    = -0b1010;    // -10
const pos    = +.5e1;      // 5

// Separator pitfalls (don’t do these)
// 1_e3;     // ❌
/// e1_0;    // ❌

Interop tips and pitfalls you’ll actually hit

If you paste data with leading zeros into source files, remember that 0755 is legacy octal and will break in strict mode. Always spell octal as 0o755. If you rely on string parsing, don’t expect underscores to work — they’re only valid when the parser sees a literal in code. And finally, don’t try to mix Number and BigInt arithmetic without converting on purpose; JavaScript will throw to prevent silent precision bugs.

// Safe octal
const perm = 0o755;

// String parsing doesn’t allow separators
parseInt("1_000", 10);  // 1, because it stops at '_' (not what you want)
Number("1_000");        // NaN

// Number ↔ BigInt requires intent
10n + 5;                // ❌ TypeError
10n + BigInt(5);        // ✅ 15n
Number(10n) + 5;        // ✅ 15

How JavaScript decides what your digits “mean”

Under the hood, the ECMAScript lexical grammar defines how sequences of characters become numeric tokens and then “mathematical values.” That grammar is why, for example, 1_000 is allowed but 1__000 is not, why 0o changes the valid digit set, and why the - in -2 isn’t part of the numeric token. You don’t need to memorize the production rules, but it’s useful to know that these behaviors are specified precisely and uniformly across engines.

Quick sanity checks you can keep in your tests

If you want to ensure you’re using literals safely in a codebase, add a few focused tests and lint rules: numbers using separators still equal their separator-free forms, BigInt values never appear with decimals or exponents, and all octal values use 0o rather than a leading 0. ESLint rules like no-loss-of-precision and TypeScript’s checks will catch many of the pitfalls, but unit tests that exercise real literals are a great backstop.

// Example test snippets (Jest)
expect(1_000_000).toBe(1000000);
expect(0o755).toBe(493);
expect(() => eval("1e3n")).toThrow();        // BigInt exponent not allowed
expect(() => eval("'use strict'; 0755")).toThrow(); // legacy octal in strict

References

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Numbers_and_strings
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number
https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Deprecated_octal_literal