Как выполнить округление до десятых в JavaScript

Округление до десятых в JavaScript выполняйте через масштабирование: умножьте значение на десять, примените Math.round(), Math.floor() или Math.ceil(), затем разделите результат на десять. Для округления до целого используйте те же методы без масштабирования.

Десятичные дроби требуют отдельного внимания из-за двоичного представления чисел. Разберем работу с десятичными разрядами, сравнение с погрешностью и расчеты в целых копейках.

МетодЧто возвращаетНаправление округленияПоведение на отрицательныхПоведение на половинках
Math.round(x)ЧислоК ближайшему целомуMath.round(-2.7) → -32.5 → 3, -2.5 → -2; к +∞
Math.floor(x)ЧислоК −∞Math.floor(-2.7) → -32.5 → 2, -2.5 → -3
Math.ceil(x)ЧислоК +∞Math.ceil(-2.7) → -22.5 → 3, -2.5 → -2
Math.trunc(x)ЧислоК нулю, отбрасывает дробьMath.trunc(-2.7) → -22.5 → 2, -2.5 → -2
x.toFixed(d)СтрокуК ближайшему значению с d знаками после точкиСохраняет знак; (-2.7).toFixed(0) → "-3"Точные половинки от нуля: (-2.5).toFixed(0) → "-3"
x.toPrecision(p)СтрокуК ближайшему значению с p значащими цифрамиСохраняет знак; (-2.7).toPrecision(1) → "-3"Точные половинки от нуля: (-2.5).toPrecision(1) → "-3"

Для чего округлять числа?

Тип Number хранит числа в двоичном формате IEEE 754 двойной точности. Целые числа до 2 ** 53 по модулю представлены точно. Однако безопасный диапазон целых заканчивается на 2 ** 53 - 1: за ним соседние целые уже могут совпадать.

Многие десятичные дроби не имеют конечного двоичного представления. JavaScript сохраняет ближайшее доступное значение, поэтому вычисления иногда дают неожиданный хвост:

console.log(0.1 + 0.2);
// 0.30000000000000004

console.log(0.1 * 0.2);
// 0.020000000000000004

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

Запускайте примеры в консоли инструментов разработчика браузера. Строки после // показывают ожидаемый результат и не мешают выполнению кода.

Округление до целых чисел

Метод Math.round() выполняет округление до целых по ближайшему значению:

console.log(Math.round(4.3));
// 4

console.log(Math.round(4.5));
// 5

console.log(Math.round(-2.5));
// -2

При равном расстоянии до соседних целых метод выбирает направление +∞. Поэтому отрицательная половинка -2.5 превращается в -2. Правило «половинки от нуля» для Math.round() не подходит.

Метод также может вернуть отрицательный ноль при округлении отрицательных дробей:

console.log(Object.is(Math.round(-0.2), -0));
// true

Округление в меньшую сторону до ближайшего целого числа

Если вы хотите округлять в меньшую сторону, используйте метод Math.floor():

console.log(Math.floor(42.23));
// 42

console.log(Math.floor(36.93));
// 36

console.log(Math.floor(-2.5));
// -3

Метод Math.floor() всегда выбирает целое в направлении −∞. Для отрицательного числа результат становится меньше исходного значения. Такой способ подходит, например, для расчета количества полностью заполненных упаковок.

Для отбрасывания дробной части используйте Math.trunc(), который движется к нулю:

console.log(Math.trunc(-41.43));
// -41

console.log(Math.floor(-41.43));
// -42

Округление в большую сторону до ближайшего целого числа

Метод Math.ceil() выполняет округление вверх, то есть в направлении +∞. Используйте его, когда неполная единица тоже требует отдельного места. Например, остаток товаров потребует еще одной коробки.

console.log(Math.ceil(42.23));
// 43

console.log(Math.ceil(36.93));
// 37

console.log(Math.ceil(-36.93));
// -36

const items = 23;
const boxCapacity = 10;
console.log(Math.ceil(items / boxCapacity));
// 3

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

Округление до сотых и десятичных чисел

Методы toFixed() и toPrecision() возвращают округленное строковое представление числа. Первый задает количество знаков после точки, второй – количество значащих цифр.

  • toFixed(d) принимает целое значение от 0 до 100. Без аргумента метод использует 0 знаков после точки.
  • toPrecision(p) считает все значащие цифры, включая целую часть. Без аргумента метод возвращает обычное строковое представление числа.
