Методы округления чисел в JavaScript

Часто вычисления дают результаты, которые не соответствуют пределам нужных диапазонов. В результате нужно осуществлять JavaScript округление до определенного значения.

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

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

0.1 * 0.2;
> 0.020000000000000004

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

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

Чтобы «обрезать» десятичное число, используются методы toFixed() или toPrecision(). Они оба принимают один аргумент, который определяет количество значимых и знаков после запятой, которые должны быть включены в результат:

  • если для toFixed() аргумент не определен, значение по умолчанию равно 0, то есть без знаков после запятой; максимальное значение аргумента равно 20;
  • если для toPrecision() аргумент не задан, число не изменяется.
var randNum = 6.25;
randNum.toFixed();
> "6"

Math.PI.toPrecision(1);
> "3"

var randNum = 87.335;
randNum.toFixed(2);
> "87.33"

var randNum = 87.337;
randNum.toPrecision(3);
> "87.3"

Примечание

И toFixed(), и toPrecision возвращают округленное строчное представление результата, а не число. Это означает, что прибавление rounded к randNum в результате даст конкатенацию строк, а не одно число:

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

Если нужно получить в результате JavaScript округления до сотых число, используйте parseFloat():

var randNum = 6.25;
var rounded = parseFloat(randNum.toFixed(1));
console.log(rounded);
> 6.3

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

var wholeNum = 1
var dollarsCents = wholeNum.toFixed(2);
console.log(dollarsCents);
> "1.00"

Обратите внимание, что если в числе больше знаков, чем задано параметром точности, toPrecision будет выдавать результат в научном формате:

var num = 123.435
num.toPrecision(2);
> "1.2e+2"

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

В некоторых случаях toFixed и toPrecision осуществляют JavaScript округление 5 в меньшую сторону, а не до большего:

var numTest = 1.005;
numTest.toFixed(2);
> 1;

Результатом приведенного выше примера должно быть 1.01, а не 1. Если нужно избежать этой ошибки, я рекомендую использовать экспоненциальные числа:

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

Применение:

round(1.005,2);
> 1.01

Если нужно еще более надежное решение, чем округление, оно доступно на MDN.

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

Альтернативный метод JavaScript округления до десятых был введен в ES6 (также известном, как JavaScript 2015). «Машинный эпсилон» обеспечивает разумный предел погрешности при сравнении двух чисел с плавающей запятой. Без округления, сравнения могут дать результаты, подобные следующим:

0.1 + 0.2 === 0.3
> false

Math.EPSILON может быть использован в функции для получения корректного сравнения:

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON * Math.max(Math.abs(x), Math.abs(y));
}

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

epsEqu(0.1 + 0.2, 0.3)
> true

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

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

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

function truncated(num) {
    return Math.trunc(num * 100) / 100;
}
truncated(3.1416)
> 3.14

Если требуется что-то более гибкое, можно воспользоваться побитовым оператором:

function truncated(num, decimalPlaces) {    
    var numPowerConverter = Math.pow(10, decimalPlaces); 
    return ~~(num * numPowerConverter)/numPowerConverter;
}

Использование:

var randInt = 35.874993;
truncated(randInt,3);
> 35.874

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

Чтобы осуществить JavaScript округление до целого, используется Math.round():

Math.round(4.3)
> 4
Math.round(4.5)
> 5

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

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

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

Math.floor(42.23);
> 42
Math.floor(36.93);
> 36

Округление «вниз» имеет одно направление для всех чисел, в том числе и для отрицательных. Это можно представить, как небоскреб с бесконечным количеством этажей, в том числе и ниже уровня фундамента (представляющих отрицательные числа). Если вы находитесь в лифте между подвальными этажами 2 и 3 (что соответствует значению -2.5), Math.floor доставит вас на этаж -3:

Math.floor(-2.5);
> -3

Если нужно избежать этого, используйте JavaScript Math округление с помощью Math.trunc(), поддерживаемый во всех современных браузерах (кроме IE / Edge):

Math.trunc(-41.43);
> -41

MDN также предоставляет полифилл из трех строк для обеспечения поддержки Math.trunc в старых браузерах и IE / Edge.

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

Если вы хотите округлить десятичные числа вверх, используйте Math.ceil. Действие этого метода также можно представить, как бесконечный лифт: Math.ceil всегда везет вас «вверх«, независимо от того, является ли число отрицательным или положительным:

Math.ceil(42.23);
> 43
Math.ceil(36.93);
> 37 
Math.ceil(-36.93); -36

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

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

function roundTo5(num) {
    return Math.round(num/5)*5;
}

Использование:

roundTo5(11);
> 10

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

function roundToMultiple(num, multiple) {
    return Math.round(num/multiple)*multiple;
}

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

var initialNumber = 11;
var multiple = 10;
roundToMultiple(initialNumber, multiple);
> 10;

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

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

Иногда нужно получить значение х, которое должно находиться в пределах определенного диапазона. Например, нужно значение от 1 до 100, но мы получаем значение 123. Чтобы исправить это, можно использовать min() (возвращает наименьшее из чисел) и max (возвращает максимально допустимое число).

Использование:

var lowBound = 1;
var highBound = 100;
var numInput = 123;
var clamped = Math.max(lowBound, Math.min(numInput, highBound));
console.log(clamped);
> 100;

Можно создать функцию или расширение класса Number:

Number.prototype.clamp = function(min, max) {
  return Math.min(Math.max(this, min), max);
};

Использование:

(numInput).clamp(lowBound, highBound);

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

Округление по Гауссубанковское«, конвергентное или голландское) представляет собой метод округления без статистической погрешности. Стандартное JavaScript округление иногда дает погрешности в большую сторону. Округление по Гауссу позволяет избежать этой погрешности с помощью округления до ближайшего четного числа. Лучшее решение, которое мне известно:

function gaussRound(num, decimalPlaces) {
    var d = decimalPlaces || 0,
    m = Math.pow(10, d),
    n = +(d ? num * m : num).toFixed(8),
    i = Math.floor(n), f = n - i,
    e = 1e-8,
    r = (f > 0.5 - e && f < 0.5 + e) ?
		((i % 2 == 0) ? i : i + 1) : Math.round(n);
    return d ? r / m : r;
}

Примеры использования:

gaussRound(2.5)
> 2

gaussRound(3.5)
> 4

gaussRound(2.57,1)
> 2.6

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

Так как JavaScript часто используется для получения информации о позиции или преобразования HTML-элементов, то можно задаться вопросом, что произойдет, если мы сгенерируем десятичные значения для элементов:

#box { width: 63.667731993px; }

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

Перевод статьи «JavaScript Rounding Recipes» был подготовлен дружной командой проекта Сайтостроение от А до Я.