I wrote a function that receives a number as an argument and returns a number (technically, a string) with a maximum of 3 numerals, no matter if there's a decimal separator or not, plus the adequate SI unit. Here some examples for you to understand its logic:
0.1 --> 0.1
42.300099 --> 42.3
1026 --> 1.03k
1200000 --> 1.2M
543760 --> 544k
34069000000 --> 34.1G
As you can see, it returns a maximum of 3 numerals (for instance, these are acceptable results: 1.23
, 12.3
and 123
, plus any unit).
The function works (as requested in any CR question), but it feels unnecessarily convoluted. Here's the code working in the snippet, with the examples above:
function formatNumber(value) {
const units = ["", "k", "M", "B"];
const digits = Math.max(Math.floor(Math.log10(value)), 0) + 1;
const keep = digits % 3 === 1 ? 2 : digits % 3 === 2 ? 1 : 0;
const places = ~~((digits - 1) / 3);
let unit = units[places];
let number = Math.round((10 ** keep) * value / (10 ** (places * 3))) / (10 ** keep);
if (number === 1e3) {
number = Math.round(number / 1e3);
unit = units[places + 1];
};
return number + unit;
};
[0.1, 42.300099, 1026, 1200000, 543760, 34069000000].forEach(e => console.log(e + " --> " + formatNumber(e)))
This is for financial data, so I have B for billions instead of the proper SI Giga. Also, since it deals with money, the code right now has no protection for values over 1 trillion (it will return number + undefined
which is NaN
), negative numbers and other cases, but I'm aware of that.
Here is the detailed explanation of what I tried to do:
function formatNumber(value) {
//the SI units, changing G for B
const units = ["", "k", "M", "B"];
//this gets how many numerals the number has, I found it in this
//SO answer: https://stackoverflow.com/a/28203456/5768908
const digits = Math.max(Math.floor(Math.log10(value)), 0) + 1;
//based on 'digits', this value will be used to determine how many numerals there will
//be after the decimal point. The remainder is used to calculate it: if we have
//just 1 numeral inside that SI unit, there's space for 2 more numerals; if we have
//2, there's space for just 1 more, and if we have 3 there's no space
const keep = digits % 3 === 1 ? 2 : digits % 3 === 2 ? 1 : 0;
//based on 'digits', we get the corresponding SI unit
const places = ~~((digits - 1) / 3);
let unit = units[places];
//using 'keep', this sets by how many powers of ten we'll multiply the number, round it
//and divide again by such power of ten. That's the same logic of keeping just two
//decimal places in a long decimal number, when we multiply it by 100, round the result
//and then divide it by ten again
let number = Math.round((10 ** keep) * value / (10 ** (places * 3))) / (10 ** keep);
//because of the rounding up, some cases like 999999 will return 1000k instead of 1M,
//so I wrote this 'if' condition
if (number === 1e3) {
number = Math.round(number / 1e3);
unit = units[places + 1];
};
return number + unit;
};
I appreciate any feedback.