const randNum = 6.25;
console.log(randNum.toFixed());
// "6"

console.log(Math.PI.toPrecision(1));
// "3"

console.log((87.337).toPrecision(3));
// "87.3"

const wholeNum = 1;
const dollarsCents = wholeNum.toFixed(2);
console.log(dollarsCents);
// "1.00"

Методы округляют значение, поэтому для простого усечения они не подходят.

Округление чисел до сотых и десятых

Чтобы понять, как округлять числа до сотых, используйте метод toFixed(1) для десятых и аргумент 2 для сотых:

const value = 6.256;
console.log(value.toFixed(1));
// "6.3"

console.log(value.toFixed(2));
// "6.26"

const roundedNumber = Number(value.toFixed(2));
console.log(roundedNumber);
// 6.26

Для преобразования строки в число также подходит parseFloat(). Однако число не хранит завершающие нули: Number("1.00") вернет 1. Если вам нужны два видимых знака, сохраняйте строковый результат.

Сравните сложение со строкой и числом на примере двух объявленных переменных:

const randNum = 6.25;
const rounded = randNum.toFixed();

console.log(randNum + rounded);
// "6.256"

console.log(randNum + Number(rounded));
// 12.25

Значащие цифры и экспоненциальная запись

Метод toPrecision() может вернуть число в экспоненциальной записи. Например, двух значащих цифр недостаточно для обычной записи следующего значения:

const num = 123.435;
console.log(num.toPrecision(2));
// "1.2e+2"

У toFixed() тоже есть особенность для очень больших чисел. При модуле от 1e21 метод использует обычное преобразование числа в строку. Поэтому фиксированные знаки после точки в таком случае не гарантированы.

Как избежать ошибок при округлении десятичных дробей

Значение 1.005 хранится чуть ниже точной десятичной дроби: примерно 1.0049999999999999. Метод toFixed(2) округляет именно сохраненное двоичное значение:

const numTest = 1.005;
console.log(numTest.toFixed(2));
// "1.00"

Метод возвращает строку "1.00", сохраняя оба знака после точки.

Для некоторых задач помогает десятичный сдвиг через экспоненциальную запись. Короткая функция собирает запись числа, сдвигает точку и применяет Math.round():

function round(value, decimals) {
    return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
}

console.log(round(1.005, 2));
// 1.01

console.log(round(1e-7, 8));
// NaN

Короткая функция ломается на числах, уже записанных через экспоненту. Для 1e-7 она собирает некорректную строку "1e-7e8". Аналогичная проблема возможна при обратном сдвиге большого промежуточного результата.

Разбирайте существующий показатель степени перед каждым сдвигом. Следующая функция принимает конечное число и целую точность от 0 до 100:

function shiftDecimal(value, places) {
    const [coefficient, exponent = '0'] = String(value).split('e');
    return Number(`${coefficient}e${Number(exponent) + places}`);
}

function roundDecimal(value, decimals = 0) {
    if (!Number.isFinite(value)) {
        throw new TypeError('value must be finite');
    }
    if (!Number.isInteger(decimals) || decimals < 0 || decimals > 100) {
        throw new RangeError('decimals must be an integer from 0 to 100');
    }

    const shifted = shiftDecimal(value, decimals);
    if (!Number.isFinite(shifted)) {
        throw new RangeError('Decimal shift overflow');
    }
    return shiftDecimal(Math.round(shifted), -decimals);
}

console.log(roundDecimal(1.005, 2));
// 1.01

console.log(roundDecimal(1e-7, 8));
// 1e-7

console.log(roundDecimal(-2.5, 0));
// -2

Функция сохраняет правило половинок Math.round() и учитывает экспоненциальную запись. Но она не восстанавливает точность, потерянную в предыдущих вычислениях. Очень большие промежуточные значения также могут потерять значащие цифры. Для точной десятичной арифметики этот прием не заменяет специализированную библиотеку.

Округление с помощью эпсилона

Константа Number.EPSILON описывает расстояние между 1 и следующим большим числом типа Number. Ее используют при выборе допуска для сравнения дробей. Свойства Math.EPSILON в JavaScript нет.

console.log(0.1 + 0.2 === 0.3);
// false

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

function epsEqu(x, y, absTolerance = Number.EPSILON,
                relTolerance = Number.EPSILON) {
    if (!Number.isFinite(x) || !Number.isFinite(y)) {
        return false;
    }
    return Math.abs(x - y) <= Math.max(
        absTolerance,
        relTolerance * Math.max(Math.abs(x), Math.abs(y))
    );
}

