Formatowanie liczb w JavaScript: toFixed i Intl.NumberFormat

Zaokrąglenie do dwóch miejsc po przecinku, wyświetlenie 1 234 567,89 w formacie francuskim, waluta, procent albo „1,4 k”: natywne metody formatowania liczb w JavaScript, ich pułapki i to, co zostało z Numeral.js.

Formatowanie liczb w JavaScripcie z biblioteką Numeral
Szybka odpowiedź

Aby wyświetlić liczbę, użyj Intl.NumberFormat albo jego skrótu toLocaleString(): (1234.5).toLocaleString('fr-FR', { minimumFractionDigits: 2 }) zwraca „1 234,50”, a opcja style: 'currency' dodaje walutę. Aby zaokrąglić bez wyświetlania, użyj Math.round(n * 100) / 100. toFixed(2) również zaokrągla, ale zwraca ciąg znaków.

Liczbę oblicza się jako Number, a wyświetla w postaci ciągu znaków: „1 234,50 €”, „25,6 %”, „1,4 k wyświetleń”. Pomiędzy tymi dwiema postaciami trzeba zaokrąglić liczbę, wstawić separator tysięcy i wybrać przecinek albo kropkę. JavaScript robi to wszystko bez żadnej biblioteki dzięki toFixed, toLocaleString, a przede wszystkim Intl.NumberFormat. Wszystkie wyniki poniżej zostały wykonane pod Node.js 22.

Zaokrąglanie liczby

Math.round, Math.floor, Math.ceil, Math.trunc

javascript
Math.round(2.5);   // 3   : do najbliższej, .5 w górę
Math.round(-2.5);  // -2  : w stronę +Infinity, nie „od zera”
Math.floor(4.7);   // 4   : w dół
Math.floor(-4.2);  // -5
Math.ceil(4.2);    // 5   : w górę
Math.trunc(-4.7);  // -4  : obcina część dziesiętną, niezależnie od znaku

Te funkcje znają tylko liczby całkowite. Aby zaokrąglić do dwóch miejsc po przecinku, mnożymy, zaokrąglamy, dzielimy:

javascript
Math.round(1.345 * 100) / 100;   // 1.35
Math.round(1.005 * 100) / 100;   // 1    : oczekiwano 1.01, patrz niżej

Pułapka binarnych ułamków dziesiętnych

1.005 nie istnieje w zapisie binarnym: liczba przechowywana w pamięci to w rzeczywistości 1.00499999…, więc zaokrągla się do 1.00. To nie błąd JavaScriptu, lecz konsekwencja formatu IEEE 754, wspólnego dla wszystkich języków. Dwa sposoby, by sobie z tym poradzić:

javascript
// 1. Najpierw popraw błąd reprezentacji, potem zaokrąglij
Math.round((1.005 + Number.EPSILON) * 100) / 100;   // 1.01

// 2. Nigdy nie przechowuj ułamków dziesiętnych: ceny trzymaj w całkowitych groszach
const prixCentimes = 1005;
(prixCentimes / 100).toFixed(2);                    // '10.05'

Drugi sposób to reguła obowiązująca wszędzie tam, gdzie chodzi o pieniądze: dodawanie całkowitych groszy nigdy nie da w wyniku 0.30000000000000004.

toFixed(): zaokrąglanie do wyświetlenia

javascript
(1234.5678).toFixed(2);  // '1234.57'
(2.5).toFixed(0);        // '3'
(1.005).toFixed(2);      // '1.00'  : ta sama binarna pułapka co w Math.round
(0.1 + 0.2).toFixed(2);  // '0.30'  : właśnie do tego służy

typeof (1.5).toFixed(2); // 'string'
(1.5).toFixed(2) + 1;    // '1.501' : konkatenacja, nie dodawanie

toFixed zwraca ciąg znaków. To rozwiązanie doskonałe do wyświetlania, ale niebezpieczne przy obliczeniach: jeśli chcesz ponownie użyć wartości, przekonwertuj ją funkcją Number(). Jej separator dziesiętny to zawsze kropka. Francuski przecinek otrzymasz przez toLocaleString.

Separator tysięcy i francuski format zapisu: toLocaleString()

