При работе с денежными значениями проблема округления возникает практически в каждой финансовой системе. Разные валюты используют различные правила отображения дробной части:
Библиотека Globalize использует данные CLDR и позволяет автоматически применять корректные правила округления для конкретной локали и валюты.
Базовое форматирование валют выполняется через метод
currencyFormatter().
const formatter = Globalize.currencyFormatter("USD");
formatter(10.235);
// "$10.24"
Значение 10.235 автоматически округляется до двух знаков
после запятой, поскольку для доллара США стандартом является точность до
центов.
Для корректной работы валютного форматирования необходимо загрузить данные CLDR.
const Globalize = require("globalize");
Globalize.load(
require("cldr-data/main/en/numbers"),
require("cldr-data/main/en/currencies"),
require("cldr-data/supplemental/currencyData"),
require("cldr-data/supplemental/likelySubtags")
);
Globalize.locale("en");
Без загрузки currencyData библиотека не сможет
определить количество дробных разрядов и правила округления для
валют.
Одно из главных преимуществ Globalize — отсутствие необходимости вручную задавать число знаков после запятой.
const usd = Globalize.currencyFormatter("USD");
const jpy = Globalize.currencyFormatter("JPY");
const bhd = Globalize.currencyFormatter("BHD");
console.log(usd(100.555));
// $100.56
console.log(jpy(100.555));
// ¥101
console.log(bhd(100.555));
// BHD 100.555
Globalize использует информацию из CLDR:
| Валюта | Количество знаков |
|---|---|
| USD | 2 |
| JPY | 0 |
| BHD | 3 |
Globalize использует стандартное математическое округление.
const formatter = Globalize.currencyFormatter("USD");
console.log(formatter(10.234));
// $10.23
console.log(formatter(10.235));
// $10.24
console.log(formatter(10.236));
// $10.24
Логика:
Округление применяется одинаково и для отрицательных чисел.
const formatter = Globalize.currencyFormatter("USD");
console.log(formatter(-10.235));
// -$10.24
Globalize корректно работает с крупными денежными значениями.
const formatter = Globalize.currencyFormatter("EUR");
console.log(formatter(987654321.987));
// €987,654,321.99
При этом:
Округление и количество знаков зависят от валюты, а не от локали. Однако сама запись числа меняется в зависимости от региона.
Globalize.locale("en");
const enFormatter =
Globalize.currencyFormatter("EUR");
console.log(enFormatter(10.235));
// €10.24
Globalize.locale("de");
const deFormatter =
Globalize.currencyFormatter("EUR");
console.log(deFormatter(10.235));
// 10,24 €
Изменяются:
Но правило округления остаётся одинаковым.
По умолчанию Globalize использует стандарт валюты, однако точность можно переопределить вручную.
const formatter = Globalize.numberFormatter({
style: "currency",
currency: "USD",
minimumFractionDigits: 4,
maximumFractionDigits: 4
});
console.log(formatter(10.23567));
// $10.2357
Здесь:
Эти параметры особенно важны для финансовых систем.
Определяет минимальное количество знаков после запятой.
const formatter = Globalize.numberFormatter({
style: "currency",
currency: "USD",
minimumFractionDigits: 2
});
console.log(formatter(10));
// $10.00
Определяет максимальное количество знаков.
const formatter = Globalize.numberFormatter({
style: "currency",
currency: "USD",
maximumFractionDigits: 1
});
console.log(formatter(10.28));
// $10.3
Иногда требуется полностью убрать дробную часть.
const formatter = Globalize.numberFormatter({
style: "currency",
currency: "USD",
maximumFractionDigits: 0
});
console.log(formatter(10.9));
// $11
Globalize не реализует банковское округление
(banker's rounding) напрямую.
Стандартное округление:
1.5 -> 2
2.5 -> 3
3.5 -> 4
Банковское округление:
1.5 -> 2
2.5 -> 2
3.5 -> 4
Для таких сценариев округление обычно выполняется отдельно, а затем результат передаётся в Globalize для форматирования.
Следует учитывать ограничения чисел с плавающей точкой в JavaScript.
Пример:
console.log(0.1 + 0.2);
// 0.30000000000000004
Это влияет и на валютные вычисления.
const formatter =
Globalize.currencyFormatter("USD");
console.log(formatter(0.1 + 0.2));
// $0.30
Хотя результат выглядит корректно, внутреннее значение содержит ошибку представления.
В серьёзных финансовых приложениях часто используют:
Пример совместного использования:
const Decimal = require("decimal.js");
const value =
new Decimal("10.235")
.toDecimalPlaces(2);
const formatter =
Globalize.currencyFormatter("USD");
console.log(formatter(value.toNumber()));
// $10.24
Такой подход:
После конвертации часто появляются длинные дробные значения.
const usdToEur = 0.923451;
const result = 100 * usdToEur;
console.log(result);
// 92.3451
Globalize автоматически округлит значение согласно валюте.
const formatter =
Globalize.currencyFormatter("EUR");
console.log(formatter(result));
// €92.35
Финансовые операции нередко требуют разных правил округления на разных этапах.
Пример:
const subtotal = 10.235;
const tax = subtotal * 0.2;
const formatter =
Globalize.currencyFormatter("USD");
console.log(formatter(tax));
// $2.05
Важно понимать разницу между:
Даже небольшие различия могут приводить к расхождению итогов.
Создание форматтера — относительно дорогая операция.
Неправильно:
function formatPrice(value) {
return Globalize
.currencyFormatter("USD")(value);
}
Правильно:
const usdFormatter =
Globalize.currencyFormatter("USD");
function formatPrice(value) {
return usdFormatter(value);
}
Это особенно важно:
Иногда требуется только округление, без отображения денежного символа.
const formatter = Globalize.numberFormatter({
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
console.log(formatter(10.235));
// 10.24
Современный JavaScript предоставляет встроенный API:
const formatter =
new Intl.NumberFormat("en", {
style: "currency",
currency: "USD"
});
console.log(formatter.format(10.235));
// $10.24
Однако Globalize предоставляет:
Globalize ожидает корректные числовые значения.
const formatter =
Globalize.currencyFormatter("USD");
console.log(formatter(NaN));
// NaN
Перед форматированием обычно выполняется валидация:
function safeFormat(value) {
if (typeof value !== "number") {
return "";
}
if (Number.isNaN(value)) {
return "";
}
return usdFormatter(value);
}
Часто создают обёртки над Globalize.
function createCurrencyFormatter(currency) {
const formatter =
Globalize.currencyFormatter(currency);
return value => formatter(value);
}
Использование:
const formatUsd =
createCurrencyFormatter("USD");
console.log(formatUsd(19.999));
// $20.00
Распространённая практика — хранить деньги в минимальных единицах.
Пример:
const cents = 1099;
const dollars = cents / 100;
const formatter =
Globalize.currencyFormatter("USD");
console.log(formatter(dollars));
// $10.99
Преимущества:
const priceFormatter =
Globalize.currencyFormatter("USD");
const products = [
{ name: "Keyboard", price: 99.995 },
{ name: "Mouse", price: 25.499 },
{ name: "Monitor", price: 199.991 }
];
products.forEach(product => {
console.log(
`${product.name}: ${
priceFormatter(product.price)
}`
);
});
Результат:
Keyboard: $100.00
Mouse: $25.50
Monitor: $199.99
const formatters = {
USD: Globalize.currencyFormatter("USD"),
EUR: Globalize.currencyFormatter("EUR"),
JPY: Globalize.currencyFormatter("JPY")
};
function formatMoney(value, currency) {
return formatters[currency](value);
}
console.log(formatMoney(10.235, "USD"));
// $10.24
console.log(formatMoney(10.235, "EUR"));
// €10.24
console.log(formatMoney(10.235, "JPY"));
// ¥10
Globalize автоматически применяет нужные правила округления для каждой валюты.