console.log(epsEqu(0.1 + 0.2, 0.3));
// true

console.log(epsEqu(0, 0));
// true

Подбирайте допуски под точность измерений и накопленную погрешность вычислений. Значения по умолчанию подходят для простого примера со сложением дробей. Длинная цепочка расчетов может потребовать большего допуска.

Добавление эпсилона также помогает округлить некоторые положительные дроби до сотых:

const x = 1.005;
const rounded = Math.round((x + Number.EPSILON) * 100) / 100;
console.log(rounded);
// 1.01

Этот прием не гарантирует правильный результат для любого числа. При большом масштабе прибавление эпсилона может вообще не изменить значение. Для отрицательных половинок добавка тоже не задает симметричное округление от нуля.

Усечение десятичных чисел

Метод Math.trunc() отбрасывает дробную часть без выбора ближайшего целого. Для усечения до сотых умножьте число на 100. Затем отбросьте дробь и разделите результат на 100.

function truncated(num, decimalPlaces = 2) {
    const factor = 10 ** decimalPlaces;
    return Math.trunc(num * factor) / factor;
}

console.log(truncated(3.1416));
// 3.14

console.log(truncated(-3.1416));
// -3.14

console.log(truncated(35.874993, 3));
// 35.874

Функция рассчитана на конечные числа и небольшую неотрицательную целую точность. Умножение дроби тоже может вносить двоичную погрешность. Если результат находится почти на границе усечения, проверьте подход на ваших входных значениях.

Не заменяйте Math.trunc() побитовым оператором ~~. Побитовые операции преобразуют значение в знаковое 32-битное целое. Произведение больше 2147483647 уже выходит за положительную границу этого диапазона.

Округление до ближайшего кратного числа

Для округления к шагу разделите число на величину шага. Затем округлите частное и умножьте результат на тот же шаг:

function roundToMultiple(num, multiple) {
    if (!Number.isFinite(multiple) || multiple <= 0) {
        throw new RangeError('multiple must be positive and finite');
    }
    return Math.round(num / multiple) * multiple;
}

console.log(roundToMultiple(11, 5));
// 10

console.log(roundToMultiple(17, 10));
// 20

console.log(roundToMultiple(-12.5, 5));
// -10

Чтобы округлять значения только в большую или меньшую сторону, замените в функции round на ceil или floor.

Дробный шаг 0.01 не гарантирует точного десятичного результата. Деление и последующее умножение могут оставить плавающую погрешность:

console.log(roundToMultiple(0.285, 0.01));
// 0.28

Поэтому для денежных расчетов удобнее задавать шаг в целых копейках.

Привязка к диапазону

Функция clamp() ограничивает значение нижней и верхней границами. Например, ввод 123 можно привести к диапазону от 1 до 100. Значение внутри диапазона функция оставит без изменений.

Методы Math.min() и Math.max() возвращают наименьший и наибольший аргументы соответственно:

function clamp(value, min, max) {
    if (min > max) {
        throw new RangeError('min must not exceed max');
    }
    return Math.max(min, Math.min(value, max));
}

console.log(clamp(123, 1, 100));
// 100

console.log(clamp(-5, 1, 100));
// 1

console.log(clamp(42.7, 1, 100));
// 42.7

Используйте функцию с конечными числовыми аргументами. Привязка сама по себе не округляет дробь внутри диапазона. Если вам нужно целое значение, передайте результат Math.round() в clamp().

Не добавляйте такой метод в Number.prototype. Изменение встроенного прототипа может конфликтовать с чужим кодом. Обычная функция решает задачу без изменения поведения всех чисел.

Округление по Гауссу

Банковское округление выбирает ближайшее значение, а точную половинку направляет к четному. Например, 2.5 превращается в 2, а 3.5 – в 4. Такое правило уменьшает систематическое смещение на наборах с многочисленными половинками.

Для форматирования используйте Intl.NumberFormat с параметром roundingMode: 'halfEven'. Параметры количества знаков задают разряд округления:

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

console.log(formatter.format(2.5));
// "2"

console.log(formatter.format(3.5));
// "4"

console.log(formatter.format(-2.5));
// "-2"

Форматтер возвращает строку. Поддержку режима в целевой среде можно определить по результату formatter.resolvedOptions().roundingMode: ожидается "halfEven". Среда без поддержки параметра может его проигнорировать.

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

