Лекция
Привет, сегодня поговорим про числа в js, обещаю рассказать все что знаю. Для того чтобы лучше понимать что такое числа в js, работа с числами в js , настоятельно рекомендую прочитать все из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend).
Infinity
NaN
isFinite(n)
isNaN
— проверка на число для строкparseInt
и parseFloat
toString(система счисления)
num.toFixed(precision)
Все числа в JavaScript, как целые так и дробные, имеют тип Number
и хранятся в 64-битном форматеIEEE-754, также известном как «double precision».
Здесь мы рассмотрим различные тонкости, связанные с работой с числами в JavaScript.
В JavaScript можно записывать числа не только в десятичной, но и в шестнадцатеричной (начинается с0x
), а также восьмеричной (начинается с 0
) системах счисления:
1 |
alert( 0xFF ); // 255 в шестнадцатиричной системе |
2 |
alert( 010 ); // 8 в восьмеричной системе |
Также доступна запись в «научном формате» (еще говорят «запись с плавающей точкой»), который выглядит как <число>e<кол-во нулей>
.
Например, 1e3
— это 1
с 3
нулями, то есть 1000
.
1 |
// еще пример научной формы: 3 с 5 нулями |
2 |
alert( 3e5 ); // 300000 |
Если количество нулей отрицательно, то число сдвигается вправо за десятичную точку, так что получается десятичная дробь:
1 |
// здесь 3 сдвинуто 5 раз вправо, за десятичную точку. |
2 |
alert( 3e-5 ); // 0.00003 <-- 5 нулей, включая начальный ноль |
Infinity
Представьте, что вы собираетесь создать новый язык… Люди будут называть его «JavaScript» (или LiveScript… неважно).
Что должно происходить при попытке деления на ноль?
Как правило, ошибка в программе… Во всяком случае, в большинстве языков программирования это именно так.
Но создатель JavaScript решил более «математическим» путем. Ведь чем меньше делитель, тем больше результат. При делении на очень-очень маленькое число должно получиться очень большое. В математическом анализе это описывается через пределы, но если упрощенно, то в качестве результата деления на 0
мы получаем «бесконечность», которая обозначается символом ∞
или, в JavaScript: "Infinity"
.
1 |
alert(1/0); // Infinity |
2 |
alert(12345/0); // Infinity |
Infinity
— особенное численное значение, которое ведет себя в точности как математическая бесконечность ∞
.
Infinity
больше любого числа.
1 |
alert(Infinity > 1234567890); // true |
2 |
alert(Infinity + 5 == Infinity); // true |
Бесконечность можно присвоить и в явном виде: var x = Infinity
.
Бывает и минус бесконечность -Infinity
:
1 |
alert( -1 / 0 ); // -Infinity |
Бесконечность можно получить также, если сделать ну очень большое число, для которого количество разрядов в двоичном представлении не помещается в соответствующую часть стандартного 64-битного формата, например:
1 |
alert( 1e500 ); // Infinity |
NaN
Если математическая операция не может быть совершена, то возвращается специальное значение NaN
(Not-A-Number).
Например, деление 0/0
в математическом смысле неопределено, поэтому возвращает NaN
:
1 |
alert( 0 / 0 ); // NaN |
Значение NaN
используется для обозначения математической ошибки и обладает следующими свойствами:
NaN
— единственное, в своем роде, которое не равно ничему, включая себя.
Следующий код ничего не выведет:
1 |
if (NaN == NaN) alert( "==" ); // Ни один вызов |
2 |
if (NaN === NaN) alert( "===" ); // не сработает |
NaN
можно проверить специальной функцией isNaN(n)
, которая возвращает true
если аргумент — NaN
и false
для любого другого значения.
1 |
var n = 0/0; |
2 |
3 |
alert( isNaN(n) ); // true |
Еще один забавный способ проверки значения на NaN
— это проверить его на равенство самому себе, вот так:
1 |
var n = 0/0; |
2 |
3 |
if (n !== n) alert( 'n = NaN!' ); |
isNaN
.
NaN
«прилипчиво». Любая операция с NaN
возвращает NaN
.
1 |
alert( NaN + 1 ); // NaN |
Если аргумент isNaN
— не число, то он автоматически преобразуется к числу.
Никакие математические операции в JavaScript не могут привести к ошибке или «обрушить» программу.
В худшем случае, результат будет NaN
.
isFinite(n)
Итак, в JavaScript есть обычные числа и три специальных числовых значения: NaN
, Infinity
и-Infinity
.
Функция isFinite(n)
возвращает true
только тогда, когда n
— обычное число, а не одно из этих значений:
1 |
alert( isFinite(1) ); // true |
2 |
alert( isFinite(Infinity) ); // false |
3 |
alert( isFinite(NaN) ); // false |
Если аргумент isFinite
— не число, то он автоматически преобразуется к числу.
Строгое преобразование можно осуществить унарным плюсом '+'
1 |
var s = "12.34" ; |
2 |
alert( +s ); // 12.34 |
Строгое — означает, что если строка не является в точности числом, то результат будет NaN
:
1 |
alert( + "12test" ); // NaN |
Единственное исключение — пробельные символы в начале и в конце строки, которые игнорируются:
1 |
alert( + " -12" ); // -12 |
2 |
alert( + " \n34 \n" ); // 34, перевод строки \n является пробельным символом |
3 |
alert( + "" ); // 0, пустая строка становится нулем |
4 |
alert( + "1 2" ); // NaN, пробел посередине числа - ошибка |
Аналогичным образом происходит преобразование и в других математических операторах и функциях:
1 |
alert( '12.34' / "-2" ); // -6.17 |
isNaN
— проверка на число для строкФункция isNaN
является математической, она преобразует аргумент в число, а затем проверяет, NaN
это или нет.
Поэтому можно использовать ее для проверки:
1 |
var x = "-11.5" ; |
2 |
if (isNaN(x)) { |
3 |
alert( "Строка преобразовалась в NaN. Не число" ); |
4 |
} else { |
5 |
alert( "Число" ); |
6 |
} |
Единственный тонкий момент — в том, что пустая строка и строка из пробельных символов преобразуются к 0
:
1 |
alert(isNaN( " \n\n " )) // false, т.к. строка из пробелов преобразуется к 0 |
И, конечно же, проверка isNaN
посчитает числами значения false, true, null
, т.к. они хотя и не числа, но преобразуются к ним:
1 |
+ false = 0 |
2 |
+ true = 1 |
3 |
+ null = 0 |
4 |
+ undefined = NaN; |
parseInt
и parseFloat
В мире HTML/CSS многие значения не являются в точности числами. Например, метрики CSS: 10pt
или -12px
.
Оператор '+'
для таких значений возвратит NaN
:
1 |
alert( + "12px" ) // NaN |
Для удобного чтения таких значений существует функция parseInt
:
1 |
alert( parseInt( '12px' ) ); // 12 |
parseInt
и ее аналог parseFloat
преобразуют строку символ за символом, пока это возможно.
При возникновении ошибки возвращается число, которое получилось. parseInt
читает из строки целое число, а parseFloat
— дробное.
1 |
alert( parseInt( '12px' ) ) // 12, ошибка на символе 'p' |
2 |
alert( parseFloat( '12.3.4' ) ) // 12.3, ошибка на второй точке |
Конечно, существуют ситуации, когда parseInt/parseFloat
возвращают NaN
. Об этом говорит сайт https://intellect.icu . Это происходит при ошибке на первом же символе:
1 |
alert( parseInt( 'a123' ) ); // NaN |
parseInt('0..')
parseInt
(но не parseFloat
) понимает 16-ричную систему счисления:
1 |
alert( parseInt( '0xFF' ) ) // 255 |
В старом стандарте JavaScript он умел понимать и восьмеричную:
1 |
alert( parseInt( '010' ) ) // в некоторых браузерах 8 |
Если вы хотите быть уверенным, что число, начинающееся с нуля, будет интерпретировано верно — используйте второй необязательный аргумент parseInt
— основание системы счисления:
1 |
alert( parseInt( '010' , 10) ); // во всех браузерах 10 |
Если вам нужна действительно точная проверка на число, которая не считает числом строку из пробелов, логические и специальные значения — используйте следующую функцию 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(основание системы)
, например:
1 |
var n = 255; |
2 |
3 |
alert( n.toString(16) ); // ff |
Основание может быть любым от 2
до 36
.
2
бывает полезно для отладки битовых операций, которые мы пройдем чуть позже:
1 |
var n = 4; |
2 |
alert( n.toString(2) ); // 100 |
36
(по количеству букв в английском алфавите — 26, вместе с цифрами, которых 10) используется для того, чтобы «кодировать» число в виде буквенно-цифровой строки. В этой системе счисления сначала используются цифры, а затем буквы от a
до z
:
1 |
var n = 1234567890; |
2 |
alert( n.toString(36) ); // kf12oi |
При помощи такого кодирования можно сделать длинный цифровой идентификатор короче, чтобы затем использовать его в URL.
Одна из самых частых операций с числом — округление. В JavaScript существуют целых 3 функции для этого.
Math.floor
Math.ceil
Math.round
1 |
alert( Math.floor(3.1) ); // 3 |
2 |
alert( Math.ceil(3.1) ); // 4 |
3 |
alert( Math.round(3.1) ); // 3 |
Битовые операторы делают любое число 32-битным целым, обрезая десятичную часть.
В результате побитовая операция, которая не изменяет число, например, двойное битовое НЕ — округляет его:
1 |
alert( ~~12.3 ); // 12 |
Любая побитовая операция такого рода подойдет, например XOR (исключающее ИЛИ,"^"
) с нулем:
1 |
alert( 12.3 ^ 0 ); // 12 |
2 |
alert( 1.2 + 1.3 ^ 0); // 2, приоритет ^ меньше, чем + |
Это удобно в первую очередь тем, что легко читается и не заставляет ставить дополнительные скобки как Math.floor(...)
:
var x = a * b / c ^ 0; // читается так: "a*b/c и округлить" |
Обычный трюк — это умножить и поделить на 10 с нужным количеством нулей. Например, округлим3.456
до 2го знака после запятой:
1 |
var n = 3.456; |
2 |
alert( Math.round( n * 100 ) / 100 ); // 3.456 -> 345.6 -> 346 -> 3.46 |
Таким образом можно округлять число и вверх и вниз.
num.toFixed(precision)
Существует специальный метод num.toFixed(precision)
, который округляет число num
до точностиprecision
и возвращает результат в виде строки:
1 |
var n = 12.34; |
2 |
alert( n.toFixed(1) ); // "12.3" |
Округление идет до ближайшего значения, аналогично Math.round
:
1 |
var n = 12.36; |
2 |
alert( n.toFixed(1) ); // "12.4" |
Итоговая строка, при необходимости, дополняется нулями до нужной точности:
1 |
var n = 12.34; |
2 |
alert( n.toFixed(5) ); // "12.34000", добавлены нули до 5 знаков после запятой |
Если нам нужно именно число, то мы можем получить его, применив '+'
к результату n.toFixed(..)
:
1 |
var n = 12.34; |
2 |
alert( +n.toFixed(5) ); // 12.34 |
toFixed
не эквивалентен Math.round
!Например, произведем округление до одного знака после запятой с использованием двух способов:
1 |
var price = 6.35; |
2 |
3 |
alert( price.toFixed(1) ); // 6.3 |
4 |
alert( Math.round(price*10)/10 ); // 6.4 |
Math.round
получился более корректным, так как по общепринятым правилам 5
округляется вверх. А toFixed
может округлить его как вверх, так и вниз. Почему? Скоро узнаем!
Запустите этот пример:
1 |
alert(0.1 + 0.2 == 0.3); |
Запустили? Если нет — все же сделайте это.
Ок, вы запустили его. Результат несколько странный, не так ли? Возможно, ошибка в браузере? Поменяйте браузер, запустите еще раз.
Хорошо, теперь мы можем быть уверены: 0.1 + 0.2
это не 0.3
. Но тогда что же это?
1 |
alert(0.1 + 0.2); // 0.30000000000000004 |
Как видите, произошла небольшая вычислительная ошибка.
Дело в том, что в стандарте IEEE 754 на число выделяется ровно 8 байт(=64 бита), не больше и не меньше.
Число 0.1 (=1/10)
короткое в десятичном формате, а в двоичной системе счисления это бесконечная дробь (перевод десятичной дроби в двоичную систему). Также бесконечной дробью является0.2 (=2/10)
.
Двоичное значение бесконечных дробей хранится только до определенного знака, поэтому возникает неточность. Это даже можно увидеть:
1 |
alert( 0.1.toFixed(20) ); // 0.10000000000000000555 |
Когда мы складываем 0.1
и 0.2
, то две неточности складываются, получаем третью.
Конечно, это не означает, что точные вычисления для таких чисел невозможны. Они возможны. И даже необходимы.
Например, есть два способа сложить 0.1
и 0.2
:
1 |
alert( (0.1*10 + 0.2*10) / 10 ); // 0.3 |
Это работает, т.к. числа 0.1*10 = 1
и 0.2*10 = 2
могут быть точно представлены в двоичной системе.
1 |
var result = 0.1 + 0.2; |
2 |
alert( +result.toFixed(10) ); // 0.3 |
Привет! Я — число, растущее само по себе!
1 |
alert(9999999999999999); |
Причина та же — потеря точности.
Из 64
бит, отведенных на число, сами цифры числа занимают до 52
бит, остальные 11
бит хранят позицию десятичной точки и один бит — знак. Так что если 52
бит не хватает на цифры, то при записи пропадут младшие разряды.
Интерпретатор не выдаст ошибку, но в результате получится «не совсем то число», что мы и видим в примере выше. Как говорится: «как смог, так записал».
Ради справедливости заметим, что в точности то же самое происходит в любом другом языке, где используется формат IEEE 754, включая Java, C, PHP, Ruby, Perl.
JavaScript предоставляет базовые тригонометрические и некоторые другие функции для работы с числами.
Встроенные функции для тригонометрических вычислений:
Math.acos(x)
x
(в радианах)Math.asin(x)
x
(в радианах)Math.atan
x
(в радианах)Math.atan2(y, x)
(y, x)
. Описание функции: 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()
Infinity
.NaN
.parseInt/parseFloat
делают числа из строк, которые начинаются с числа.Math.floor
, Math.round
, Math.ceil
и битовый оператор. Для округления до нужного знака используйте +n.toFixed(p)
или трюк с умножением и делением на10p
.0
до 1
генерируются с помощью Math.random()
, остальные — преобразованием из них.Существуют и другие математические функции. Вы можете ознакомиться с ними в справочнике в разделах Number и Math.
К сожалению, в одной статье не просто дать все знания про числа в js. Но я - старался. Если ты проявишь интерес к раскрытию подробностей,я обязательно напишу продолжение! Надеюсь, что теперь ты понял что такое числа в js, работа с числами в js и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)
Комментарии
Оставить комментарий
Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)
Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)