Введение
Наличие чисел и числовых данных позволяет вам каким-либо образом работать с языком. Но помимо работы с арифметическими операторами в JavaScript, математические конструкции могут быть сложной задачей для новичков. По этой причине, нужно концентрироваться не на синтаксисе, а на общих математических функциях, этот список начинается с таких вещей как сортировка, округление и генерация случайных значений, этого достаточно, прежде чем углубляться в детали. Чтобы работать с математикой в JavaScript, вам достаточно иметь понятия о таких вещах как функция, операнд и оператор.
От переводчиков
Всем привет, с вами Максим Иванов и Дмитрий Сергиенков, и сегодня мы решили, что мы не будем говорить о модных и полезных штуках типа ReactJS, Angular, TypeScript и других. Сегодня мы уделим внимание математике в JavaScript. Если вам нравится математика, вы можете заниматься ей всё свободное время, но если вашей целью являются не научные изыскания, а работа программистом, математика вряд ли станет лучшим объектом для изучения.
- Работа со случайными числами
- Основной случай
- Случайное число в интервале [min, max)
- Случайное число в интервале [min, max]
- Булевские случайные величины (true/false)
- Случайные величины с исключениями
- Случайные величины без повторений
- Криптографические случайные величины
- Округления
- Зачем необходимо округление?
- Округление десятичных чисел
- Как избежать ошибок округления с десятичными числами
- Машинное эпсилон округление
- Отсечение дробной части
- Округление до ближайшего числа
- Округление к меньшему до ближайшего целого числа
- Округление к большему до ближайшего целого числа
- Округление до большего/меньшего необходимого числа
- Фиксирование числа в диапазоне
- Гауссово округление
- Сортировка
- Работа со степенными функциями
- Математические константы
- Math.abs, parseInt, parseFloat
Работа со случайными числами
Случайные числа часто требуются в JavaScript, к примеру, для отрисовки звезд, разбросанных по ночному небу. Но есть много различных видов случайностей, и в зависимости от логики и потребностей вашего приложения вам может понадобиться один из них.
Основной случай
Самая простая форма случайности — это функция Math.random(), встроенная в JavaScript.
> Math.random()
0.19401081069372594
Math.random() всегда возвращает число с плавающей точкой между 0 и 1. С технической точки зрения число, возвращаемое при помощи Math.random() может быть 0, но никогда не будет равно 1.
Если вы часто используйте Math.random(), используйте свою собственную функцию в сценариях:
function getRandom() {
return Math.random();
}
Проблема, конечно, в том, что данная функция всегда будет создавать случайное число в пределах весьма ограниченного диапазона, далее мы постараемся рассмотреть некоторые рекомендации предназначенные для решения этой проблемы.
Случайное число в интервале [min, max)
Расширение такой функциональности требует немного математики:
Случайное число с плавающей точкой:
function getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}
getRandomFloat(11, 101)
> 75.31898734299466
Целочисленное случайное число:
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
getRandomInt(10, 20)
> 12
Случайное число в интервале [min, max]
function getRandomInRange(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
getRandomInRange(1, 10)
> 7
Булевские случайные величины (true/false)
Если вы хотите простую случайность 0 или 1, представляющую подбрасывание монеты:
function coinToss() {
return Math.floor(Math.random() * 2);
}
coinToss();
> 0
Если вам нужно получить true или false:
function coinToss() {
return (Math.floor(Math.random() * 2) === 0);
}
coinToss();
> true
Или же:
function coinToss() {
return Math.random()<.5;
}
coinToss();
> true
Если вы хотите связать конкретные слова со сторонами монеты (да / нет, верх / низ и т.д.):
function coinFlip() {
return (Math.floor(Math.random() * 2) === 0) ? "up" : "down";
}
coinFlip();
> up
Случайные величины с исключениями
Для ограниченного диапазона целых чисел необходимо создать массив чисел, которые вы хотели бы извлечь и в дальнейшем выбрать случайным образом из этого массива:
let numPool = [ 1, 3, 5, 7, 9, 10 ],
rand = numPool[Math.floor(Math.random() * numPool.length)];
Можно также использовать массив чисел, которые вы хотите исключить, и приготовить пустой массив, в котором будет содержаться результат фильтрации из первого массива во второй:
let numPool = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
let excludePool = [ 3, 4 ];
let filteredPool = [];
Затем в цикле прогоняем массив numPool, если выпавшее число существует и находится в массиве excludePool, помещаем результат в filteredPool:
for (let i = 0; i < numPool.length; i++) {
if (excludePool.indexOf(numPool[i]) === -1) {
filteredPool.push(numPool[i]);
}
}
И, наконец, получаем случайные числа из массива filteredPool:
let rand = filteredPool[Math.floor(Math.random() * filteredPool.length)];
Случайные величины без повторений
Для небольшого набора чисел необходимо создать массив, заполненный элементами, далее перетасуйте их в случайном порядке, поместите результат в новый массив, а затем вытаскивайте по одному:
let numPool = [ 13, 21, 36, 14, 27, 10 ];
function shuffle(numPool) {
for(
let j, x, i = numPool.length; i;
j = parseInt(Math.random() * i),
x = numPool[--i],
numPool[i] = numPool[j],
numPool[j] = x
);
return numPool;
};
let randomResult = shuffle(numPool);
while( randomResult.length > 0 ) {
console.log( randomResult.pop() );
}
Для получения более широкого диапазона чисел, создайте и заполните массив случайными числами, исключая любые, которые были ранее сгенерированы:
let numReserve = []
while (numReserve.length < 12) {
let randomNumber = Math.ceil(Math.random() * 1000);
let found = false;
for (let i = 0; i < numReserve.length; i++) {
if (numReserve[i] === randomNumber){
found = true;
break;
}
}
if (!found) { numReserve[numReserve.length]=randomNumber; }
}
В приведенном выше коде numReserve заполняется 12ю случайными числами от 0 до 1000.
Криптографические случайные величины
К сожалению, ни один из выше методов не создает число с достаточной хаотичностью для криптографически защищенных функций (Math.random() не является достаточной функцией, которая генерирует случайные числа). Поэтому, мы можем использовать Web Cryptography API путем создания typedArray:
let cryptoStor = new Uint16Array(8);
В этом случае мы создаем массив с восемью различными слотами, каждый из которых содержит беззнаковое 16-разрядное целое число. Существуют и другие варианты Int8Array, Uint8Array, int16Array, Int32Array и Uint32Array.
Затем, необходимо заполнить массив случайными числами определенного типа:
window.crypto.getRandomValues(cryptoStor);
> [43484, 57947, 46691, 49849, 24272, 11827, 28203, 17423]
Ко всему прочему, Web Cryptography API имеет хорошую поддержку в современных браузерах.
К прочтению:
1. Случайное простое число
2. Генерация случайных целых чисел в JavaScript в определенном диапазоне
3. Введение в криптографию
4. Криптографически стойкий генератор псевдослучайных чисел
5. Генератор псевдослучайных чисел
Округления
Очень часто вычисления в JavaScript дают не совсем те результаты, которые мы хотим. Разумеется, мы можем делать с числами что угодно — округлять в большую или меньшую сторону, устанавливать диапазоны, отсекать ненужные числа до определенного количества знаков после запятой, все зависит от того, что вы хотите сделать в дальнейшем с этим числом.
Зачем необходимо округление?
Одним из любопытных аспектов JavaScript является то, что он на самом деле не хранит целые числа, мы сразу же работаем с числами с плавающей точкой. Это, в сочетании с тем фактом, что многие дробные значения не могут быть выражены конечным числом знаков после запятой, в JavaScript мы можем получить такие результаты:
0.1 * 0.2;
> 0.020000000000000004
0.3 - 0.1
> 0.19999999999999998
Для практических целей эта неточность не имеет никакого значения, в нашем случае мы говорим об ошибке в квинтиллионных долях, однако, кого-то это может разочаровать. Мы можем получить несколько странный результат и при работе с числами, которые представляют собой значения валют, процентов или размеров файла. Для того, чтобы исправить эти неточности, нам как раз и необходимо уметь округлять результаты, при этом достаточно установить десятичную точность.
Округление чисел имеет практическое применение, мы можем манипулировать числом в некотором диапазоне, например, хотим округлить значение до ближайшего целого числа, а не работать только с десятичной частью.
Округление десятичных чисел
Для того, чтобы отсечь десятичное число, используйте toFixed или метод toPrecision. Оба они принимают единственный аргумент, который определяет, соответственно, сколько значащих цифр (т.е. общее количество цифр, используемых в числе) или знаков после запятой (количество после десятичной точки) должен включать в себя результат:
- Если аргумент не определен для toFixed(), то по умолчанию он будет равен нулю, что означает 0 знаков после запятой, аргумент имеет максимальное значение, равное 20.
- Если аргумент не задан для toPrecision, число остается нетронутым
let randNum = 6.25;
randNum.toFixed();
> "6"
Math.PI.toPrecision(1);
> "3"
randNum = 87.335;
randNum.toFixed(2);
> "87.33"
randNum = 87.337;
randNum.toPrecision(3);
> "87.3"
Оба метода toFixed() и toPrecision() возвращают строковое представление результата, а не число. Это означает, что при суммировании округленного значения с randNum будет произведена конкатенация строк, а не сумма чисел:
let randNum = 6.25;
let rounded = randNum.toFixed(); // "6"
console.log(randNum + rounded);
> "6.256"
Если вы хотите, чтобы результат имел числовой тип данных, то вам необходимо будет применить parseFloat:
let randNum = 6.25;
let rounded = parseFloat(randNum.toFixed(1));
console.log(rounded);
> 6.3
Обратите внимание, что значения 5 округлены, за исключением редких случаев.
Методы toFixed() и toPrecision() являются полезными, ибо они могут не только отсекать дробную часть, но и дополнять знаки после запятой, что удобно при работе с валютой:
let wholeNum = 1
let dollarsCents = wholeNum.toFixed(2);
console.log(dollarsCents);
> "1.00"
Стоить обратите внимание, что toPrecision будет давать результат в экспоненциальной записи, если число целых чисел больше, чем сам сама точность:
let num = 123.435
num.toPrecision(2);
> "1.2e+2"
Как избежать ошибок округления с десятичными числами
В некоторых случаях, toFixed и toPrecision округляет значение 5 в меньшую сторону, а в большую:
let numTest = 1.005;
numTest.toFixed(2);
> "1.00"
Результат расчета выше должен был быть 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.
Машинное эпсилон округление
Альтернативный метод округления десятичных чисел был введен в ES6. Машинное эпсилон округление обеспечивает разумный предел погрешности при сравнении двух чисел с плавающей точкой. Без округления, сравнения могут дать результаты, подобные следующим:
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 математические функции, но если вы хотите получить поддержку в таких браузерах, как IE 11, используйте polyfills.
Отсечение дробной части
Все методы, представленные выше умеют округлять до десятичных чисел. Для того, чтобы просто отсечь число до двух знаков после запятой, необходимо сначала умножить его на 100, а затем полученный результат уже разделить на 100:
function truncated(num) {
return Math.trunc(num * 100) / 100;
}
truncated(3.1416)
> 3.14
Если вы хотите приспособить метод под любое количество знаков после запятой, вы можете воспользоваться двойным побитовым отрицанием:
function truncated(num, decimalPlaces) {
let numPowerConverter = Math.pow(10, decimalPlaces);
return ~~(num * numPowerConverter)/numPowerConverter;
}
Теперь:
let randInt = 35.874993;
truncated(randInt,3);
> 35.874
Округление до ближайшего числа
Для того, чтобы округлить десятичное число до ближайшего числа в большую или в меньшую сторону, в зависимости от того, к чему мы ближе всего, используйте Math.round():
Math.round(4.3)
> 4
Math.round(4.5)
> 5
Обратите внимание, что «половина значения», 0.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
Но если вы хотите избежать подобной ситуации, используйте Math.trunc, поддерживаемый во всех современных браузерах (кроме IE / Edge):
Math.trunc(-41.43);
> -41
На MDN вы найдете polyfill, который обеспечит поддержку 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
Если вы хотите округлять до кратных своему значению, мы использовать более общую функцию, передавая в нее начальное значение и кратное:
function roundToMultiple(num, multiple) {
return Math.round(num/multiple)*multiple;
}
Теперь:
let initialNumber = 11;
let multiple = 10;
roundToMultiple(initialNumber, multiple);
> 10;
Фиксирование числа в диапазоне
Есть много случаев, когда мы хотим получить значение х, лежащее в пределах диапазона. Например, нам может понадобиться значение от 1 до 100, но при этом мы получили значение 123. Для того, чтобы исправить это, мы можем использовать минимальное (возвращает наименьшее из набора чисел) и максимальное (возвращает наибольшее из любого множества чисел). В нашем примере, диапазон от 1 до 100:
let lowBound = 1;
let highBound = 100;
let numInput = 123;
let clamped = Math.max(lowBound, Math.min(numInput, highBound));
console.log(clamped);
> 100;
Опять же, мы можем переиспользовать операцию и обернуть все это в функцию, воспользуемся решением предложенное Daniel X. Moore:
Number.prototype.clamp = function(min, max) {
return Math.min(Math.max(this, min), max);
};
Теперь:
numInput.clamp(lowBound, highBound);
> 100;
Гауссово округление
Гауссово округление, также известное как банковское округлением, заключается в том, что округление для этого случая происходит к ближайшему чётному. Этот метод округления работает без статистической погрешности. Лучшее решение было предложено Tim Down:
function gaussRound(num, decimalPlaces) {
let 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-элементов, вы можете задаться вопросом, что произойдет, если мы cгенерируем десятичные значения для наших элементов:
#box { width: 63.667731993px; }
Хорошая новость заключается в том, что современные браузеры будут учитывать десятичные значения в блочной модели, в том числе в процентных или пиксельных единицах измерения.
К прочтению:
1. Что каждый ученый должен знать об арифметике при работе с плавающей запятой
2. Быстрая JavaScript-библиотека, работающая с десятичной арифметикой произвольной точности
3. Расширенный тип Decimal произвольной точности для JavaScript
4. JavaScript-библиотека произвольной точности для десятичной и произвольной арифметики
5. Что должен знать каждый JavaScript-разработчик о плавающей запятой
6. Отчего такие проблемы с плавающей запятой в JS?
Сортировка
Очень часто нам приходится сортировать какие-либо элементы, например, у нас есть массив игровых рекордов, при этом они должны быть организованы по убыванию ранга игроков. К сожалению, стандартный метод sort() имеет некоторые удивительные ограничения: он хорошо работает с часто употребляемыми английскими словами, но сразу же ломается при встрече с числами, уникальными символами или словами в верхнем регистре.
Сортировка в алфавитном порядке
Казалось бы, сортировки массива по алфавиту должна быть простейшей задачей:
let fruit = ["butternut squash", "apricot", "cantaloupe"];
fruit.sort();
> "apricot", "butternut squash", "cantaloupe"]
Тем не менее мы сталкиваемся с проблемой, как только один из элементов находится в верхнем регистре:
let fruit = ["butternut squash", "apricot", "Cantalope"];
fruit.sort();
> "Cantaloupe", "apricot", "butternut squash"]
Это связано с тем, что, по умолчанию, сортировщик сравнивает первый символ представленный в Unicode. Unicode — это уникальный код для любого символа, независимо от платформы, независимо от программы, независимо от языка. Например, если смотреть по кодовой таблице символ «a» имеет значение U+0061 (в шестнадцатеричной системе 0x61), в то время как символ «C» имеет код U+0043 (0x43), который идет раньше в Unicode-таблице, чем символ «a».
Чтобы отсортировать массив, который может содержать смешанные регистры первых букв, нам необходимо либо преобразовать все элементы временно в нижний регистру, или определить свой порядок сортировки при помощи метода localeCompare() c некоторыми аргументами. Как правило, для такого случая, лучше сразу создать функцию для многократного использования:
function alphaSort(arr) {
arr.sort(function (a, b) {
return a.localeCompare(b, 'en', {'sensitivity': 'base'});
});
}
let fruit = ["butternut squash", "apricot", "Cantaloupe"];
alphaSort(fruit)
> ["apricot", "butternut squash", "Cantaloupe"]
Если вы хотите получить массив отсортированный в обратный алфавитном порядке, просто поменяйте позициями а и b в функции:
function alphaSort(arr) {
arr.sort(function (a, b) {
return b.localeCompare(a, 'en', {'sensitivity': 'base'});
});
}
let fruit = ["butternut squash", "apricot", "Cantaloupe"];
alphaSort(fruit)
> ["Cantaloupe", "butternut squash", "apricot"]
Тут стоит обратить внимание, что localeCompare используется с аргументами, еще надо помнить, что он поддерживается IE11+, для более старых версий IE, мы можем использовать его без аргументов, и в нижнем регистре:
function caseSort(arr) {
arr.sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
}
let fruit = ["butternut squash", "apricot", "Cantaloupe"];
caseSort(fruit)
> ["apricot", "butternut squash", "Cantaloupe"]
Числовая сортировка
Все это не относится к тому примеру, о котором мы говорили выше про массив игровых рекордов. С некоторыми числовыми массивами сортировка работает просто идеально, но в какой-то момент результат может быть непредсказуемым:
let highScores = [11, 57, 10, 16, 32, 100];
highScores.sort();
> [10, 100, 11, 16, 32, 57]
Дело в том, что метод sort() производит лексикографическую сравнение: а это означает, что числа будут преобразованы в строку и сравнения будут снова проводиться путем сопоставления первого символа этой строки в порядке символов Unicode-таблицы. Поэтому нам снова необходимо определить свой порядок сортировки:
let highScores = [11, 57, 10, 16, 32, 100];
highScores.sort( function(a,b) { return a - b; } );
> [10, 11, 16, 32, 57, 100]
Опять же, для сортировки чисел в обратном порядке, поменяйте позициями a и b в функции.
Сортировка JSON-подобной структуры
И наконец, если у нас есть JSON-подобная структура данных, представленная как массив игровых рекордов:
let scores = [
{
"name": "Daniel",
"score": 21768
},
{
"name": "Michael",
"score": 33579
},
{
"name": "Alison",
"score": 38395
}
];
В ES6+, вы можете использовать стрелочные функции:
scores.sort((a, b) => b.score - a.score));
Для старых браузеров, не имеющих такую поддержку:
scores.sort(function(a, b) { return a.score - b.score });
Как видите, сортировка в JavaScript это довольно не очевидная вещь, я надеюсь, что эти примеры облегчат как-нибудь жизнь.
К прочтению:
1. Сортировка
2. Алгоритм сортировки
3. Пузырьковая сортировка и все-все-все
4. Естественная сортировка строк на JavaScript
5. Утонченная сортировка в JavaScript
6. Javascript Sort Benchmark
7. Looking for performance? Probably you should NOT use [].sort (V8)
Работа со степенными функциями
Возведение в степень — операция, первоначально определяемая как результат многократного умножения натурального числа на себя, квадратный корень из числа a — число, дающее a при возведении в квадрат. Этими функциями мы могли пользоваться постоянно в повседневной жизни на уроках математики, в том числе при вычислении площадей, объемов или даже при физическом моделировании.
В JavaScript степенная функция представлена как Math.pow(), в новом стандарте ES7 был представлен новый оператор возведения в степень — " * * ".
Возведение в степень
Для того, чтобы возвести число в n-ую степень, используйте функцию Math.pow(), где первый аргумент это число, которое будет возведено в степень, второй аргумент это показатель степени:
Math.pow(3,2)
> 9
Такая форма записи означает 3 в квадрате, или 3 ? 3, что приводит к результату 9. Можно привести еще пример, конечно:
Math.pow(5,3);
> 125
То есть, 5 в кубе, или 5 ? 5 ? 5, равно 125.
ECMAScript 7 — это следующая версия JavaScript, в принципе, мы можем использовать новый предложенный оператор возведения в степень — * *, такая форма записи может быть более наглядной:
3 ** 2
> 9
На данный момент поддержка этого оператора довольно ограниченная, поэтому его не рекомендуется использовать.
Степенная функция может пригодиться в самых разных ситуациях. Простой пример, вычисление количества секунд в часе: Math.pow (60,2).
Квадратный и кубический корень
Math.sqrt() и Math.cbrt() противоположны функции Math.pow(). Как мы помним, квадратный корень из числа a — число, дающее a при возведении в квадрат.
Math.sqrt(9)
> 3
В тоже время кубический корень из числа a — число, дающее a при возведении в куб.
Math.cbrt(125)
> 5
Math.cbrt() был введен в спецификацию JavaScript совсем недавно, и поэтому поддерживается только в современных браузерах: Chrome 38+, Firefox и Opera 25+ и Safari 7.1+. Вы заметите, что Internet Explorer отсутствует в этом списке, однако на MDN вы найдете полифилл.
Примеры
Конечно, мы можем использовать и не целые значения в одной из этих функций:
Math.pow(1.25, 2);
> 1.5625
Math.cbrt(56.57)
> 3.8387991760286138
Обратите внимание, что это вполне себе работает и при использовании отрицательных значениях аргументов:
Math.pow(-5,2)
> 25
Math.pow(10,-2)
> 0.01
Тем не менее, для квадратного корня это не будет работать:
Math.sqrt(-9)
> NaN
Из математического анализа мы знаем, что под мнимым числом понимают квадратные корни из отрицательных чисел. И это может привести нас к еще одной технике работы с комплексными числами, но это уже другая история.
Вы можете использовать дробные значения в Math.pow(), чтобы найти квадратные и кубические корни чисел. Квадратный корень использует показатель 0.5:
Math.pow(5, 0.5); // = Math.sqrt(5) = 5 ** (1/2)
> 2.23606797749979
Однако, из-за капризов с плавающей точкой, вы не можете точно предположить правильный результат:
Math.pow(2.23606797749979,2)
> 5.000000000000001
В таких ситуациях, вы вам придется прибегать к отсечению знаков у числа или округление до какого-либо значения.
Некоторые, по непонятным причинам в JavaScript путают функцию Math.pow() с Math.exp(), которая является экспоненциальной функцией для чисел, в целом. Примечание: в английском языке «показатель степени» переводится как «exponent», поэтому это скорее относится к англоговорящим, хотя существуют и альтернативные названия показателя степени, такие как index, power.
К прочтению:
1. Math.pow(num, 2) vs (self = num) * self
2. Арифметический квадратный корень из отрицательного числа
3. Пример решения квадратного уравнения с отрицательным дискриминантом
4. Обширная математическая библиотека для JavaScript и Node.js
5. MathML и polyfill
Математические константы
Работа с математикой в JavaScript облегчается за счет ряда встроенных констант. Эти константы являются свойствами объекта Math. Стоит обратить внимание, что константы пишутся в верхнем регистре, а не CamelCase нотации.
Math.PI
Число Пи — математическая константа, равная отношению длины окружности к длине её диаметра. Старое название — лудольфово число. Пи — иррациональное число, то есть его значение не может быть точно выражено в виде дроби m/n, где m и n — целые числа. Следовательно, его десятичное представление никогда не заканчивается и не является периодическим. На бумаге популярно использовать его короткую форму записи — 3.14159.
Ваш браузер не может хранить бесконечное число, так что JavaScript округляет число Пи до такого значения 3,141592653589793, что более чем достаточно для большинства задач.
Хотя это число наиболее распространено в задачах на нахождении длин, площади окружности, число Пи также используется в вероятности, статистики, инженерных и естественных науках: это как универсальная константа.
Math.SQRT2
Квадратный корень из числа 2 — положительное вещественное число, которое при умножении само на себя даёт число 2. Геометрически корень из 2 можно представить как длину диагонали квадрата со стороной 1 (это следует из теоремы Пифагора). Это было первое известное в истории математики иррациональное число. JavaScript округляет это число до такого значения 1.4142135623730951. (Из-за ошибок округления в JavaScript: Math.SQRT2 * Math.SQRT2 не равно 2).
Math.SQRT1_2
Квадратный корень из 0.5 — это единица, деленная на корень квадратный из 2. И опять же, это иррациональное число.
Простейшими преобразованиями на бумаге мы можем записать это так:
v(1/2) = v1 / v2 = 1 / v2 = v2 / 2
Но из-за проблем с плавающей точкой, мы можем получить такой результат:
Math.SQRT1_2 === Math.sqrt(1/2)
> true
Math.sqrt(2) / 2 === Math.sqrt(1/2)
> true
1 / Math.sqrt(2) === Math.sqrt(1/2)
> false
Math.E
Как ни странно, в математике константа е всегда записывалась в нижнем регистре, в JavaScript же это число используют в верхнем регистре. Число e — основание натурального логарифма, математическая константа, иррациональное и трансцендентное число. Иногда число e называют числом Эйлера или числом Непера. JavaScript округляет его как 2,718281828459045. Число e играет важную роль в дифференциальном и интегральном исчислении, а также во многих других разделах математики.
Math.pow(Math.E,1)
> 2.718281828459045
Math.pow(Math.E,2)
> 7.3890560989306495
Math.pow(Math.E,3)
> 20.085536923187664
Натуральный логарифм
Натуральный логарифм — это логарифм по основанию e, где e — иррациональная константа, равная приблизительно 2,718281828. Натуральный логарифм числа x — это показатель степени, в которую нужно возвести число e, чтобы получить x. Math.log(х) — это натуральный из x по основанию e.
Math.log(-1); // NaN, out of range
Math.log(0); // -Infinity
Math.log(1); // 0
Math.log(10); // 2.302585092994046
Если вам нужно получить логарифм из y по основанию x:
function getBaseLog(x, y) {
return Math.log(y) / Math.log(x);
}
getBaseLog(1/5, 5)
> -1
Однако, из-за особенностей округления чисел с плавающей точкой, ответ получается не всегда точный и только близок к правильному значению:
getBaseLog(10, 1000)
> 2.9999999999999996
Math.LN2
Свойство Math.LN2 представляет натуральный логарифм из 2 равный 0.6931471805599453.
Math.LN10
Свойство Math.LN10 представляет натуральный логарифм из 10 равный 2.302585092994046
Math.LOG2E
Свойство Math.LOG2E представляет двоичный логарифм из e равный 1.4426950408889634
Math.LOG10E
Свойство Math.LOG10E представляет десятичный логарифм из e равный 0.4342944819032518
К прочтению:
1. Математическая константа
2. 10 чисел, на которых держится мир
3. Математическую константу положили на музыку
Math.abs, parseInt, parseFloat
Работа с числами в JavaScript может быть куда более сложной, чем кажется. Полученные значения не всегда попадают внутрь ожидаемых диапазонов, иногда результат может оказаться вовсе не тем, что мы ожидали.
Math.abs()
Метод Math.abs() возвращает абсолютное значение числа, что напоминает нам аналогичную математическую функцию модуля числа a.
let newVal = -57.64;
Math.abs(newVal);
> 57.64
Math.abs(0) всегда возвращает нуль, но если поставить знак минус перед функцией -Math.abs(NUM) мы всегда будем отрицательное значение.
-Math.abs(0);
> -0
parseInt()
Мы знаем, что JavaScript понимает, что «15» это строка, а не число и, например, при разборе CSS-свойств средствами JavaScript, или получив какое-либо значение из неподготовленного массива, наши результаты могут получиться непредсказуемыми. Мы могли получить на вход строку представленную как «17px», и для нас это не является редкостью. Вопрос заключается в том, как преобразовать эту строку в фактическое значение и использовать его в дальнейших расчетах.
Синтаксис: parseInt(string, radix);
Функция parseInt преобразует первый переданный ей аргумент в строковый тип, интерпретирует его и возвращает целое число или значение NaN. Результат (если не NaN) является целым числом и представляет собой первый аргумент (string), рассматривающийся как число в указанной системе счисления (radix). Например, основание 10 указывает на преобразование из десятичного числа, 8 — восьмеричного, 16 — шестнадцатеричного и так далее. Если основание больше 10, то для обозначения цифр больше 9 используются буквы. Например, для шестнадцатеричных чисел (основание 16) используются буквы от A до F.
Рассмотрим пример работы с CSS-свойствами, где, условно говоря, мы можем получить такое значение:
let elem = document.body;
let centerPoint = window.getComputedStyle(elem).transformOrigin;
> "454px 2087.19px"
Мы можем разделить значения по пробелам:
let centers = centerPoint.split(" ");
> ["454px", "2087.19px"]
Однако, каждый элемент все еще есть строка, мы можем избавиться от этого применив нашу функцию:
let centerX = parseInt(centers[0], 10);
> 454
let centerY = parseInt(centers[1], 10);
> 2087
Как видите, вторым аргументом мы указываем систему счисления, в которую будет преобразовано число, этот параметр необязательный, но его рекомендуется использовать, в случае, если вы не знаете какая строка поступит на вход.
parseFloat()
Из примера выше, вы наверное заметили, что parseInt отбрасывает дробную часть. В нашем случае, parseFloat умеет работать с числами с плавающей точкой. Опять же, это может быть полезным при разборе CSS и других задачах, особенно при работе с плавающей точкой в ??процентах.
Синтаксис: parseFloat(string)
let FP = "33.33333%";
console.log(parseFloat(FP));
> 33.33333
Обратите внимание, что в синтаксисе parseFloat нет второго аргумента.
Мы понимаем, что parseInt() и parseFloat() являются чрезвычайно полезными функциями, важно учитывать, что и тут не обойтись без ошибок, поэтому необходимо проверять диапазон ожидаемых значений и в конечном счете анализировать результат, чтобы гарантировать, что полученные значения верны.
К прочтению:
1. Разница между parseInt() и parseFloat()
2. В чем разница между parseInt() и Number() в JavaScript?
3. JavaScript parseInt, Convert String to Int Performance
4. Benchmark: Number vs. mul vs. parseInt vs. parseFloat
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (20)
yAnTar_yAnTar
12.11.2016 13:28+2А почему coinToss не сделать проще?
return Math.round(Math.random());
вместо
return Math.floor(Math.random() * 2);
Shakirok
18.11.2016 21:50+1Дело в особенности языка.
Из-за неточности вычислений с плавающей запятой, очень редко может возникать ситуация неправильного округления. ссылка
Метод floor работает гораздо проще и просто возвращает ближайшее целое число, которое меньше или равно указанному.
Таким образом, вместо ненадежного round() мы умножаем рандомное число на 2, результатом чего всегда будет число в промежутке [0,2) без каких-либо округлений. После чего надежно используем floor() и получаем 0 или 1.
babichss
12.11.2016 15:34+2Восхитительная работа. Вошла в зал славы и будет даваться на ознакомлениям всем моим ученикам.
zhaparoff
12.11.2016 15:34-1function truncated(num, decimalPlaces) { let numPowerConverter = Math.pow(10, decimalPlaces); return ~~(num * numPowerConverter)/numPowerConverter; }
Теперь я понимаю, чем занято одно из ядер моего процессора на 100%, когда я открываю простенькую HTML страничку, на которой 200 чисел, округленных до 6 знаков после запятой.
ilinsky
14.11.2016 13:31+1Будьте осторожны с использованием ~~ при работы с числами:
function truncated(num, decimalPlaces) { let numPowerConverter = Math.pow(10, decimalPlaces); return ~~(num * numPowerConverter)/numPowerConverter; } > truncated(0.12345678912345678, 17) > 1.578423886e-8
Простая замена ~~ на Math.round возвращает точность:
function truncated(num, decimalPlaces) { let numPowerConverter = Math.pow(10, decimalPlaces); return Math.round(num * numPowerConverter)/numPowerConverter; } > truncated(0.12345678912345678, 17) > 0.12345678912345678
profesor08
12.11.2016 15:34+1function coinToss() { return Math.random()<.5; }
Но ведь в данном случае true будет чаще встречаться, чем false.
Math.random() генерирует случайное число от 0 до 0.9999999999999999, следовательно надо сравнивать не с 0.5, а с 0.49999999999999994 либо 0.49999999999999995, чтоб иметь наиболее честный результат.Sixshaman
12.11.2016 15:53+1На большинстве монет из реального мира одна сторона чуть-чуть тяжелее другой, так что в статье всё в порядке.
vintage
12.11.2016 16:04+2Не будет. Это условие разбивает всё множество значений [0,1) на 2 эквивалентных диапазона [0,.5) и [0+.5,.5+.5).
torbasow
12.11.2016 19:25Самое сложное с математической точки зрения, что у меня было, это была система тригонометрических уравнений, сведённая к биквадратичным. Но это же школьный курс?
VariousIng
14.11.2016 10:47Очень интересная статья. Нет-нет да и приходится к математике в JS обращаться, на ходу реализуя и возможно далеко не самый лучший вариант. А тут всё грамотно по полочкам разложено, есть вопрос, открыл нужное место и уже хорошее решение за 5 сек готово! Сей труд не пропадёт даром!
ilinsky
14.11.2016 13:35Будьте осторожны с использованием ~~ при работы с числами:
function truncated(num, decimalPlaces) { let numPowerConverter = Math.pow(10, decimalPlaces); return ~~(num * numPowerConverter)/numPowerConverter; } > truncated(0.12345678912345678, 17) > 1.578423886e-8
Простая замена ~~ на Math.round возвращает точность:
function truncated(num, decimalPlaces) { let numPowerConverter = Math.pow(10, decimalPlaces); return Math.round(num * numPowerConverter)/numPowerConverter; } > truncated(0.12345678912345678, 17) > 0.12345678912345678
Louter
14.11.2016 20:22+1Про точные вычисления в JS:
Если понадобится прецизионная точность работы с числами, то MathJS в помощь: можно указать точность 64 (и больше) бит и уже 0.1 + 0.2 === 0.3
Про ГПСЧ:
Если возможности позволяют, то два и более ГПСЧ (с разных устройств) помогают создать нормальный рандом. В отдельных случаях движения мыши вполне годны для ГСЧ.
vintage
Плохая новость в том, что в вебките размеры отрисовки будут скакать на 1 пиксель туда-сюда. Например, однопиксельный бордюр будет местами двухпиксельным, а местами его вообще не будет.
vintage
А самое печальное, что это касается и таких значений как: 1%, 1.2rem, 1.2em, 10vmin, 10vh, 10vw и тд. Короче, чтобы всё было хорошо, получаемые в результате пиксельные размеры должны быть целыми.
demimurych
Поправьте меня если я ошибаюсь.
Скакать на пиксель туда или сюда в одном и том же окружении он не будет. Под одним и тем же окружением я понимаю то от чего берется процент для относительной единицы. Грубо говоря, если размер области 100 пикселей, то позиция элемента left 3.7px всегда будет в одном и том же месте и скакнуть на пиксель влево или вправо может только в случае изменения размера области.
Равно и в случае с бордером. Он никогда не будет нулевым. Может быть меньше может быть больше, но не нулевой. Исключение только в случаях когда область просмотра масштабируется в область с меньшим разрешением.
vintage
Тут проблема в том, что в разных местах происходит округление в разную сторону и получаются артефакты типа этих: https://jsfiddle.net/sb4206bz/1/
Насчёт пропадания именно бордера — у меня не получилось сейчас воспроизвести (получилось только для box-shadow: https://jsfiddle.net/sb4206bz/2/). Возможно в свежих билдах хрома это пофиксили, а возможно я не так воспроизвожу. Но проблемы такие были. По той же причине.
profesor08
Ужас. Аналогичное часто наблюдал в верстке различных макетов, где есть основной задний фон на весь экран и центральный плавающий блок (margin: 0 auto).