I. Task
In one of the scripts I encountered the need to format the output of large numbers
for readability . I found several recipes for how to divide the number into
groups by category , but in the discussions there were doubts about performance. I decided to conduct several tests.
Ii. Solution options
')
Suppose we have a variable with a number.
var i = 100000;
Its output can be turned into
100,000
(or
100 000
, or
100.000
) in the following ways.
1. Replacing by regular expression
There are several options, this one seemed to me the most compact:
i.toString().replace( /\B(?=(?:\d{3})+$)/g, ',' );
2. Using an Intl
Object
Namely, the
format()
method of the
NumberFormat
constructor. There are two options.
but. With default:
var fn_undef = new Intl.NumberFormat(); fn_undef.format(i);
b. With forced setting of locale:
var fn_en_US = new Intl.NumberFormat('en-US'); fn_en_US.format(i);
This method has much in common with the previous one, as can be understood from the descriptions. Also consider two options.
but. With default:
i.toLocaleString();
b. With forced setting of locale:
i.toLocaleString('en-US');
This method seems to be the most concise and convenient, but in fact it turns out to be the most insidious.
Iii. Tests
Imagine that we need to display a table with a large number of formatted numbers, in which case the difference in the speed of the methods will be important. Let's try to test the cycle with hundreds of thousands of operations on several engines and in different execution environments.
1. Node.js 4.1.0
Unfortunately, the
ru-RU
locale in this version of Node.js is not supported (or I do not know how to add its support), so for consistency I had to use the
en-US
locale everywhere.
First, the script defines variables and for illustration displays the formatting by all means (five identical results). Then follow five test cycles with the display of the elapsed time after each.
Code for Node.js 'use strict'; var i = 100000; const fn_undef = new Intl.NumberFormat(); const fn_en_US = new Intl.NumberFormat('en-US'); console.log( i.toString().replace( /\B(?=(?:\d{3})+$)/g, ',' ) ); console.log( fn_undef.format(i) ); console.log( fn_en_US.format(i) ); console.log( i.toLocaleString() ); console.log( i.toLocaleString('en-US') ); var time = process.hrtime(); while (i-- > 0) { i.toString().replace( /\B(?=(?:\d{3})+$)/g, ',' ); } console.log(process.hrtime(time)); i = 100000; time = process.hrtime(); while (i-- > 0) { fn_undef.format(i); } console.log(process.hrtime(time)); i = 100000; time = process.hrtime(); while (i-- > 0) { fn_en_US.format(i); } console.log(process.hrtime(time)); i = 100000; time = process.hrtime(); while (i-- > 0) { i.toLocaleString(); } console.log(process.hrtime(time)); i = 100000; time = process.hrtime(); while (i-- > 0) { i.toLocaleString('en-US'); } console.log(process.hrtime(time));
The function for profiling
hrtime
gives the time difference as a tuple of two numbers in the array: the number of seconds and nanoseconds.
Example output (excluding initial illustrations):
[ 0, 64840650 ] [ 0, 473762595 ] [ 0, 470775460 ] [ 0, 514655925 ] [ 14, 120328524 ]
As we can see, the first option is the fastest. The next two are almost the same, but slower than the first by an order of magnitude. The fourth way is a little slower. But the latter is abnormally slow.
This is
Intl.NumberFormat.format()
essential difference between the
Intl.NumberFormat.format()
and
Number.toLocaleString()
methods is
Intl.NumberFormat.format()
: in the first, we once set the locale in the constructor, in the second we set it in each call. When determining the locale, the interpreter performs fairly resource-intensive operations
described in the help . In the first case, it proivzodit them once and for all the time the formatter, in the second case, it produces them again a hundred thousand times. Unnoticeable difference in the code, but very significant for the runtime.
You can make a preliminary conclusion: if you know the desired locale in advance, it is better to use the replacement by a regular expression. If the locale is unpredictable, it is better to use the
Intl.NumberFormat.format()
method without specifying the locale forcibly.
If we test this code (replacing the profiling function) in browsers, we will make sure that this conclusion is correct for them too.
2. Browsers
Run this code in consoles.
Browser Code var i = 100000; const fn_undef = new Intl.NumberFormat(); const fn_en_US = new Intl.NumberFormat('en-US'); console.log( i.toString().replace( /\B(?=(?:\d{3})+$)/g, ',' ) ); console.log( fn_undef.format(i) ); console.log( fn_en_US.format(i) ); console.log( i.toLocaleString() ); console.log( i.toLocaleString('en-US') ); var time = Date.now(); while (i-- > 0) { i.toString().replace( /\B(?=(?:\d{3})+$)/g, ',' ); } console.log(Date.now() - time); i = 100000; time = Date.now(); while (i-- > 0) { fn_undef.format(i); } console.log(Date.now() - time); i = 100000; time = Date.now(); while (i-- > 0) { fn_en_US.format(i); } console.log(Date.now() - time); i = 100000; time = Date.now(); while (i-- > 0) { i.toLocaleString(); } console.log(Date.now() - time); i = 100000; time = Date.now(); while (i-- > 0) { i.toLocaleString('en-US'); } console.log(Date.now() - time);
Now you have to compare milliseconds, but it will be quite visual.
but. Chrome 47.0.2515.0
80 543 528 604 18699
b. Firefox 44.0a1
218 724 730 439 7177
at. IE 11.0.14
215 328 355 32628 37384
We see that Chrome in the latter method lagged behind Node.js, Firefox turned out to be in the same problematic place twice as fast, and in IE 11 the penultimate way in speed was much closer to the latter (i.e., omitting the locale does not much save this option in IE).
Finally, for greater objectivity and for the convenience of those who want to check out,
he added a page on jsperf.com . My last test revision issued the following:
The code is simplified there, because the site takes over the main work on looping. You can experiment by editing the code and adding your own test cases.
PS In the comments added two more ways. Although they are much more voluminous by code, in many test cases they are even faster replaced by regular expressions (tests on Node and in browser consoles:
one ,
two ). Added a
test page with all seven ways . She gives me:
PS 2 Two more functions appeared, made new tests (
one ,
two ) and
added them to jsperf.com . At the same time, I slightly corrected the code with a regular expression, taking the compilation out of the loop: even in MDN it says that literals do not recompile in regular loops, I am not sure if I mean when they are defined outside the loop or even when inside (in Perl it eats an additional flag that forbids recompiling a regular expression that does not change in the loop, I don’t know how JS behaves in these cases). In any case, tests in Node.js and browsers showed a slight increase in speed when the regular schedule is removed from the loop. According to the results of the new tests out of nine methods, the new four, “mathematical”, definitely win, but at the same time different “mathematical” methods win in each browser. My new results:
PS 3 Another +1 function: a
new table (already ten options),
my indicators .
PS 4 I decided to add the most linear variant - enumeration of all possible lengths of an integer in the safe range
Number.MAX_SAFE_INTEGER
with string concatenation by character and inserting a separator in the right places.
This is the eleventh option (the
exhaustion()
function), and it turned out to be quite fast, and even ranked first in the tests on Firefox.