Новые возможности при работе с числами и объектом Math в ES6
1 Обзор
Теперь вы можете задавать числа в двоичной и восьмеричной нотации:
> 0xFF // ES5: шестнадцатеричная
255
> 0b11 // ES6: двоичная
3
> 0o10 // ES6: восьмеричная
8
Глобальный объект Number
получил несколько новых свойств. В том числе:
Number.EPSILON
для сравнения чисел с плавающей запятой с допуском ошибок округления;- метод и константы для определения, является ли целое число безопасным в JavaScript (в 53-битном диапазоне, в котором нет потери точности).
2 Новые целочисленные литералы
В ECMAScript 5 уже есть литералы для шестнадцатеричных целых чисел:
> 0x9
9
> 0xA
10
> 0x10
16
> 0xFF
255
ECMAScript 6 привносит два новых типа целочисленных литералов:
- двоичные литералы с префиксом
0b
или0B
:> 0b11 3 > 0b100 4
- восьмеричные литералы с префиксом
0o
или0O
(да, это ноль с последующей заглавной буквой O; с вами всё будет хорошо, если будете использовать первый вариант):> 0o7 7 > 0o10 8
Помните, что метод Number.prototype.toString(основание_системы_счисления)
может использоваться, чтобы конвертировать числа:
> (255).toString(16)
'ff'
> (4).toString(2)
'100'
> (8).toString(8)
'10'
2.1 Использование восьмеричных литералов: права доступа к файлам в Unix-стиле
В модуле файловой системы Node.js у нескольких функций есть параметр mode. Его значение используется, чтобы задать права доступа к файлу с помощью кодов, которые являются наследием из Unix:
- права доступа могут быть заданы для трех категорий пользователей:
- User: владелец файла;
- Group: члены группы, связанной с файлом;
- All: все;
- каждой категории могут быть даны следующие разрешения;
- r (read): пользователям из категории разрешено читать файл;
- w (write): пользователям из категории разрешено изменять файл;
- x (execute): пользователям из категории разрешено выполнять файл.
Это означает, что права доступа могут быть представлены 9 битами (3 категории с 3 разрешениями в каждой):
User | Group | All | |
---|---|---|---|
Разрешения | r, w, x | r, w, x | r, w, x |
Бит | 8, 7, 6 | 5, 4, 3 | 2, 1, 0 |
Разрешения одной из категорий пользователей хранятся в 3 битах:
Биты | Разрешения | Восьмеричное число |
---|---|---|
000 | ––– | 0 |
001 | ––x | 1 |
010 | –w– | 2 |
011 | –wx | 3 |
100 | r–– | 4 |
101 | r–x | 5 |
110 | rw– | 6 |
111 | rwx | 7 |
Это означает, что восьмеричные числа – это компактное представление всех разрешений. Вам необходимы только 3 цифры, по одной цифре на каждую категорию пользователей. Два примера:
- 755 = 111, 101, 101: я могу изменять, читать и выполнять; все остальные могут только читать и выполнять;
- 640 = 110, 100, 000: я могу читать и записывать; пользователи группы могут читать; у остальных нет доступа совсем.
2.2 parseInt()
и новые целочисленные литералы
У parseInt()
следующая сигнатура:
parseInt(string, radix?)
Она обеспечивает специальную поддержку шестнадцатеричной нотации литералов: префикс строки 0x
(или 0X
) удаляется если:
- основание системы счисления (
radix
) пропущено или равно 0. Тогдаradix
устанавливается равным 16; radix
уже равен 16.
Например:
> parseInt('0xFF')
255
> parseInt('0xFF', 0)
255
> parseInt('0xFF', 16)
255
Во всех остальных случаях, строка обрабатывается только до первого символа не цифры:
> parseInt('0xFF', 10)
0
> parseInt('0xFF', 17)
0
В parseInt()
нет специальной поддержки двоичных и восьмеричных литералов:
> parseInt('0b111')
0
> parseInt('0b111', 2)
0
> parseInt('111', 2)
7
> parseInt('0o10')
0
> parseInt('0o10', 8)
0
> parseInt('10', 8)
8
Если вы хотите парсить эти типы литералов, то должны использовать Number()
:
> Number('0b111')
7
> Number('0o10')
8
С другой стороны, вы также можете удалить префикс и использовать parseInt() с соответствующим основанием системы счисления:
> parseInt('111', 2)
7
> parseInt('10', 8)
8
3 Новые свойства Number
В этом разделе описываются свойства, которые появились в Number
в ECMAScript 6.
3.1 Функции, которые были глобальными
Четыре функции, связанные с числами, которые уже были доступны как глобальные функции, были добавлены (без изменений, либо с незначительными изменениями) к Number
, как методы: isFinite
, isNaN
, parseFloat
и parseInt
.
Number.isFinite(number)
Является ли number
вещественным числом (либо оно является бесконечностью, или минус бесконечностью, или NaN
)?
> Number.isFinite(Infinity)
false
> Number.isFinite(-Infinity)
false
> Number.isFinite(NaN)
false
> Number.isFinite(123)
true
Преимущество данного метода заключается в том, что его параметр необязательно должен быть числом (тогда как у глобальной функции должен):
> Number.isFinite('123')
false
> isFinite('123')
true
Number.isNaN(number)
Является ли number
значением NaN
? Подобная проверка через ===
является грязным хаком. NaN
– единственное значение, которое не равно само себе:
> let x = NaN;
> x === NaN
false
Поэтому для подобной проверки используется следующее выражение
> x !== x
true
Использование Number.isNaN()
более наглядно:
> Number.isNaN(x)
true
Преимуществом Number.isNaN()
так же является то, что его параметр необязательно должен быть числом (в отличие от глобальной функции):
> Number.isNaN('???')
false
> isNaN('???')
true
Number.parseFloat
и Number.parseInt
Следующие два метода работают в точности так же, как и глобальные функции с аналогичными именами. Они были добавлены к Number
ради полноты; теперь все функции, связанные с числами, доступны в нем.
Number.parseFloat(string)
Number.parseInt(string, radix)
3.2 Number.EPSILON
Ошибки округления, особенно с десятичными дробями, могут стать проблемой в JavaScript. Например, 0.1 и 0.2 могут быть представлены с точностью, которую вы можете заметить, если сложите их и сравните сумму с 0.3 (которое может быть представлено неточно).
> 0.1 + 0.2 === 0.3
false
Number.EPSILON
задает приемлемые границы ошибок при сравнении чисел с плавающей запятой. Оно обеспечивает лучший способ для сравнения значений с плавающей запятой, как показано в следующей функции:
function epsEqu(x, y) {
return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true
3.3 Number.isInteger(number)
В JavaScript числа только с плавающей запятой (double
). Соответственно, целые числа – это просто числа с плавающей запятой без дробной части.
Number.isInteger(number)
возвращает true
, если number
является числом, и у него нет дробной части.
> Number.isInteger(-17)
true
> Number.isInteger(33)
true
> Number.isInteger(33.1)
false
> Number.isInteger('33')
false
> Number.isInteger(NaN)
false
> Number.isInteger(Infinity)
false
3.4 Безопасные целые числа
У JavaScript чисел пространства для хранения достаточно для представления 53-битных целых чисел со знаком. Это означает, что целые числа i в диапазоне –253 < i < 253 безопасны. Следующие свойства помогают определить, является ли целое число безопасным в JavaScript:
Number.isSafeInteger(number)
Number.MIN_SAFE_INTEGER
Number.MAX_SAFE_INTEGER
Понятие безопасных целых чисел основывается на том, как математические целые числа представлены в JavaScript. В диапазоне (–253, 253) (кроме нижней и верхней границ) целые числа JavaScript безопасны: это означает полное соответствие между ними и математическими целыми числами, которые они представляют.
За этим диапазоном, целые числа JavaScript небезопасны: два или более математических целых числа представлены как одно целое число JavaScript. Например, начиная с 253, JavaScript может представить только каждое второе математическое число:
> Math.pow(2, 53)
9007199254740992
> 9007199254740992
9007199254740992
> 9007199254740993
9007199254740992
> 9007199254740994
9007199254740994
> 9007199254740995
9007199254740996
> 9007199254740996
9007199254740996
> 9007199254740997
9007199254740996
Таким образом, одно безопасное целое число JavaScript однозначно представляет одно математическое целое число.
Свойства Number
Два свойства Number
, задающие нижнюю и верхнюю границы безопасных целых чисел, могут быть определены, как показано ниже:
Number.MAX_SAFE_INTEGER = Math.pow(2, 53)-1;
Number.MIN_SAFE_INTEGER = -Number.MAX_SAFE_INTEGER;
Number.isSafeInteger()
определяет, является число JavaScript безопасным целым числом, и может быть определен, как показано ниже:
Number.isSafeInteger = function (n) {
return (typeof n === 'number' &&
Math.round(n) === n &&
Number.MIN_SAFE_INTEGER <= n &&
n <= Number.MAX_SAFE_INTEGER);
}
Для переданного значения n
, данная функция сначала проверяет, является ли n
числом и целым числом. Если обе проверки удачны, n
безопасно, если больше или равно MIN_SAFE_INTEGER
и меньше или равно MAX_SAFE_INTEGER
.
Безопасные результаты арифметических вычислений
Как мы можем убедиться в том, что результаты арифметических вычислений корректны? Например, следующий результат точно некорректен:
> 9007199254740990 + 3
9007199254740992
У нас два безопасных аргумента, но небезопасный результат:
> Number.isSafeInteger(9007199254740990)
true
> Number.isSafeInteger(3)
true
> Number.isSafeInteger(9007199254740992)
false
Следующий результат также некорректен:
> 9007199254740995 - 10
9007199254740986
На этот раз результат безопасен, но один из аргументов нет:
> Number.isSafeInteger(9007199254740995)
false
> Number.isSafeInteger(10)
true
> Number.isSafeInteger(9007199254740986)
true
Таким образом, результат применения оператора op
над целыми числами гарантировано будет корректным, если все аргументы и результат безопасны. Более формально:
isSafeInteger(a) && isSafeInteger(b) && isSafeInteger(a op b)
подразумевает, что a op b
даст корректный результат.
4 Math
У глобального объекта Math появилось несколько новых методов ECMAScript 6.
4.1 Различный функционал при работе с числами
Math.sign(x)
Возвращает знак x
, как -1
или +1
. Кроме случаев, когда x
либо NaN
, либо 0
, тогда будет возвращено x
[1].
[1]: Хотя вы обычно этого не видите, но -0 выдает результат -0, а +0 выдает результат +0.
> Math.sign(-8)
-1
> Math.sign(3)
1
> Math.sign(0)
0
> Math.sign(NaN)
NaN
> Math.sign(-Infinity)
-1
> Math.sign(Infinity)
1
Math.trunc(x)
Удаляет десятичную дробь x
.
> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3
Math.cbrt(x)
Возвращает кубический корень x
(\(\sqrt{x^3}\))
> Math.cbrt(8)
2
4.2 Использование 0 вместо 1 в возведении в степень и логарифмах
Маленькая дробь может быть представлена более точно, если она идет после нуля. Я продемонстрирую это с помощью десятичных дробей. (Внутренне, числа с плавающей запятой в JavaScript на базе 2, но внешне вы видите их, как на базе 10.) Числа с плавающей запятой на базе 10 представлены, как мантисса x 10степень. Если перед запятой идет ноль, то у малых дробей будет меньше значащих цифр. Например,
- (A) 0.000000234 = 2.34 × 10−7. Значащие цифры: 234
- (B) 1.000000234 = 1.000000234 × 100. Значащие цифры: 1000000234
Относительно точности, значение степени здесь не является проблемой, а вот объем мантиссы является. Вот почему (A) даст вам большую точность, чем (B).
Вы можете увидеть это при следующих действиях: первое число (1 x 10–16) отличается от ноля, хотя это же число, добавленное к 1, равно 1.
> 1e-16 === 0
false
> 1 + 1e-16 === 1
true
Math.expm1(x)
Возвращает Math.exp(x)–1
. Обратный метод для Math.log1p()
.
Таким образом, данный метод обеспечивает большую точность там, где результат Math.exp()
близок к 1. Вы можете увидеть разницу между этими методами при следующих действиях:
> Math.expm1(1e-10)
1.00000000005e-10
> Math.exp(1e-10)-1
1.000000082740371e-10
Первый результат лучше, что вы можете проверить с помощью библиотеки (например, demical.js) для чисел с плавающей запятой с произвольной точностью ("bigfloats"):
> var Decimal = require('decimal.js').config({precision:50});
> new Decimal(1e-10).exp().minus(1).toString()
'1.000000000050000000001666666666708333333e-10'
Math.log1p(x)
Возвращает Math.log(1 + x)
. Обратный метод для Math.expm1()
.
Таким образом, данный метод позволяет вам задать параметры, которые близки к 1, с большей точностью.
Мы уже установили, что 1 + 1e-16 === 1. Следовательно, не станет сюрпризом, что следующие два вызова log()
дадут одинаковый результат:
> Math.log(1 + 1e-16)
0
> Math.log(1 + 0)
0
log1p()
, наоборот, даст разные результаты:
> Math.log1p(1e-16)
1e-16
> Math.log1p(0)
0
4.3 Логарифмы по основанию 2 и 10
Math.log2(x)
Вычисляет логарифм по основанию 2.
> Math.log2(8)
3
Math.log10(x)
Вычисляет логарифм по основанию 10.
> Math.log10(100)
2
4.4 Поддержка компиляции в JavaScript
Emscipten разработал стиль кодирования, который был взят asm.js: операции виртуальной машины (байткод) представлены в статическом подмножестве JavaScript. Это подмножество может эффективно выполняться JavaScript движками: если это результат компиляции из C++, то оно работает примерно на 70% от исходной скорости.
Math.fround(x)
Округляет x
до 32-битного значения с плавающей запятой (float
). Используется в asm.js, чтобы сообщить движку, что внутри необходимо использовать значение типа float
.
Math.imul(x, y)
Перемножает два 32-битных целых числа x и y и возвращает младшие 32 бита результата. Это единственная 32-битная базовая математическая операция, которая не может быть смоделирована с помощью оператора JavaScript и которая приводит результат обратно к 32 битам. Например, idiv
может быть реализован следующим образом:
function idiv(x, y) {
return (x / y) | 0;
}
В отличие от этого, умножение двух больших 32-битных целых чисел может дать значение типа double, которое настолько велико, что младшие биты потеряются.
4.5 Побитовые операции
Math.clz32(x)
Подсчитывает ведущие нулевые биты в 32-битном целом числе x
.
> Math.clz32(0b01000000000000000000000000000000)
1
> Math.clz32(0b00100000000000000000000000000000)
2
> Math.clz32(2)
30
> Math.clz32(1)
31
4.6 Тригонометрические методы
Math.sinh(x)
Рассчитывает гиперболический синусx
Math.cosh(x)
Рассчитывает гиперболический косинусx
Math.tanh(x)
Рассчитывает гиперболический тангенсx
Math.asinh(x)
Рассчитывает гиперболический арксинусx
Math.acosh(x)
Рассчитывает гиперболический арккосинусx
Math.atanh(x)
Рассчитывает гиперболический арктангенсx
Math.hypot(...values)
Рассчитывает квадратный корень суммы квадратов аргументов