Новые возможности при работе с числами и объектом 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_INTEGERNumber.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)
Рассчитывает гиперболический синусxMath.cosh(x)
Рассчитывает гиперболический косинусxMath.tanh(x)
Рассчитывает гиперболический тангенсxMath.asinh(x)
Рассчитывает гиперболический арксинусxMath.acosh(x)
Рассчитывает гиперболический арккосинусxMath.atanh(x)
Рассчитывает гиперболический арктангенсxMath.hypot(...values)
Рассчитывает квадратный корень суммы квадратов аргументов
