28 KiB
Числа
Все числа в JavaScript, как целые так и дробные, имеют тип Number
и хранятся в 64-битном формате IEEE-754, также известном как "double precision".
Здесь мы рассмотрим различные тонкости, связанные с работой с числами в JavaScript.
Способы записи
В JavaScript можно записывать числа не только в десятичной, но и в шестнадцатеричной (начинается с 0x
), а также восьмеричной (начинается с 0
) системах счисления:
//+ run
alert( 0xFF ); // 255 в шестнадцатиричной системе
alert( 010 ); // 8 в восьмеричной системе
Также доступна запись в "научном формате" (ещё говорят "запись с плавающей точкой"), который выглядит как <число>e<кол-во нулей>
.
Например, 1e3
-- это 1
с 3
нулями, то есть 1000
.
//+ run
// еще пример научной формы: 3 с 5 нулями
alert( 3e5 ); // 300000
Если количество нулей отрицательно, то число сдвигается вправо за десятичную точку, так что получается десятичная дробь:
//+ run
// здесь 3 сдвинуто 5 раз вправо, за десятичную точку.
alert( 3e-5 ); // 0.00003 <-- 5 нулей, включая начальный ноль
Деление на ноль, Infinity
Представьте, что вы собираетесь создать новый язык... Люди будут называть его "JavaScript" (или LiveScript... неважно).
Что должно происходить при попытке деления на ноль?
Как правило, ошибка в программе... Во всяком случае, в большинстве языков программирования это именно так.
Но создатель JavaScript решил пойти математически правильным путем. Ведь чем меньше делитель, тем больше результат. При делении на очень-очень маленькое число должно получиться очень большое. В математическом анализе это описывается через [пределы](http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B5%D0%B4%D0%B5%D0%BB_(%D0%BC%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0)), и если подразумевать предел, то в качестве результата деления на 0
мы получаем "бесконечность", которая обозначается символом ∞
или, в JavaScript: "Infinity"
.
//+ run
alert( 1 / 0 ); // Infinity
alert( 12345 / 0 ); // Infinity
Infinity
-- особенное численное значение, которое ведет себя в точности как математическая бесконечность ∞
.
- `Infinity` больше любого числа.
- Добавление к бесконечности не меняет её.
//+ run
alert( Infinity > 1234567890 ); // true
alert( Infinity + 5 == Infinity ); // true
Бесконечность можно присвоить и в явном виде: var x = Infinity
.
Бывает и минус бесконечность -Infinity
:
//+ run
alert( -1 / 0 ); // -Infinity
Бесконечность можно получить также, если сделать ну очень большое число, для которого количество разрядов в двоичном представлении не помещается в соответствующую часть стандартного 64-битного формата, например:
//+ run
alert( 1e500 ); // Infinity
NaN
Если математическая операция не может быть совершена, то возвращается специальное значение NaN
(Not-A-Number).
Например, деление 0/0
в математическом смысле неопределено, поэтому его результат NaN
:
//+ run
alert( 0 / 0 ); // NaN
Значение NaN
используется для обозначения математической ошибки и обладает следующими свойствами:
- Значение `NaN` -- единственное, в своем роде, которое *не равно ничему, включая себя*.
Следующий код ничего не выведет:
//+ run if (NaN == NaN) alert( "==" ); // Ни один вызов if (NaN === NaN) alert( "===" ); // не сработает
- Значение `NaN` можно проверить специальной функцией `isNaN(n)`, которая преобразует аргумент к числу и возвращает `true`, если получилось `NaN`, и `false` -- для любого другого значения.
//+ run var n = 0 / 0; alert( isNaN(n) ); // true alert( isNaN("12") ); // false, строка преобразовалась к обычному числу 12
[smart header="Забавный способ проверки на
NaN
"] Отсюда вытекает забавный способ проверки значения наNaN
: можно проверить значение на равенство самому себе, если не равно -- тоNaN
://+ run var n = 0 / 0; if (n !== n) alert( 'n = NaN!' );
Это работает, но для наглядности лучше использовать
isNaN(n)
. [/smart] - Значение `NaN` "прилипчиво". Любая операция с `NaN` возвращает `NaN`.
//+ run alert( NaN + 1 ); // NaN
Если аргумент isNaN
-- не число, то он автоматически преобразуется к числу.
[smart header="Математические операции в JS безопасны"] Никакие математические операции в JavaScript не могут привести к ошибке или "обрушить" программу.
В худшем случае, результат будет NaN
.
[/smart]
isFinite(n)
Итак, в JavaScript есть обычные числа и три специальных числовых значения: NaN
, Infinity
и -Infinity
.
Тот факт, что они, хоть и особые, но -- числа, демонстрируется работой оператора +
:
//+ run
var value = prompt("Введите Infinity", 'Infinity');
*!*
var number = +value;
*/!*
alert( number ); // Infinity, плюс преобразовал строку "Infinity" к такому "числу"
Обычно если мы хотим от посетителя получить число, то Infinity
или NaN
нам не подходят. Для того, чтобы отличить "обычные" числа от таких специальных значений, существует функция isFinite
.
Функция isFinite(n)
преобразует аргумент к числу и возвращает true
, если это не NaN/Infinity/-Infinity
:
//+ run
alert( isFinite(1) ); // true
alert( isFinite(Infinity) ); // false
alert( isFinite(NaN) ); // false
Преобразование к числу
Большинство арифметических операций и математических функций преобразуют значение в число автоматически.
Для того, чтобы сделать это явно, обычно перед значением ставят унарный плюс '+'
:
//+ run
var s = "12.34";
alert( +s ); // 12.34
При этом, если строка не является в точности числом, то результат будет NaN
:
//+ run
alert( +"12test" ); // NaN
Единственное исключение -- пробельные символы в начале и в конце строки, которые игнорируются:
//+ run
alert( +" -12" ); // -12
alert( +" \n34 \n" ); // 34, перевод строки \n является пробельным символом
alert( +"" ); // 0, пустая строка становится нулем
alert( +"1 2" ); // NaN, пробел посередине числа - ошибка
Аналогичным образом происходит преобразование и в других математических операторах и функциях:
//+ run
alert( '12.34' / "-2" ); // -6.17
Мягкое преобразование: parseInt и parseFloat
В мире HTML/CSS многие значения не являются в точности числами. Например, метрики CSS: 10pt
или -12px
.
Оператор '+'
для таких значений возвратит NaN
:
//+ run
alert(+"12px") // NaN
Для удобного чтения таких значений существует функция parseInt
:
//+ run
alert( parseInt('12px') ); // 12
Функция parseInt
и ее аналог parseFloat
преобразуют строку символ за символом, пока это возможно.
При возникновении ошибки возвращается число, которое получилось. Функция parseInt
читает из строки целое число, а parseFloat
-- дробное.
//+ run
alert(parseInt('12px')) // 12, ошибка на символе 'p'
alert(parseFloat('12.3.4')) // 12.3, ошибка на второй точке
Конечно, существуют ситуации, когда parseInt/parseFloat
возвращают NaN
. Это происходит при ошибке на первом же символе:
//+ run
alert( parseInt('a123') ); // NaN
Проверка на число
Для проверки строки на число можно использовать функцию isNaN(str)
.
Она преобразует строку в число аналогично +
, а затем вернёт true
, если это NaN
, т.е. если преобразование не удалось:
//+ run
var x = prompt("Введите значение", "-11.5");
if (isNaN(x)) {
alert( "Строка преобразовалась в NaN. Не число" );
} else {
alert( "Число" );
}
Однако, у такой проверки есть две особенности:
- Пустая строка и строка из пробельных символов преобразуются к `0`, поэтому считаются числами.
- Если применить такую проверку не к строке, то могут быть сюрпризы, в частности `isNaN` посчитает числами значения `false, true, null`, так как они хотя и не числа, но преобразуются к ним.
//+ run
alert( isNaN(null) ); // false - не NaN, т.е. "число"
alert( isNaN("\n \n") ); // false - не NaN, т.е. "число"
Если такое поведение допустимо, то isNaN
-- приемлемый вариант.
Если же нужна действительно точная проверка на число, которая не считает числом строку из пробелов, логические и специальные значения, а также отсекает Infinity
-- используйте следующую функцию isNumeric
:
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
Разберёмся, как она работает. Начнём справа.
- Функция `isFinite(n)` преобразует аргумент к числу и возвращает `true`, если это не `Infinity/-Infinity/NaN`.
Таким образом, правая часть отсеет заведомо не-числа, но оставит такие значения как
true/false/null
и пустую строку''
, т.к. они корректно преобразуются в числа. - Для их проверки нужна левая часть. Вызов `parseFloat(true/false/null/'')` вернёт `NaN` для этих значений.
Так устроена функция
parseFloat
: она преобразует аргумент к строке, т.е.true/false/null
становятся"true"/"false"/"null"
, а затем считывает из неё число, при этом пустая строка даётNaN
.
В результате отсеивается всё, кроме строк-чисел и обычных чисел.
toString(система счисления)
Как показано выше, числа можно записывать не только в 10-чной, но и в 16-ричной системе. Но бывает и противоположная задача: получить 16-ричное представление числа. Для этого используется метод toString(основание системы)
, например:
//+ run
var n = 255;
alert( n.toString(16) ); // ff
В частности, это используют для работы с цветовыми значениями в браузере, вида #AABBCC
.
Основание может быть любым от 2
до 36
.
- Основание `2` бывает полезно для отладки побитовых операций:
//+ run var n = 4; alert( n.toString(2) ); // 100
- Основание `36` (по количеству букв в английском алфавите -- 26, вместе с цифрами, которых 10) используется для того, чтобы "кодировать" число в виде буквенно-цифровой строки. В этой системе счисления сначала используются цифры, а затем буквы от `a` до `z`:
//+ run var n = 1234567890; alert( n.toString(36) ); // kf12oi
При помощи такого кодирования можно "укоротить" длинный цифровой идентификатор, например чтобы выдать его в качестве URL.
Округление
Одна из самых частых операций с числом -- округление. В JavaScript существуют целых 3 функции для этого.
- `Math.floor`
- Округляет вниз
- `Math.ceil`
- Округляет вверх
- `Math.round`
- Округляет до ближайшего целого
//+ run no-beautify
alert( Math.floor(3.1) ); // 3
alert( Math.ceil(3.1) ); // 4
alert( Math.round(3.1) ); // 3
[smart header="Округление битовыми операторами"] Битовые операторы делают любое число 32-битным целым, обрезая десятичную часть.
В результате побитовая операция, которая не изменяет число, например, двойное битовое НЕ -- округляет его:
//+ run
alert( ~~12.3 ); // 12
Любая побитовая операция такого рода подойдет, например XOR (исключающее ИЛИ, "^"
) с нулем:
//+ run
alert( 12.3 ^ 0 ); // 12
alert( 1.2 + 1.3 ^ 0 ); // 2, приоритет ^ меньше, чем +
Это удобно в первую очередь тем, что легко читается и не заставляет ставить дополнительные скобки как Math.floor(...)
:
var x = a * b / c ^ 0; // читается как "a * b / c и округлить"
[/smart]
Округление до заданной точности
Для округления до нужной цифры после запятой можно умножить и поделить на 10 с нужным количеством нулей. Например, округлим 3.456
до 2го знака после запятой:
//+ run
var n = 3.456;
alert( Math.round(n * 100) / 100 ); // 3.456 -> 345.6 -> 346 -> 3.46
Таким образом можно округлять число и вверх и вниз.
num.toFixed(precision)
Существует также специальный метод num.toFixed(precision)
, который округляет число num
до точности precision
и возвращает результат в виде строки:
//+ run
var n = 12.34;
alert( n.toFixed(1) ); // "12.3"
Округление идёт до ближайшего значения, аналогично Math.round
:
//+ run
var n = 12.36;
alert( n.toFixed(1) ); // "12.4"
Итоговая строка, при необходимости, дополняется нулями до нужной точности:
//+ run
var n = 12.34;
alert( n.toFixed(5) ); // "12.34000", добавлены нули до 5 знаков после запятой
Если нам нужно именно число, то мы можем получить его, применив '+'
к результату n.toFixed(..)
:
//+ run
var n = 12.34;
alert( +n.toFixed(5) ); // 12.34
[warn header="Метод toFixed
не эквивалентен Math.round
!"]
Например, произведём округление до одного знака после запятой с использованием двух способов: toFixed
и Math.round
с умножением и делением:
//+ run
var price = 6.35;
alert( price.toFixed(1) ); // 6.3
alert( Math.round(price * 10) / 10 ); // 6.4
Как видно, результат разный! Вариант округления через Math.round
получился более корректным, так как по общепринятым правилам 5
округляется вверх. А toFixed
может округлить его как вверх, так и вниз. Почему? Скоро узнаем!
[/warn]
Неточные вычисления
Запустите этот пример:
//+ run
alert( 0.1 + 0.2 == 0.3 );
Запустили? Если нет -- все же сделайте это.
Ок, вы запустили его. Он вывел false
. Результат несколько странный, не так ли? Возможно, ошибка в браузере? Поменяйте браузер, запустите еще раз.
Хорошо, теперь мы можем быть уверены: 0.1 + 0.2
это не 0.3
. Но тогда что же это?
//+ run
alert( 0.1 + 0.2 ); // 0.30000000000000004
Как видите, произошла небольшая вычислительная ошибка, результат сложения 0.1 + 0.2
немного больше, чем 0.3
.
//+ run
alert( 0.1 + 0.2 > 0.3 ); // true
Всё дело в том, что в стандарте IEEE 754 на число выделяется ровно 8 байт(=64 бита), не больше и не меньше.
Число 0.1 (одна десятая)
записывается просто в десятичном формате, а в двоичной системе счисления это бесконечная дробь (перевод десятичной дроби в двоичную систему). Также бесконечной дробью является 0.2 (=2/10)
.
Двоичное значение бесконечных дробей хранится только до определенного знака, поэтому возникает неточность. Её даже можно увидеть:
//+ run
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
Когда мы складываем 0.1
и 0.2
, то две неточности складываются, получаем незначительную, но всё же ошибку в вычислениях.
Конечно, это не означает, что точные вычисления для таких чисел невозможны. Они возможны. И даже необходимы.
Например, есть два способа сложить 0.1
и 0.2
:
- Сделать их целыми, сложить, а потом поделить:
//+ run alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3
Это работает, т.к. числа
0.1*10 = 1
и0.2*10 = 2
могут быть точно представлены в двоичной системе. - Сложить, а затем округлить до разумного знака после запятой. Округления до 10-го знака обычно бывает достаточно, чтобы отсечь ошибку вычислений:
//+ run var result = 0.1 + 0.2; alert( +result.toFixed(10) ); // 0.3
[smart header="Забавный пример"] Привет! Я -- число, растущее само по себе!
//+ run
alert( 9999999999999999 ); // выведет 10000000000000000
Причина та же -- потеря точности.
Из 64
бит, отведённых на число, сами цифры числа занимают до 52
бит, остальные 11
бит хранят позицию десятичной точки и один бит -- знак. Так что если 52
бит не хватает на цифры, то при записи пропадут младшие разряды.
Интерпретатор не выдаст ошибку, но в результате получится "не совсем то число", что мы и видим в примере выше. Как говорится: "как смог, так записал".
[/smart]
Ради справедливости заметим, что в точности то же самое происходит в любом другом языке, где используется формат IEEE 754, включая Java, C, PHP, Ruby, Perl.
Другие математические методы
JavaScript предоставляет базовые тригонометрические и некоторые другие функции для работы с числами.
Тригонометрия
Встроенные функции для тригонометрических вычислений:
- `Math.acos(x)`
- Возвращает арккосинус `x` (в радианах)
- `Math.asin(x)`
- Возвращает арксинус `x` (в радианах)
- `Math.atan(x)`
- Возвращает арктангенс `x` (в радианах)
- `Math.atan2(y, x)`
- Возвращает угол до точки `(y, x)`. Описание функции: [Atan2](http://en.wikipedia.org/wiki/Atan2).
- `Math.sin(x)`
- Вычисляет синус `x` (в радианах)
- `Math.cos(x)`
- Вычисляет косинус `x` (в радианах)
- `Math.tan(x)`
- Возвращает тангенс `x` (в радианах)
Функции общего назначения
Разные полезные функции:
- `Math.sqrt(x)`
- Возвращает квадратный корень из `x`.
- `Math.log(x)`
- Возвращает натуральный (по основанию
e
) логарифм `x`. - `Math.pow(x, exp)`
- Возводит число в степень, возвращает
xexp
, например `Math.pow(2,3) = 8`. Работает в том числе с дробными и отрицательными степенями, например: `Math.pow(4, -1/2) = 0.5`. - `Math.abs(x)`
- Возвращает абсолютное значение числа
- `Math.exp(x)`
- Возвращает
ex
, гдеe
-- основание натуральных логарифмов. - `Math.max(a, b, c...)`
- Возвращает наибольший из списка аргументов
- `Math.min(a, b, c...)`
- Возвращает наименьший из списка аргументов
- `Math.random()`
- Возвращает псевдо-случайное число в интервале [0,1) - то есть между 0(включительно) и 1(не включая). Генератор случайных чисел инициализуется текущим временем.
Форматирование
Для красивого вывода чисел в стандарте ECMA 402 есть метод toLocaleString()
:
//+ run
var number = 123456789;
alert( number.toLocaleString() ); // 123 456 789
Его поддерживают все современные браузеры, кроме IE10- (для которых нужно подключить библиотеку Intl.JS). Он также умеет форматировать валюту и проценты. Более подробно про устройство этого метода можно будет узнать в статье , когда это вам понадобится.
Итого
- Числа могут быть записаны в шестнадцатиричной, восьмеричной системе, а также "научным" способом.
- В JavaScript существует числовое значение бесконечность `Infinity`.
- Ошибка вычислений дает `NaN`.
- Арифметические и математические функции преобразуют строку в точности в число, игнорируя начальные и конечные пробелы.
- Функции `parseInt/parseFloat` делают числа из строк, которые начинаются с числа.
- Есть четыре способа округления: `Math.floor`, `Math.round`, `Math.ceil` и битовый оператор. Для округления до нужного знака используйте `+n.toFixed(p)` или трюк с умножением и делением на
10p
. - Дробные числа дают ошибку вычислений. При необходимости ее можно отсечь округлением до нужного знака.
- Случайные числа от `0` до `1` генерируются с помощью `Math.random()`, остальные -- преобразованием из них.
Существуют и другие математические функции. Вы можете ознакомиться с ними в справочнике в разделах Number и Math.