ES2021: numeric separators

[2018-02-02] dev, javascript, esnext, es2021
(Ad, please don’t block)

The proposal “Numeric Separators” by Sam Goto and Rick Waldron lets us use _ as a separator in numeric literals. This blog post explains how that works.

What are numeric literals?  

JavaScript has several numeric literals:

  • Decimal literals: 123.45, 86, 12e-5
  • Binary integer literals: 0b1011
  • Octal integer literals: 0o765
  • Hexadecimal integer literals: 0x2FF

Interestingly, unary minus (-) is an operator and not part of numeric literals (apart from signed exponents in decimal literals).

Grouping digits in numeric literals  

Grouping digits to make long numbers more readable has a long tradition. For example:

  • At the end of 2016, Munich had 1,464,301 inhabitants.
  • The distance between Earth and Sun is 149,600,000 km.

The proposal allows underscores as separators in numeric literals:

const inhabitantsOfMunich = 1_464_301;
const distanceEarthSunInKm = 149_600_000;

With other bases, grouping is important, too:

const fileSystemPermission = 0b111_111_000;
const bytes = 0b1111_10101011_11110000_00001101;
const words = 0xFAB_F00D;

You can also use the separator in fractions and exponents:

const massOfElectronInKg = 9.109_383_56e-31;
const trillionInShortScale = 1e1_2;

Restrictions  

The key restriction to keep in mind is: You can only put underscores between two digits. Therefore, the following numeric literals are illegal.

3_.141
3._141

1_e12
1e_12

_1464301  /* valid variable name! */
1464301_

0_b111111000
0b_111111000

Furthermore, you can never use more than one underscore in a row:

123__456 /* two underscores – not allowed */

The motivation behind these restrictions is to keep things simple.

Bigints and numeric separators  

The proposed arbitrary-precision integers (bigints) enable you to represent larger numbers numerically. Thus, numeric separators are especially helpful with them:

const massOfEarthInKg = 6_000_000_000_000_000_000_000_000n;

Bigints are often used to represent money in the financial technical sector. Separators can help here, too:

const priceInCents = 123_000_00; // 123 thousand dollars

Parsing numbers with separators  

The following functions for parsing numbers will not support separators:

  • Number()
  • parseInt()
  • parseFloat()

For example:

> Number('123_456')
NaN
> parseInt('123_456')
123

The rationale is that numeric separators are for code. Other kinds of input should be processed differently.

A helper function for parsing numbers with separators  

One technique for parsing numbers with separators is to remove non-digit characters:

const RE_NON_DIGIT = /[^0-9]/gu;
function removeNonDigits(str) {
    str = str.replace(RE_NON_DIGIT, '');
    return Number(str);
}

This is how you use this function:

> removeNonDigits('149,600,000')
149600000
> removeNonDigits('1,407,836')
1407836

Tips  

Don’t forget about exponential notation  

With trailing zeros, exponential notation may be more convenient than grouping zeros. Compare:

const timeoutInMilliseconds = 10e3; /* 10 seconds */
const tenSecondTimeout = 10_000; /* 10 seconds */

Numbers aren’t always the best choice to represent numeric(ish) data  

Some data such as phone numbers, credit card numbers and social security numbers, are in some ways numbers, in others not: There may be non-numeric prefixes and separators and leading digits are significant. They should also never be represented in exponential notation.

Therefore – avoid:

// Don’t do this:
const phoneNumber = 555_2368;
const creditCardNumber = 378_2822_4631_0005;
const socialSecurityNumber = 111_11_1111;