javascript
const n = 1234567.891;

n.toLocaleString('fr-FR');   // '1 234 567,891'
n.toLocaleString('en-US');   // '1,234,567.891'
n.toLocaleString('de-DE');   // '1.234.567,891'

(1234.5).toLocaleString('fr-FR', { minimumFractionDigits: 2 });   // '1 234,50'
(3.14159).toLocaleString('fr-FR', { maximumFractionDigits: 2 });  // '3,14'

Jedna metoda, trzy krajowe konwencje zapisu, ani jednej linii kodu formatującego. Dwie uwagi:

  • Francuski separator tysięcy to nie zwykła spacja, lecz wąska spacja niełamliwa (U+202F). Zapobiega ona rozbiciu zapisu „1 234” na końcu wiersza. Jeśli chcesz ją usunąć, filtruj wyrażeniem /\s/g, a nie ' '.
  • Bez podanego argumentu języka toLocaleString() korzysta z ustawień przeglądarki lub systemu. Dla stabilnego wyniku zawsze podawaj 'fr-FR'.

Intl.NumberFormat: waluta, procenty, jednostki

toLocaleString to skrót do Intl.NumberFormat. Pełny obiekt tworzy się raz i używa wielokrotnie, co ma znaczenie, gdy formatujemy listę albo tabelę.

formats.js
const euros = new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' });
euros.format(1234.5);   // '1 234,50 €'

const dollars = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
dollars.format(1234.5); // '$1,234.50'

const pourcent = new Intl.NumberFormat('fr-FR', { style: 'percent', maximumFractionDigits: 1 });
pourcent.format(0.256); // '25,6 %'  : ta wartość to ułamek, 0.256, a nie 25.6

const km = new Intl.NumberFormat('fr-FR', { style: 'unit', unit: 'kilometer' });
km.format(12.5);        // '12,5 km'

const go = new Intl.NumberFormat('fr-FR', { style: 'unit', unit: 'gigabyte', maximumFractionDigits: 2 });
go.format(2.35);        // '2,35 Go'

Formater walutowy umieszcza symbol we właściwym miejscu w zależności od języka (po liczbie po francusku, przed liczbą po angielsku), dobiera liczbę miejsc po przecinku właściwą dla danej waluty i wstawia spację niełamliwą. To reguły, których nie trzeba już samodzielnie programować.

Notacja kompaktowa: 1,4 k i 1 M

Wyświetlenie „1 400 wyświetleń” jako „1,4 k” albo „1 000 000” jako „1 M” to częsta potrzeba. Robi to opcja notation: 'compact', w wybranym języku.

javascript
const compact = new Intl.NumberFormat('fr-FR', { notation: 'compact' });
compact.format(1400);       // '1,4 k'
compact.format(1000000);    // '1 M'

new Intl.NumberFormat('en-US', { notation: 'compact' }).format(23000);   // '23K'
new Intl.NumberFormat('en-US', { notation: 'compact', maximumFractionDigits: 3 }).format(1230974);   // '1.231M'

new Intl.NumberFormat('fr-FR', { notation: 'compact', compactDisplay: 'long' }).format(2500000);   // '2,5 millions'

Inne przydatne opcje:

javascript
new Intl.NumberFormat('fr-FR', { minimumIntegerDigits: 2 }).format(7);          // '07'
new Intl.NumberFormat('fr-FR', { useGrouping: false }).format(1234567);        // '1234567'
new Intl.NumberFormat('fr-FR', { signDisplay: 'always' }).format(12);          // '+12'
new Intl.NumberFormat('en-US', { notation: 'scientific' }).format(676565765722); // '6.766E11'
new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 2, roundingMode: 'halfExpand' }).format(1.005); // '1,01'

Ostatnia linijka zasługuje na komentarz: Intl.NumberFormat zaokrągla na podstawie dziesiętnej reprezentacji liczby, a nie jej binarnego przybliżenia. 1.005 rzeczywiście staje się „1,01”, podczas gdy toFixed dawało „1.00”. To kolejny powód, by formatować liczby przez Intl, zamiast robić to ręcznie.

Zera wiodące i inne systemy liczbowe

