Форматирование диапазонов чисел

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

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

Ключевые методы:

  • formatRange(start, end)
  • formatRangeToParts(start, end)

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

formatRange: базовое форматирование интервала

Метод formatRange формирует строковое представление диапазона чисел с учётом локали.

const formatter = new Intl.NumberFormat('ru-RU');

formatter.formatRange(1, 5);
// "1–5"

В англоязычной локали результат будет аналогичен:

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

formatter.formatRange(1, 5);
// "1–5"

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

Округление внутри диапазонов

Округление применяется к каждому значению независимо, но с учётом настроек форматтера:

const formatter = new Intl.NumberFormat('en-US', {
  maximumFractionDigits: 0
});

formatter.formatRange(1.2, 5.7);
// "1–6"

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

Поведение при одинаковых значениях

Если границы диапазона совпадают, результатом становится одиночное значение:

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

formatter.formatRange(5, 5);
// "5"

Это важное поведение, предотвращающее избыточное отображение интервала.

formatRangeToParts: структурированный результат

Метод formatRangeToParts возвращает не строку, а массив структурированных частей, что позволяет точно контролировать визуализацию диапазона.

const formatter = new Intl.NumberFormat('ru-RU');

formatter.formatRangeToParts(10, 20);

Результат:

[
  { type: 'integer', value: '10', source: 'startRange' },
  { type: 'literal', value: '–', source: 'shared' },
  { type: 'integer', value: '20', source: 'endRange' }
]

Поле source

Поле source указывает происхождение части:

  • startRange — первая граница
  • endRange — вторая граница
  • shared — общие элементы (например, разделитель)

Это позволяет дифференцировать стили для каждой части диапазона.

Локализация разделителя диапазона

Разделитель диапазона не фиксирован и зависит от локали. В разных языковых системах используются разные символы:

  • en-US:
  • ru-RU:
  • de-DE:
  • fr-FR: (с пробелами в некоторых контекстах)

Несмотря на визуальную схожесть, внутренние правила могут включать:

  • неразрывные пробелы
  • специфические типографические требования
  • разные правила группировки чисел

Диапазоны с единицами измерения

Intl.NumberFormat поддерживает форматирование диапазонов с единицами через style: 'unit'.

const formatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer-per-hour'
});

formatter.formatRange(10, 50);
// "10–50 km/h"

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

Диапазоны дат и чисел: различия модели

Хотя API диапазонов существует и в Intl.DateTimeFormat, в Intl.NumberFormat модель проще: она оперирует чистыми числами без календарной логики.

Это приводит к различиям:

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

Поведение с большими числами

Для больших значений форматирование диапазонов учитывает группировку:

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

formatter.formatRange(1000, 5000);
// "1,000–5,000"

Группировка определяется локалью и может включать пробелы вместо запятых:

const formatter = new Intl.NumberFormat('de-DE');

formatter.formatRange(1000, 5000);
// "1.000–5.000"

Граничные случаи

Перевёрнутый диапазон

Если начало больше конца, поведение не нормализуется автоматически:

formatter.formatRange(10, 5);
// "10–5"

Нормализация диапазона должна выполняться вручную при необходимости.

NaN и Infinity

Некорректные значения приводят к строковым представлениям, зависящим от реализации:

formatter.formatRange(NaN, 10);
// "NaN–10"
formatter.formatRange(Infinity, 10);
// "∞–10"

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

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

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

function formatRange(a, b) {
  return formatter.formatRange(a, b);
}

Это особенно важно при рендеринге списков, таблиц и UI-компонентов.

Отличие formatRange от ручной конкатенации

Ручная сборка строк:

`${a} - ${b}`

приводит к ряду проблем:

  • отсутствие локализации
  • неправильные пробелы и тире
  • невозможность адаптации под разные языки
  • отсутствие единообразного округления

formatRange решает эти проблемы на уровне спецификации ECMAScript Internationalization API.

Совместимость и эволюция API

Поддержка formatRange и formatRangeToParts появилась в более поздних версиях спецификации ECMAScript. В старых средах доступны только базовые методы форматирования чисел, что требует полифиллов или ручной логики.

При отсутствии поддержки часто применяется стратегия:

  • форматирование двух чисел отдельно
  • соединение через локализованный разделитель
  • ручная синхронизация настроек форматтера

Однако такое решение не эквивалентно нативному API, поскольку теряет семантическую связь диапазона.

Использование в интерфейсах данных

Диапазонное форматирование применяется в интерфейсах:

  • фильтры цен
  • диапазоны статистики
  • шкалы показателей
  • отображение результатов поиска
  • аналитические панели

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