function gaussRound(num) {
    if (!Number.isFinite(num)) {
        throw new TypeError('num must be finite');
    }
    const lower = Math.floor(num);
    const fraction = num - lower;

    if (fraction === 0.5) {
        return lower % 2 === 0 ? lower : lower + 1;
    }
    return Math.round(num);
}

console.log(gaussRound(2.5));
// 2

console.log(gaussRound(3.5));
// 4

console.log(gaussRound(-3.5));
// -4

Функция обрабатывает точные половинки сохраненного двоичного числа. Масштабирование через умножение на 100 не делает ее точной десятичной арифметикой. Для банковского округления финансовых дробей задавайте режим в библиотеке десятичных вычислений.

Как округлять и выводить денежные суммы

Храните рублевые суммы в целых копейках, если точности копейки достаточно. Тогда сложение и умножение на целое количество товаров обходятся без дробных рублей. Все промежуточные целые должны оставаться в безопасном диапазоне Number.

  1. Получайте цену сразу в копейках: например, 19990 вместо дробных рублей.
  2. Складывайте позиции и умножайте цену на целое количество товаров.
  3. Округляйте дробные копейки по выбранному правилу после расчета скидки или налога.
  4. Переводите итог в рубли только для отображения пользователю.

Пример рассчитывает три товара со скидкой 10% на весь заказ. Итог округляется до копейки по правилу Math.round():

const priceKopecks = 19990;
const quantity = 3;
const subtotalKopecks = priceKopecks * quantity;
const discountedNumerator = subtotalKopecks * 90;

if (!Number.isSafeInteger(discountedNumerator)) {
    throw new RangeError('Amount exceeds the safe integer range');
}

const totalKopecks = Math.round(discountedNumerator / 100);
console.log(totalKopecks);
// 53973

const moneyFormatter = new Intl.NumberFormat('ru-RU', {
    style: 'currency',
    currency: 'RUB',
    minimumFractionDigits: 2,
    maximumFractionDigits: 2
});

console.log(moneyFormatter.format(totalKopecks / 100));
// "539,73 ₽"

Форматтер выводит валюту и сохраняет два знака после запятой. Между суммой и символом валюты он может поставить неразрывный пробел. Альтернативный вызов использует те же параметры:

console.log((53973 / 100).toLocaleString('ru-RU', {
    style: 'currency',
    currency: 'RUB',
    minimumFractionDigits: 2,
    maximumFractionDigits: 2
}));
// "539,73 ₽"

Не считайте умножение произвольных дробных рублей на 100 надежным вводом. Исходное значение уже может содержать двоичную погрешность. Получайте целые копейки из источника данных либо разбирайте десятичную строку без промежуточного Number.

Правило округления и момент его применения определяют итоговую сумму. Округление каждой позиции может отличаться от округления общей суммы заказа. Для возвратов отдельно задавайте поведение отрицательных половинок.

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

Десятичные числа в CSS

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

#box { width: 63.667731993px; }

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

Не округляйте размеры автоматически перед каждой записью в style. Округление может накопить расхождения между соседними колонками или сделать анимацию дерганой. Используйте целые CSS-пиксели, только если этого требует ваша сетка или логика интерфейса:

const box = document.querySelector('#box');
const measuredWidth = 63.667731993;

if (box) {
    box.style.width = `${Math.round(measuredWidth)}px`;
    console.log(box.style.width);
    // "64px"
}

Пример сработает на странице с элементом id="box". Если дробный размер нужен для плавного движения, записывайте measuredWidth без округления.

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

  • Ближайшее целое для вычислений – Math.round(); учитывайте половинки к +∞.
  • Округление строго вниз или вверх – Math.floor() либо Math.ceil().
  • Удаление дробной части – Math.trunc(), особенно для отрицательных чисел.
  • Вывод заданного количества знаков – toFixed() или Intl.NumberFormat.
  • Округление обычных дробей до сотых для вычислений – десятичный сдвиг с учетом экспоненты и ограничений Number.
  • Сравнение результатов дробных вычислений – абсолютный и относительный допуски.
  • Деньги – целые минимальные единицы или библиотека десятичной арифметики.

Совет от нашей редакции: добавьте в тесты положительные и отрицательные половинки, ноль и 1.005. Такие примеры сразу показывают, соответствует ли выбранный метод правилам вашей задачи.

Комментарии

Оставьте свой комментарий
Пока никто не оставил комментариев