Группировка цифр

Механизм локализации числового форматирования

Форматирование чисел в разных языках и регионах опирается на различные правила разделения разрядов. В одних системах используется разделение тысяч через запятую, в других — через пробел, в третьих — применяется нестандартная схема группировки, например индийская (2–2–3 вместо классической 3–3–3).

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

Основной механизм:

new Intl.NumberFormat(locale).format(number)

Поведение группировки управляется внутренними правилами CLDR (Common Locale Data Repository), а не фиксированными настройками JavaScript.


Стандартная группировка тысяч

Наиболее распространённая схема — группировка по три цифры справа налево.

Пример:

new Intl.NumberFormat("en-US").format(1234567890);
// 1,234,567,890

Для разных локалей результат отличается:

new Intl.NumberFormat("de-DE").format(1234567890);
// 1.234.567.890
new Intl.NumberFormat("fr-FR").format(1234567890);
// 1 234 567 890

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


Управление группировкой через useGrouping

Опция useGrouping контролирует включение или отключение разделения разрядов.

new Intl.NumberFormat("en-US", { useGrouping: true }).format(1234567);
// 1,234,567

Отключение группировки:

new Intl.NumberFormat("en-US", { useGrouping: false }).format(1234567);
// 1234567

useGrouping может принимать дополнительные режимы в современных реализациях:

  • "always" — всегда применять группировку
  • "auto" — применять по умолчанию
  • "min2" — группировать только при числе ≥ 10000 (зависит от движка)
  • "true" / "false" — логический режим (устаревающий стиль)

Поведение зависит от реализации ECMAScript-движка, но концепция остаётся одинаковой: контроль включения разрядных разделителей.


Локальные особенности группировки

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

Индийская система (lakhs, crores)

new Intl.NumberFormat("en-IN").format(123456789);
// 12,34,56,789

Группировка:

  • первая группа: 3 цифры
  • далее: каждые 2 цифры

Эта система сохраняется даже при больших числах и применяется автоматически.


Восточноазиатские локали

В китайской и японской нотации часто отсутствует привычная западная группировка:

new Intl.NumberFormat("zh-CN").format(123456789);
// 123,456,789 (или без разделителей в некоторых форматах)

В некоторых случаях локаль может предпочитать отсутствие разделителей при определённых стилях форматирования.


Арабские локали и направление записи

new Intl.NumberFormat("ar-EG").format(123456789);
// ١٢٣٬٤٥٦٬٧٨٩

Особенности:

  • использование арабско-индийских цифр
  • иной символ разделителя групп
  • поддержка RTL-направления отображения

Разделители и форматирование через formatToParts

Intl.NumberFormat позволяет не только получать строку, но и структуру компонентов.

new Intl.NumberFormat("en-US").formatToParts(1234567);

Результат:

[
  { type: "integer", value: "1" },
  { type: "group", value: "," },
  { type: "integer", value: "234" },
  { type: "group", value: "," },
  { type: "integer", value: "567" }
]

Типы частей:

  • integer — числовые сегменты
  • group — разделитель разрядов
  • decimal — десятичный разделитель
  • fraction — дробная часть
  • literal — служебные символы

Этот механизм позволяет полностью контролировать отображение чисел без изменения локальной логики форматирования.


Пользовательская модификация группировки

Хотя Intl API не предоставляет прямой настройки символа разделителя, formatToParts позволяет реконструировать строку:

const formatter = new Intl.NumberFormat("en-US");

const parts = formatter.formatToParts(1234567);

const custom = parts
  .map(p => (p.type === "group" ? " " : p.value))
  .join("");

Результат:

1 234 567

Такой подход сохраняет локальные правила группировки, но меняет визуальное представление.


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

Группировка тесно связана с форматированием дробной части:

new Intl.NumberFormat("en-US", {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
}).format(1234567);

Результат:

1,234,567.00

При увеличении дробной части группировка целой части остаётся неизменной, но добавляется дополнительный разделитель:

new Intl.NumberFormat("en-US", {
  minimumFractionDigits: 3
}).format(1234567.1);
// 1,234,567.100

Влияние numberingSystem

Система счисления влияет на отображение цифр, но не на группировку.

new Intl.NumberFormat("en-US", {
  numberingSystem: "arab"
}).format(1234567);

Результат:

١,٢٣٤,٥٦٧

Группировка сохраняется, но символы заменяются на соответствующие выбранной системе.


Поведение при больших числах

Для очень больших значений форматирование остаётся стабильным:

new Intl.NumberFormat("en-US").format(1e20);
// 100,000,000,000,000,000,000

Однако в некоторых локалях возможны ограничения визуального форматирования при использовании compact-режимов:

new Intl.NumberFormat("en-US", {
  notation: "compact"
}).format(1234567890);
// 1.2B

В компактной записи классическая группировка заменяется на сокращённую форму.


Взаимодействие группировки с другими режимами

Группировка сохраняется или модифицируется в зависимости от режима форматирования:

  • standard — классическая группировка
  • scientific — экспоненциальная форма, группировка отсутствует
  • engineering — группировка заменяется экспонентой
  • compact — группировка заменяется сокращениями

Пример научной нотации:

new Intl.NumberFormat("en-US", {
  notation: "scientific"
}).format(1234567);
// 1.234567E6

Производительность и кеширование форматтеров

Создание Intl.NumberFormat — относительно дорогая операция. При массовом форматировании чисел используется повторное применение одного экземпляра:

const formatter = new Intl.NumberFormat("en-US");

const values = [1000, 20000, 300000];

values.map(v => formatter.format(v));

Это особенно важно при работе с большими массивами данных, где группировка применяется многократно.


Поведение в разных движках

Хотя спецификация ECMAScript стандартизирует API, реализация может отличаться:

  • различия в поддержке useGrouping режимов
  • небольшие расхождения в локалях CLDR
  • различия в форматировании крайних значений
  • особенности fallback локалей при отсутствии данных

Несмотря на это, базовая логика группировки остаётся стабильной: она определяется локалью и управляется через Intl API без необходимости ручной реализации разделителей.