Округлення чисел в JavaScript

19

Від автора: досить часто в JavaScript під час обчислень можна отримати число не зовсім в потрібному нам діапазоні або числа, які потрібно «почистити» перед подальшим використанням. Дані числа округляються у більшу чи меншу ступінь, задаються в певному діапазоні або «обрізаються до потрібної кількості знаком після коми – все залежить від того, що потрібно вам.

Навіщо округляти числа?

Один з цікавих фактів про JS полягає в тому, що мова насправді не зберігає цілі числа: числа представлені в двійковому вигляді з плаваючою комою. Це, в поєднанні з тим фактом, що не всі дробу можна представити у вигляді десяткового числа з кінцевим кількістю цифр після коми, означає, що в JS можна отримати наступний результат (з допомогою консолі):

0.1 * 0.2;
> 0.020000000000000004

У більшості випадків з практичної точки зору ця похибка не буде мати значення (одна помилка на 2 квінтильйонів), але все ж це трохи розчаровує. Трохи дивний результат можна отримати при роботі з валютами, процентними значеннями або одиницями вимірювання розміру файлу. Щоб уникнути цієї помилки, необхідно округлити число або ввести певну кількість знаків після коми.

Округлення має багато прикладів практичного застосування: наприклад, якщо користувач переміщує повзунок елемента range, щоб не працювати з десятковими числами, нам було б зручніше округляти отримане значення до найближчого цілого.

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

Для урізання десяткових чисел можна використовувати методи 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»

Якщо потрібно привести результат до числа, скористайтесь parseFloat:

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

(зверніть, що число 5 округлилося в більшу сторону, за винятком рідкісних випадків; більш докладно трохи нижче.) Також методи toFixed() і toPrecision() бувають корисні, коли необхідно цілому числу приписати десяткову частину. Це особливо зручно при роботі з валютами:

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

Зверніть увагу, що якщо в числі більше цифр, ніж в аргументі toPrecision, то воно буде записано у науковому вигляді (з мантиссой і порядком):

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

Як уникати помилок при округлення десяткових чисел

У деяких випадках toFixed і toPrecision округляють 5 не вгору, а вниз:

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

Результат вище повинен бути 1.01, а не 1. Якщо для вас важлива точність, я б порекомендував вам рішення від Jack L Moore, який в обчисленнях використовує експоненціальні числа:

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

І результат:

round(1.005,2);
> 1.01

На MDN є ще більш надійне рішення.

Усічення десяткових чисел

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

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

Якщо ви хочете додати трохи гнучкості, можна скористатися побітовим оператором ~~:

function truncated(num, після коми) {
var numPowerConverter = Math.pow(10, після коми);
return ~~(num * numPowerConverter)/numPowerConverter;
}

Результат:

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

У наступній статті я детальніше розповім про побітові операції.

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

Для округлення десяткового числа вгору або вниз до найближчого цілого використовуйте 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.5), метод Math.floor доставить вам на -3 поверх:

Math.floor(-2.5);
> -3

Якщо ви не хочете, щоб від’ємні числа теж округлювалися в меншу сторону, скористайтесь 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

Округлення вгору і вниз до найближчого кратного числа

Якщо необхідно округлити число до найближчого кратного п’яти, найлегше створити функцію, яка розділить число на 5, округлить його і помножить назад:

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

Результат:

roundTo5(11);
> 10

Якщо необхідно округляти числа під різні кратні, можна змінити функцію і передавати в неї як аргументи обидва значення, кількість і кратність:

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

Щоб викликати функцію, необхідно вказати два параметри, кількість і кратність:

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

Щоб округляти вниз або вгору ставте функції ceil або floor.

Установка діапазону для числа

Буває багато випадків, коли ми отримуємо число Х, і нам необхідно загнати його в певний діапазон. Наприклад, нам потрібно число від 1 до 100, а отримали ми 123. Тут нам знадобляться методи min (завжди повертає найменше з набору чисел) і max (найбільше число з набору). Приклад з діапазоном від 1 до 100:

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

Це можна перетворити на функцію або розширення класу Number, варіант з розширенням вперше запропонував Daniel X. Moore:

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

Результат:

(numInput).clamp(lowBound, highBound);

Округлення за Гаусом

Округлення за Гаусом, яке також називають округленням для «банкірів», конвергентним округленням, голландським округленням і непарних-парних округленням, це метод округлення без статистичного зсуву; в звичайному округленні числа автоматично завищуються. В округленні за Гаусом наводиться число до найближчого парним. Найкраще відоме мені рішення у Tim Down:

function gaussRound(num, після коми) {
var d = після коми || 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.667731993 px; }

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