Как округлять числа вверх, вниз или до ближайшего значения
Управляйте тем, как JavaScript округляет десятичные числа при форматировании с использованием различных режимов округления
Введение
При форматировании чисел для отображения часто требуется округлять десятичные значения. Цена $2.567 должна стать $2.57. Измерение в 3.891 метра может отображаться как 4 метра. Способ округления этих чисел влияет на точность, ожидания пользователей и бизнес-логику.
Разные ситуации требуют разных стратегий округления. Иногда нужно округлять в большую сторону, чтобы обеспечить достаточную стоимость продукта. Иногда нужно округлять в меньшую сторону, чтобы уложиться в бюджет. Чаще всего округляют до ближайшего значения для сохранения точности.
JavaScript предоставляет девять различных режимов округления через API Intl.NumberFormat. Эти режимы контролируют, как числа округляются, когда они находятся между двумя возможными значениями. В этом уроке объясняются три наиболее распространенных режима округления и показано, когда использовать остальные.
Как JavaScript округляет числа по умолчанию
Когда вы форматируете число без указания режима округления, JavaScript использует стратегию, называемую halfExpand. Этот режим округляет значения до ближайшего возможного значения, а если число находится ровно посередине между двумя значениями, оно округляется в сторону от нуля.
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 0
});
console.log(formatter.format(2.4));
// Вывод: "2"
console.log(formatter.format(2.5));
// Вывод: "3"
console.log(formatter.format(2.6));
// Вывод: "3"
Значение 2.4 ближе к 2, чем к 3, поэтому оно округляется вниз до 2. Значение 2.6 ближе к 3, чем к 2, поэтому оно округляется вверх до 3. Значение 2.5 находится ровно посередине между 2 и 3, поэтому режим halfExpand округляет его в сторону от нуля до 3.
Такое поведение по умолчанию соответствует тому, чему большинство людей учатся в школе и ожидают в повседневных расчетах. Оно равномерно распределяет ошибки округления по многим вычислениям, что делает его подходящим для общего форматирования чисел.
Понимание, что означает "от нуля"
Фраза "от нуля" описывает направление округления для чисел, которые находятся ровно посередине между двумя возможными значениями. Для положительных чисел округление от нуля означает округление в большую сторону. Для отрицательных чисел округление от нуля означает округление в меньшую сторону, увеличивая модуль числа.
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 0
});
console.log(formatter.format(2.5));
// Вывод: "3"
console.log(formatter.format(-2.5));
// Вывод: "-3"
И 2.5, и -2.5 округляются от нуля. Для 2.5 "от нуля" означает в сторону положительной бесконечности, что дает 3. Для -2.5 "от нуля" означает в сторону отрицательной бесконечности, что дает -3. В обоих случаях модуль числа увеличивается.
Округление вверх с помощью ceil
Режим округления ceil всегда округляет в сторону положительной бесконечности. Для положительных чисел это означает округление вверх. Для отрицательных чисел это означает округление в сторону нуля.
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 0,
roundingMode: "ceil"
});
console.log(formatter.format(2.1));
// Вывод: "3"
console.log(formatter.format(2.9));
// Вывод: "3"
console.log(formatter.format(-2.1));
// Вывод: "-2"
console.log(formatter.format(-2.9));
// Вывод: "-2"
Этот режим полезен, когда нужно гарантировать, что число никогда не будет меньше необходимого. Например, если вы рассчитали, что коробка может вместить 2.3 предмета, вам потребуется 3 коробки. Если вы рассчитали, что задача займет 1.1 дня, вам нужно запланировать 2 дня.
const boxFormatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 0,
roundingMode: "ceil"
});
const itemsPerBox = 5;
const totalItems = 12;
const boxesNeeded = totalItems / itemsPerBox;
console.log(boxFormatter.format(boxesNeeded));
// Вывод: "3"
Расчет дает 2.4 коробки, но вы не можете заказать дробную часть коробки. Округление вверх гарантирует, что у вас будет достаточно емкости.
Округление вниз с помощью floor
Режим округления floor всегда округляет в сторону отрицательной бесконечности. Для положительных чисел это означает округление вниз. Для отрицательных чисел это означает округление в сторону от нуля.
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 0,
roundingMode: "floor"
});
console.log(formatter.format(2.1));
// Вывод: "2"
console.log(formatter.format(2.9));
// Вывод: "2"
console.log(formatter.format(-2.1));
// Вывод: "-3"
console.log(formatter.format(-2.9));
// Вывод: "-3"
Этот режим полезен, когда вам нужны консервативные оценки или когда вы хотите оставаться в пределах лимитов. Например, если у вас есть бюджет $100.87, вы можете отобразить его как $100, чтобы случайно не превысить расходы.
const budgetFormatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
roundingMode: "floor"
});
const availableBudget = 100.87;
console.log(budgetFormatter.format(availableBudget));
// Вывод: "$100"
Округление вниз гарантирует, что отображаемая сумма всегда достижима в рамках фактического бюджета.
Округление до ближайшего с помощью halfExpand
Хотя halfExpand является режимом по умолчанию, вы можете указать его явно, чтобы сделать намерение в коде более очевидным. Этот режим округляет до ближайшего значения и обрабатывает случаи на середине, округляя в сторону от нуля.
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 1,
roundingMode: "halfExpand"
});
console.log(formatter.format(2.14));
// Вывод: "2.1"
console.log(formatter.format(2.15));
// Вывод: "2.2"
console.log(formatter.format(2.16));
// Вывод: "2.2"
Значение 2.14 ближе к 2.1, поэтому оно округляется до 2.1. Значение 2.16 ближе к 2.2, поэтому оно округляется до 2.2. Значение 2.15 находится ровно на середине, поэтому оно округляется в сторону от нуля до 2.2.
Этот режим хорошо подходит для большинства задач форматирования чисел, так как он минимизирует общую ошибку округления при множестве вычислений. Каждое значение на середине имеет равные шансы быть на положительной или отрицательной стороне от нуля, поэтому направление округления уравновешивается со временем.
Сочетание режимов округления с количеством дробных знаков
Режимы округления работают вместе с настройками количества дробных знаков, чтобы контролировать итоговый результат. Опция maximumFractionDigits определяет, сколько десятичных знаков будет отображаться, а roundingMode определяет, как обрабатывать значения, которые находятся между представимыми числами.
const ceilFormatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
roundingMode: "ceil"
});
console.log(ceilFormatter.format(10.001));
// Вывод: "$10.01"
console.log(ceilFormatter.format(10.999));
// Вывод: "$11.00"
При двух десятичных знаках 10.001 нужно округлить либо до 10.00, либо до 10.01. Режим ceil округляет вверх, давая 10.01. Значение 10.999 округляется вверх до 11.00.
Округление к нулю с помощью trunc
Режим округления trunc округляет к нулю, что означает удаление дробной части числа. Для положительных чисел это округление вниз. Для отрицательных чисел это округление вверх.
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 0,
roundingMode: "trunc"
});
console.log(formatter.format(2.1));
// Вывод: "2"
console.log(formatter.format(2.9));
// Вывод: "2"
console.log(formatter.format(-2.1));
// Вывод: "-2"
console.log(formatter.format(-2.9));
// Вывод: "-2"
Этот режим фактически усекает десятичную часть. Он полезен, когда нужно показать только целую часть числа, не учитывая дробное значение для целей округления.
Округление от нуля с помощью expand
Режим округления expand округляет от нуля. Для положительных чисел это округление вверх. Для отрицательных чисел это округление вниз до большего по модулю значения.
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 0,
roundingMode: "expand"
});
console.log(formatter.format(2.1));
// Вывод: "3"
console.log(formatter.format(2.9));
// Вывод: "3"
console.log(formatter.format(-2.1));
// Вывод: "-3"
console.log(formatter.format(-2.9));
// Вывод: "-3"
Этот режим гарантирует, что округление всегда увеличивает модуль числа. Это может быть полезно в финансовых контекстах, где важно быть консервативным в сторону точности, избегая недооценки.
Понимание режимов округления до середины
Пять режимов, начинающихся с half, округляют до ближайшего значения, но различаются тем, как они обрабатывают случаи точного нахождения посередине. Эти режимы позволяют вам точно контролировать поведение при разрешении конфликтов, когда значения находятся ровно между двумя представимыми числами.
Режим halfCeil округляет значения, находящиеся посередине, в сторону положительной бесконечности.
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 0,
roundingMode: "halfCeil"
});
console.log(formatter.format(2.5));
// Вывод: "3"
console.log(formatter.format(-2.5));
// Вывод: "-2"
Режим halfFloor округляет значения, находящиеся посередине, в сторону отрицательной бесконечности.
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 0,
roundingMode: "halfFloor"
});
console.log(formatter.format(2.5));
// Вывод: "2"
console.log(formatter.format(-2.5));
// Вывод: "-3"
Режим halfTrunc округляет значения, находящиеся посередине, в сторону нуля.
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 0,
roundingMode: "halfTrunc"
});
console.log(formatter.format(2.5));
// Вывод: "2"
console.log(formatter.format(-2.5));
// Вывод: "-2"
Режим halfEven округляет значения, находящиеся посередине, до ближайшего четного числа. Этот режим иногда называют банковским округлением, так как он уменьшает смещение в финансовых расчетах.
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 0,
roundingMode: "halfEven"
});
console.log(formatter.format(2.5));
// Вывод: "2"
console.log(formatter.format(3.5));
// Вывод: "4"
console.log(formatter.format(4.5));
// Вывод: "4"
Значение 2.5 округляется до 2, так как 2 — четное число. Значение 3.5 округляется до 4, так как 4 — четное число. Значение 4.5 также округляется до 4, так как 4 — четное число.
Выбор режима округления для вашего приложения
Выбор режима округления зависит от ваших бизнес-требований и характера отображаемых данных. Разные контексты требуют разных стратегий.
Используйте ceil, когда нужно обеспечить достаточность. Планирование емкости, подсчет запасов и оценка времени часто требуют округления вверх, чтобы гарантировать наличие достаточных ресурсов.
Используйте floor, когда нужно оставаться в пределах. Отображение бюджета, отслеживание квот и расчеты скидок часто требуют округления вниз, чтобы избежать превышения доступных ресурсов.
Используйте halfExpand для общего отображения, где важна точность, но не требуется экстремальная точность. Это значение по умолчанию, так как оно хорошо подходит для большинства задач форматирования чисел.
Используйте halfEven для финансовых расчетов, где нужно минимизировать накопительное смещение из-за округления. Этот режим гарантирует, что при большом количестве расчетов ошибки округления не будут систематически склоняться в одну сторону.
Используйте trunc, когда нужно отображать только целую часть числа без применения логики округления к дробной части.
Использование режимов округления при форматировании валюты
Режимы округления естественно взаимодействуют с форматированием валюты. Разные компании имеют разные правила округления валютных значений, и опция roundingMode позволяет реализовать эти правила.
const retailPrice = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
roundingMode: "ceil"
});
const wholesalePrice = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
roundingMode: "floor"
});
const calculatedPrice = 19.874;
console.log(retailPrice.format(calculatedPrice));
// Вывод: "$19.88"
console.log(wholesalePrice.format(calculatedPrice));
// Вывод: "$19.87"
Розничный продавец может округлять цены вверх для обеспечения прибыльности, в то время как оптовик может округлять вниз, чтобы оставаться конкурентоспособным. Одно и то же рассчитанное значение цены приводит к разным отображаемым значениям в зависимости от бизнес-правил, закодированных в режиме округления.