Библиотека Globalize предоставляет набор инструментов для корректного форматирования и разбора чисел с учётом локализации. Одной из наиболее сложных задач интернационализации является работа с десятичными числами, поскольку разные страны используют различные разделители дробной части, схемы группировки и правила округления.
В большинстве случаев задачи сводятся к трём операциям:
Основной метод форматирования —
Globalize.numberFormatter().
const Globalize = require("globalize");
Globalize.locale("en");
const formatter = Globalize.numberFormatter();
console.log(formatter(12345.678));
// 12,345.678
Для английской локали:
Для русской локали результат будет другим.
Globalize.locale("ru");
const formatter = Globalize.numberFormatter();
console.log(formatter(12345.678));
// 12 345,678
Особенности:
Форматтер поддерживает настройку точности отображения через параметр
maximumFractionDigits.
const formatter = Globalize.numberFormatter({
maximumFractionDigits: 2
});
console.log(formatter(12.34567));
// 12.35
В данном случае число округляется до двух знаков после запятой.
Параметр minimumFractionDigits гарантирует фиксированную
длину дробной части.
const formatter = Globalize.numberFormatter({
minimumFractionDigits: 2
});
console.log(formatter(12));
// 12.00
Полезно для:
Часто требуется ограничить диапазон точности.
const formatter = Globalize.numberFormatter({
minimumFractionDigits: 2,
maximumFractionDigits: 4
});
console.log(formatter(12));
// 12.00
console.log(formatter(12.3));
// 12.30
console.log(formatter(12.345678));
// 12.3457
Логика работы:
Globalize использует стандартные механизмы округления JavaScript.
const formatter = Globalize.numberFormatter({
maximumFractionDigits: 2
});
console.log(formatter(1.005));
Результат может оказаться неожиданным из-за особенностей IEEE 754.
JavaScript хранит числа в формате двойной точности IEEE 754. Из-за этого некоторые десятичные дроби невозможно представить точно.
Классический пример:
console.log(0.1 + 0.2);
// 0.30000000000000004
Проблема относится не к Globalize, а к самому механизму чисел JavaScript.
Числа в памяти хранятся в двоичном виде.
Некоторые десятичные значения:
не имеют точного двоичного представления.
В результате появляются микроскопические ошибки округления:
console.log(1.005 * 100);
// 100.49999999999999
При последующем округлении получается неожиданный результат.
Для повышения точности часто применяют промежуточное преобразование.
function round(value, precision) {
const multiplier = Math.pow(10, precision);
return Math.round((value + Number.EPSILON) * multiplier) / multiplier;
}
console.log(round(1.005, 2));
// 1.01
После такого округления значение можно безопасно передавать в Globalize.
const formatter = Globalize.numberFormatter({
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
const value = round(1.005, 2);
console.log(formatter(value));
// 1.01
Подход особенно важен в:
Globalize поддерживает форматные шаблоны.
const formatter = Globalize.numberFormatter({
raw: "0.000"
});
console.log(formatter(12.3));
// 12.300
Шаблон задаёт:
const formatter = Globalize.numberFormatter({
maximumFractionDigits: 10
});
console.log(formatter(0.000000123456));
Важно помнить:
При работе с экстремально малыми или большими значениями JavaScript автоматически использует экспоненциальную форму.
console.log(0.00000000000001);
// 1e-14
Globalize способен корректно отображать такие значения при соответствующих настройках.
const formatter = Globalize.numberFormatter({
maximumFractionDigits: 20
});
console.log(formatter(1e-14));
Для преобразования локализованной строки используется
numberParser().
Globalize.locale("ru");
const parser = Globalize.numberParser();
console.log(parser("12 345,67"));
// 12345.67
Globalize автоматически:
parseFloat("12,5");
// 12
Стандартный парсер Jav * aScript:
const parser = Globalize.numberParser();
console.log(parser("12,5"));
// 12.5
При русской локали значение будет обработано правильно.
JavaScript имеет ограничение:
Number.MAX_SAFE_INTEGER
Значение:
9007199254740991
После превышения безопасного диапазона появляются ошибки.
console.log(9007199254740991 + 1);
console.log(9007199254740991 + 2);
Оба результата могут совпасть.
Globalize ориентирован на тип Number.
const value = 12345678901234567890n;
Тип BigInt напрямую не поддерживается форматтерами
чисел.
Обычно используют:
value.toString()
либо специализированные библиотеки.
Для критически важных вычислений применяют:
Пример с decimal.js:
const Decimal = require("decimal.js");
const result = new Decimal(0.1)
.plus(0.2);
console.log(result.toString());
// 0.3
После вычислений результат можно форматировать через Globalize.
const formatter = Globalize.numberFormatter({
maximumFractionDigits: 2
});
console.log(
formatter(Number(result))
);
Разные локали используют разные символы.
| Локаль | Дробный разделитель |
|---|---|
| en | . |
| ru | , |
| fr | , |
| de | , |
Пример:
Globalize.locale("de");
const formatter = Globalize.numberFormatter();
console.log(formatter(1234.56));
// 1.234,56
Globalize автоматически применяет правила локали.
Globalize.locale("en");
console.log(
Globalize.numberFormatter()(1000000)
);
// 1,000,000
Для Индии схема будет иной:
Globalize.locale("en-IN");
console.log(
Globalize.numberFormatter()(1000000)
);
// 10,00,000
const formatter = Globalize.numberFormatter({
useGrouping: false
});
console.log(formatter(1234567.89));
// 1234567.89
Подходит для:
Проценты также используют дробные значения.
const formatter = Globalize.numberFormatter({
style: "percent",
maximumFractionDigits: 2
});
console.log(formatter(0.1234));
// 12.34%
Следует учитывать:
0.1 === 10%
Globalize умножает число на 100 при форматировании процентов.
console.log(formatter(0.005));
// 0.5%
Валюты имеют разные правила дробной части.
const formatter = Globalize.currencyFormatter("USD");
console.log(formatter(12.5));
// $12.50
Японская иена:
const formatter = Globalize.currencyFormatter("JPY");
console.log(formatter(12.5));
// ¥13
У иены отсутствует дробная часть.
const formatter = Globalize.currencyFormatter("USD", {
maximumFractionDigits: 4
});
console.log(formatter(12.34567));
// $12.3457
Опасный пример:
const total = 0.1 + 0.2 + 0.3;
Ошибки накапливаются при:
Распространённая практика:
const cents = 1050;
Вместо:
10.50
Преимущества:
Форматирование:
const amount = cents / 100;
console.log(formatter(amount));
Полезно контролировать конечное значение.
function normalizeMoney(value) {
return Number(value.toFixed(2));
}
Иногда требуется:
const original = 12.3456789;
const formatter = Globalize.numberFormatter({
maximumFractionDigits: 2
});
console.log(formatter(original));
// 12.35
Исходное значение остаётся неизменным.
Создание форматтера — сравнительно дорогая операция.
Нежелательно:
for (const value of values) {
const formatter = Globalize.numberFormatter();
}
Правильный подход:
const formatter = Globalize.numberFormatter();
for (const value of values) {
console.log(formatter(value));
}
В крупных приложениях часто создают фабрики форматтеров.
const cache = {};
function getFormatter(locale) {
if (!cache[locale]) {
Globalize.locale(locale);
cache[locale] =
Globalize.numberFormatter({
maximumFractionDigits: 2
});
}
return cache[locale];
}
Перед форматированием желательно проверять значения.
function isValidNumber(value) {
return typeof value === "number" &&
Number.isFinite(value);
}
console.log(
Globalize.numberFormatter()(NaN)
);
console.log(
Globalize.numberFormatter()(Infinity)
);
Следует заранее определять стратегию отображения:
"—""N/A""Ошибка"const formatter = Globalize.numberFormatter({
minimumFractionDigits: 2
});
console.log(formatter(-12.5));
// -12.50
Особенности IEEE 754 проявляются и здесь.
console.log(Math.round(-1.5));
// -1
Поведение может отличаться от бухгалтерских стандартов округления.
В финансовой сфере иногда используют округление к ближайшему чётному числу.
Пример:
JavaScript такого механизма не предоставляет напрямую.
Для подобных задач обычно используют:
Важно проверять:
Пример тестового набора:
[
0,
0.1,
0.105,
1.005,
9999999.999,
-12.3456
]
Наиболее надёжный подход:
Выполнять вычисления через decimal-библиотеки.
Хранить деньги в минимальных единицах.
Контролировать округление отдельно.
Использовать Globalize только для отображения.
Разделять:
Такой подход минимизирует: