Правила округления валют

Округление валютных значений в международной локализации в JavaScript определяется не только математическими правилами, но и набором стандартов, учитывающих валюту, региональные соглашения и специфику отображения денежных единиц. Основным инструментом выступает Intl.NumberFormat, поведение которого при работе с валютами значительно отличается от обычного форматирования чисел.


При использовании режима форматирования style: "currency" округление определяется сразу несколькими уровнями:

  • стандартом валюты (ISO 4217),
  • настройками локали,
  • параметрами Intl.NumberFormat,
  • и внутренними правилами движка JavaScript.

Каждая валюта имеет фиксированное количество знаков после запятой, называемое minor unit.

Например:

  • USD → 2 знака (центах)
  • JPY → 0 знаков (иены)
  • KWD → 3 знака (филсы)

Это поведение применяется автоматически:

new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
}).format(10.235);

Результат:

$10.23

Округление произошло по стандартному правилу валюты USD.


Минимальные и максимальные знаки дробной части

Ключевую роль играют параметры:

  • minimumFractionDigits
  • maximumFractionDigits

При валютном стиле они по умолчанию устанавливаются на основе валюты.

new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "JPY"
}).format(1234.56);

Результат:

¥1,235

Несмотря на отсутствие дробной части у JPY, значение было округлено до ближайшего целого числа.

Если переопределить поведение:

new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 0
}).format(10.75);

Результат:

$11

Система принудительно игнорирует стандарт валюты и применяет пользовательское правило.


Алгоритмы округления

Современная спецификация ECMA-402 (Intl API) вводит параметр roundingMode, который управляет стратегией округления.

Поддерживаемые режимы:

  • ceil — всегда вверх
  • floor — всегда вниз
  • expand — в сторону увеличения по абсолютному значению
  • trunc — усечение
  • halfCeil — половина вверх
  • halfFloor — половина вниз
  • halfExpand — классическое банковское округление вверх при 0.5
  • halfTrunc — половина к нулю
  • halfEven — банковское округление к ближайшему чётному

Пример:

new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  roundingMode: "halfEven",
  maximumFractionDigits: 2
}).format(1.235);

Результат зависит от стратегии округления и может отличаться от привычного toFixed.


Влияние ISO 4217 на точность

Каждая валюта в стандарте ISO 4217 содержит поле minor unit digits, которое автоматически определяет:

  • количество знаков после запятой,
  • базовую точность округления,
  • поведение при форматировании.

Примеры:

Валюта Minor units Пример результата
USD 2 10.23
JPY 0 10
BHD 3 10.235

При этом библиотека не хранит эти данные внутри JavaScript — они подгружаются из ICU (International Components for Unicode), используемого движком.


Переопределение валютных правил

Разработчик может принудительно изменить стандартное округление:

new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "JPY",
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
}).format(1234.5);

Результат:

¥1,234.50

В этом случае форматирование перестаёт соответствовать реальным стандартам валюты и становится чисто визуальным.


Сценарии банковского округления

Банковские и финансовые системы часто используют halfEven, чтобы минимизировать накопление ошибок при больших массивах операций.

new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  roundingMode: "halfEven",
  maximumFractionDigits: 2
}).format(2.675);

Поведение может отличаться от Math.round, поскольку используется контекстная арифметика округления, а не стандартное преобразование числа.


Внутренние этапы обработки значения

При форматировании валюты происходит последовательная цепочка преобразований:

  1. Приведение числа к decimal-формату ICU.
  2. Применение правил валюты (minor units).
  3. Применение minimumFractionDigits / maximumFractionDigits.
  4. Применение roundingMode.
  5. Формирование строки в соответствии с локалью.
  6. Добавление символа валюты и группировок.

Важно, что округление происходит до этапа локализации строки, а не после.


Различия между toLocaleString и Intl.NumberFormat

toLocaleString является обёрткой над Intl.NumberFormat, но поведение при округлении идентично:

(10.235).toLocaleString("en-US", {
  style: "currency",
  currency: "USD"
});

Тем не менее Intl.NumberFormat предпочтительнее при массовом форматировании из-за возможности переиспользования экземпляра.


Особенности работы с очень большими и точными числами

JavaScript использует IEEE 754, поэтому значения вроде:

0.1 + 0.2

могут давать неточное представление до округления. Intl API компенсирует это на этапе форматирования:

new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 2
}).format(0.1 + 0.2);

Результат:

$0.30

Связь округления с отображением валютного знака

Округление и отображение символа валюты связаны, но независимы. Например:

  • округление влияет только на числовую часть,
  • символ валюты добавляется после вычислений,
  • позиция символа зависит от локали ("en-US" vs "ru-RU").
new Intl.NumberFormat("ru-RU", {
  style: "currency",
  currency: "USD"
}).format(1234.5);

Результат:

1 234,50 $

Поведение при отсутствии дробных единиц

Для валют без дробной части система полностью подавляет десятичную запись:

new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "JPY"
}).format(99.99);

Результат:

¥100

Даже если передано дробное значение, оно приводится к целому по правилам округления валюты.


Взаимодействие с BigInt

Intl.NumberFormat поддерживает BigInt, но правила округления применяются только к дробным типам. Для целых значений округление не выполняется:

new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
}).format(12345678901234567890n);

Результат:

$12,345,678,901,234,567,890.00

Дробная часть добавляется автоматически из-за валютного формата USD.


Итоговое поведение системы округления

Система округления валют в Intl API представляет собой многоуровневую модель, где итоговое значение формируется не одной функцией, а комбинацией стандартов ICU, правил ISO 4217 и пользовательских параметров форматирования.