javascript
String(7).padStart(2, '0');   // '07'  : godziny, minuty, numery faktur
String(7).padStart(3, '0');   // '007'

(255).toString(16);   // 'ff'        : szesnastkowo, na przykład do koloru CSS
(255).toString(2);    // '11111111'  : binarnie

(123.456).toPrecision(4);   // '123.5'     : łączna liczba cyfr znaczących
(1234.5678).toExponential(2); // '1.23e+3'

Warto znać dwa zachowania toString(): powyżej 1021 i poniżej 10−6 JavaScript przełącza się na zapis naukowy ((1e21).toString() zwraca '1e+21', (0.0000001).toString() zwraca '1e-7'). toFixed albo Intl.NumberFormat z opcją notation: 'standard' zachowują pełny zapis.

Operacja odwrotna

Sformatowanego ciągu znaków nie da się przekonwertować wprost: Number('1 234,50') zwraca NaN. Trzeba usunąć spacje i zamienić przecinek na kropkę przed konwersją. Pełny opis znajdziesz w artykule Konwersja ciągu znaków na liczbę w JavaScript.

A co z Numeral.js?

Pierwsza wersja tego artykułu prezentowała bibliotekę Numeral.js, w formacie kompaktowym: numeral(50000).format('0,0') dla „50,000”, '0a' dla „23k”, '0.00b' dla „2.35GB”, '0o' dla „23rd”. Wciąż jest obecna w wielu projektach, a jej składnia jest przyjemna.

javascript
import numeral from 'numeral';

numeral(50000).format('0,0');       // 50,000
numeral(23000).format('0a');        // 23k
numeral(2348895676).format('0.00b'); // 2.35GB

Jednak jej ostatnia wersja, 2.0.6, pochodzi z marca 2017 roku, a projekt nie jest już rozwijany: lokalizacje są zamrożone, a francuski separator nie odpowiada oczekiwanej wąskiej spacji. W nowym projekcie Intl.NumberFormat pokrywa te same potrzeby (tysiące, notacja kompaktowa, waluta, procenty) bez żadnej zależności, z prawdziwymi konwencjami każdego języka i zerowym wpływem na rozmiar paczki. Jeśli Numeral jest już w projekcie, zostaw go tam, ale nie dodawaj go w nowych.

Podsumowanie

Potrzeba Narzędzie Przykład
Zaokrąglenie do obliczeń Math.round(n * 100) / 100 albo całkowite grosze 1.35
Zaokrąglenie do wyświetlenia, kropka dziesiętna toFixed(2) '1234.57'
Format francuski, tysiące i przecinek toLocaleString('fr-FR') '1 234 567,891'
Waluta, procent, jednostka Intl.NumberFormat + style '1 234,50 €'
1,4 k / 1 M notation: 'compact' '1,4 k'
Zera wiodące padStart(2, '0') '07'

Częste błędy

Dodawanie wyniku toFixed (1.5).toFixed(2) + 1 daje „1.501”: toFixed zwraca ciąg znaków, więc dodawanie staje się konkatenacją. Przekonwertuj wynik funkcją Number(), jeśli chcesz go dalej przeliczać.
Oczekiwanie „szkolnego” zaokrąglenia (1.005).toFixed(2) daje „1.00”, ponieważ 1.005 jest przechowywane w pamięci jako 1.00499999… w zapisie binarnym. Do zaokrągleń finansowych pracuj na całkowitych groszach.
Zastępowanie spacji z toLocaleString('fr-FR') zwykłą spacją Francuski separator tysięcy to wąska spacja niełamliwa (U+202F). replace(' ', '') jej nie usunie. Użyj /\s/g.
Tworzenie Intl.NumberFormat od nowa w pętli Budowa formatera jest kosztowna. Utwórz go raz, a potem wywołuj format() dla każdej wartości.

JavaScript

Damien Flandrin Web developer od 2010 roku, twórca Gekkode i Email Impact. Każdy artykuł jest sprawdzany na prawdziwym projekcie przed publikacją. Kontakt
Newsletter

Nowe testy, poradniki i projekty — e-mailem.

Powtarzalne testy, wersjonowany kod, datowane wyniki. Nigdy spamu.