Новые возможности при работе с числами и объектом Math в ES6

Добавлено 6 ноября 2015 в 08:00

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 разрешениями в каждой):

 UserGroupAll
Разрешенияr, w, xr, w, xr, w, x
Бит8, 7, 65, 4, 32, 1, 0

Разрешения одной из категорий пользователей хранятся в 3 битах:

БитыРазрешенияВосьмеричное число
000–––0
001––x1
010–w–2
011–wx3
100r––4
101r–x5
110rw–6
111rwx7

Это означает, что восьмеричные числа – это компактное представление всех разрешений. Вам необходимы только 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)
    Рассчитывает квадратный корень суммы квадратов аргументов

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.


Сообщить об ошибке