update
This commit is contained in:
parent
962caebbb7
commit
87bf53d076
1825 changed files with 94929 additions and 0 deletions
11
1-js/4-data-structures/2-number/1-sum-interface/solution.md
Normal file
11
1-js/4-data-structures/2-number/1-sum-interface/solution.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
```js
|
||||
//+ run demo
|
||||
var a = +prompt("Введите первое число", "");
|
||||
var b = +prompt("Введите второе число", "");
|
||||
|
||||
alert( a + b );
|
||||
```
|
||||
|
||||
Обратите внимание на оператор `+` перед `prompt`, он сразу приводит вводимое значение к числу. Если бы его не было, то `a` и `b` были бы строками и складывались бы как строки, то есть `"1" + "2" = "12"`.
|
9
1-js/4-data-structures/2-number/1-sum-interface/task.md
Normal file
9
1-js/4-data-structures/2-number/1-sum-interface/task.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Интерфейс суммы
|
||||
|
||||
[importance 5]
|
||||
|
||||
Создайте страницу, которая предлагает ввести два числа и выводит их сумму.
|
||||
|
||||
[demo /]
|
||||
|
||||
P.S. Есть "подводный камень" при работе с типами.
|
|
@ -0,0 +1,8 @@
|
|||
Во внутреннем двоичном представлении `6.35` является бесконечной двоичной дробью. Хранится она с потерей точности.. А впрочем, посмотрим сами:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( 6.35.toFixed(20) ); // 6.34999999999999964473
|
||||
```
|
||||
|
||||
Интерпретатор видит число как `6.34...`, поэтому и округляет вниз.
|
19
1-js/4-data-structures/2-number/2-why-rounded-down/task.md
Normal file
19
1-js/4-data-structures/2-number/2-why-rounded-down/task.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Почему 6.35.toFixed(1) == 6.3?
|
||||
|
||||
[importance 4]
|
||||
|
||||
В математике принято, что `5` округляется вверх, например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( 1.5.toFixed(0) ); // 2
|
||||
alert( 1.35.toFixed(1) ); // 1.4
|
||||
```
|
||||
|
||||
Но почему в примере ниже `6.35` округляется до `6.3`?
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( 6.35.toFixed(1) ); // 6.3
|
||||
```
|
||||
|
13
1-js/4-data-structures/2-number/3-sum-prices/solution.md
Normal file
13
1-js/4-data-structures/2-number/3-sum-prices/solution.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
Есть два основных подхода.
|
||||
<ol>
|
||||
<li>Можно хранить сами цены в "копейках" (центах и т.п.). Тогда они всегда будут целые и проблема исчезнет. Но при показе и при обмене данными нужно будет это учитывать и не забывать делить на 100.</li>
|
||||
<li>При операциях, когда необходимо получить окончательный результат -- округлять до 2го знака после запятой. Все, что дальше -- ошибка округления:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var price1 = 0.1, price2 = 0.2;
|
||||
alert( +(price1 + price2).toFixed(2) );
|
||||
```
|
||||
|
||||
</li>
|
||||
</ol>
|
16
1-js/4-data-structures/2-number/3-sum-prices/task.md
Normal file
16
1-js/4-data-structures/2-number/3-sum-prices/task.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Сложение цен
|
||||
|
||||
[importance 5]
|
||||
|
||||
Представьте себе электронный магазин. Цены даны с точностью до копейки(цента, евроцента и т.п.).
|
||||
|
||||
Вы пишете интерфейс для него. Основная работа происходит на сервере, но и на клиенте все должно быть хорошо. Сложение цен на купленные товары и умножение их на количество является обычной операцией.
|
||||
|
||||
Получится глупо, если при заказе двух товаров с ценами `0.10$` и `0.20$` человек получит общую стоимость `0.30000000000000004$`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( 0.1 + 0.2 + '$' );
|
||||
```
|
||||
|
||||
Что можно сделать, чтобы избежать проблем с ошибками округления?
|
|
@ -0,0 +1,14 @@
|
|||
Потому что `i` никогда не станет равным `10`.
|
||||
|
||||
Запустите, чтобы увидеть *реальные* значения `i`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var i = 0;
|
||||
while(i < 11) {
|
||||
i += 0.2;
|
||||
if (i>9.8 && i<10.2) alert(i);
|
||||
}
|
||||
```
|
||||
|
||||
Ни одно из них в точности не равно `10`.
|
13
1-js/4-data-structures/2-number/4-endless-loop-error/task.md
Normal file
13
1-js/4-data-structures/2-number/4-endless-loop-error/task.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Бесконечный цикл по ошибке
|
||||
|
||||
[importance 4]
|
||||
|
||||
Этот цикл - бесконечный. Почему?
|
||||
|
||||
```js
|
||||
var i = 0;
|
||||
while(i != 10) {
|
||||
i += 0.2;
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
function getDecimal(num) {
|
||||
var str = "" + num;
|
||||
var zeroPos = str.indexOf(".");
|
||||
if (zeroPos == -1) return 0;
|
||||
str = str.slice( zeroPos );
|
||||
return +str;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
describe("getDecimal", function() {
|
||||
it("возвращает дробную часть 1.2 как 0.2", function() {
|
||||
assert.equal( getDecimal(1.2), 0.2 );
|
||||
});
|
||||
|
||||
it("возвращает дробную часть 1.3 как 0.3", function() {
|
||||
assert.equal( getDecimal(1.3), 0.3 );
|
||||
});
|
||||
|
||||
it("возвращает дробную часть 12.345 как 0.345", function() {
|
||||
assert.equal( getDecimal(12.345), 0.345 );
|
||||
});
|
||||
|
||||
it("возвращает дробную часть -1.2 как 0.2", function() {
|
||||
assert.equal( getDecimal(-1.2), 0.2 );
|
||||
});
|
||||
|
||||
it("возвращает дробную часть 5 как 0", function() {
|
||||
assert.equal( getDecimal(5), 0 );
|
||||
});
|
||||
});
|
82
1-js/4-data-structures/2-number/5-get-decimal/solution.md
Normal file
82
1-js/4-data-structures/2-number/5-get-decimal/solution.md
Normal file
|
@ -0,0 +1,82 @@
|
|||
# Функция
|
||||
|
||||
Первая идея может быть такой:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function getDecimal(num) {
|
||||
return num - Math.floor(num);
|
||||
}
|
||||
|
||||
alert( getDecimal(12.5) ); // 0.5
|
||||
*!*
|
||||
alert( getDecimal(-1.2) ); // 0.8, неверно!
|
||||
*/!*
|
||||
```
|
||||
|
||||
Как видно из примера выше, для отрицательных чисел она не работает.
|
||||
|
||||
Это потому, что округление `Math.floor` происходит всегда к ближайшему меньшему целому, то есть `Math.floor(-1.2) = -2`, а нам бы хотелось убрать целую часть, т.е. получить `-1`.
|
||||
|
||||
Можно попытаться решить проблему так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function getDecimal(num) {
|
||||
return num > 0 ? num - Math.floor(num) : Math.ceil(num) - num;
|
||||
}
|
||||
|
||||
alert( getDecimal(12.5) ); // 0.5
|
||||
*!*
|
||||
alert( getDecimal(-1.2) ); // 0.19999999999999996, неверно!
|
||||
alert( getDecimal(1.2) ); // 0.19999999999999996
|
||||
*/!*
|
||||
```
|
||||
|
||||
Проблема с отрицательными числами решена, но результат, увы, не совсем тот.
|
||||
|
||||
Внутреннее неточное представление чисел приводит к ошибке в вычислениях, которая проявляется при работе и с положительными и с отрицательными числами.
|
||||
|
||||
Давайте попробуем ещё вариант -- получим остаток при делении на `1`. При таком делении от любого числа в остатке окажется именно дробная часть:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function getDecimal(num) {
|
||||
return num > 0 ? (num % 1) : (-num % 1);
|
||||
}
|
||||
|
||||
alert( getDecimal(12.5) ); // 0.5
|
||||
*!*
|
||||
alert( getDecimal(1.2) ); // 0.19999999999999996, неверно!
|
||||
*/!*
|
||||
```
|
||||
|
||||
В общем-то, работает, функция стала короче, но, увы, ошибка сохранилась.
|
||||
|
||||
Что делать?
|
||||
|
||||
Увы, операции с десятичными дробями подразумевают некоторую потерю точности.
|
||||
|
||||
Зависит от ситуации.
|
||||
<ul>
|
||||
<li>Если внешний вид числа неважен и ошибка в вычислениях допустима -- она ведь очень мала, то можно оставить как есть.</li>
|
||||
<li>Перейти на промежуточные целочисленные вычисления там, где это возможно.</li>
|
||||
<li>Если мы знаем, что десятичная часть жёстко ограничена, к примеру, может содержать не более 2 знаков то можно округлить число, то есть вернуть `+num.toFixed(2)`.</li>
|
||||
</ul>
|
||||
|
||||
Если эти варианты не подходят, то можно работать с числом как со строкой:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function getDecimal(num) {
|
||||
var str = "" + num;
|
||||
var zeroPos = str.indexOf(".");
|
||||
if (zeroPos == -1) return 0;
|
||||
str = str.slice( zeroPos );
|
||||
return +str;
|
||||
}
|
||||
|
||||
alert( getDecimal(12.5) ); // 0.5
|
||||
alert( getDecimal(1.2) ); // 0.2
|
||||
```
|
||||
|
12
1-js/4-data-structures/2-number/5-get-decimal/task.md
Normal file
12
1-js/4-data-structures/2-number/5-get-decimal/task.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Как получить дробную часть числа?
|
||||
|
||||
[importance 4]
|
||||
|
||||
Напишите функцию `getDecimal(num)`, которая возвращает десятичную часть числа:
|
||||
|
||||
```js
|
||||
alert( getDecimal(12.345) ); // 0.345
|
||||
alert( getDecimal(1.2) ); // 0.2
|
||||
alert( getDecimal(-1.2) ); // 0.2
|
||||
```
|
||||
|
33
1-js/4-data-structures/2-number/6-formula-binet/solution.md
Normal file
33
1-js/4-data-structures/2-number/6-formula-binet/solution.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function fibBinet(n) {
|
||||
var phi = (1 + Math.sqrt(5)) / 2;
|
||||
// используем Math.round для округления до ближайшего целого
|
||||
return Math.round( Math.pow(phi, n) / Math.sqrt(5) );
|
||||
}
|
||||
|
||||
function fib(n){
|
||||
var a=1, b=0, x;
|
||||
for(i=0; i<n; i++) {
|
||||
x = a+b;
|
||||
a = b
|
||||
b = x;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
alert( fibBinet(2) ); // 1, равно fib(2)
|
||||
alert( fibBinet(8) ); // 21, равно fib(8)
|
||||
*!*
|
||||
alert( fibBinet(77)); // 5527939700884755
|
||||
alert( fib(77) ); // 5527939700884757, не совпадает!
|
||||
*/!*
|
||||
```
|
||||
|
||||
**Результат вычисления <code>F<sub>77</sub></code> получился неверным!**
|
||||
|
||||
Причина -- в ошибках округления, ведь √5 -- бесконечная дробь.
|
||||
|
||||
Ошибки округления при вычислениях множатся и, в итоге, дают расхождение.
|
27
1-js/4-data-structures/2-number/6-formula-binet/task.md
Normal file
27
1-js/4-data-structures/2-number/6-formula-binet/task.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Формула Бине
|
||||
|
||||
[importance 4]
|
||||
|
||||
Последовательность [чисел Фибоначчи](http://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%B0_%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8) имеет формулу <code>F<sub>n</sub> = F<sub>n-1</sub> + F<sub>n-2</sub></code>. То есть, следующее число получается как сумма двух предыдущих.
|
||||
|
||||
Первые два числа равны `1`, затем `2(1+1)`, затем `3(1+2)`, `5(2+3)` и так далее: `1, 1, 2, 3, 5, 8, 13, 21...`.
|
||||
|
||||
Код для их вычисления (из задачи [](/task/fibonacci-numbers)):
|
||||
|
||||
```js
|
||||
function fib(n){
|
||||
var a=1, b=0, x;
|
||||
for(i=0; i<n; i++) {
|
||||
x = a+b;
|
||||
a = b
|
||||
b = x;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
```
|
||||
|
||||
Существует [формула Бине](http://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%B0_%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8#.D0.A4.D0.BE.D1.80.D0.BC.D1.83.D0.BB.D0.B0_.D0.91.D0.B8.D0.BD.D0.B5), согласно которой <code>F<sub>n</sub></code> равно ближайшему целому для <code>ϕ<sup>n</sup>/√5</code>, где <code>ϕ=(1+√5)/2</code> -- золотое сечение.
|
||||
|
||||
Напишите функцию `fibBinet(n)`, которая будет вычислять <code>F<sub>n</sub></code>, используя эту формулу. Проверьте её для значения <code>F<sub>77</sub></code> (должно получиться `fibBinet(77) = 5527939700884757`).
|
||||
|
||||
**Одинаковы ли результаты, полученные при помощи кода `fib(n)` выше и по формуле Бине? Если нет, то почему и какой из них верный?**
|
|
@ -0,0 +1,9 @@
|
|||
Сгенерируем значение в диапазоне `0..1` и умножим на `max`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var max = 10;
|
||||
|
||||
alert( Math.random()*max );
|
||||
```
|
||||
|
5
1-js/4-data-structures/2-number/7-random-0-max/task.md
Normal file
5
1-js/4-data-structures/2-number/7-random-0-max/task.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Случайное из интервала (0, max)
|
||||
|
||||
[importance 2]
|
||||
|
||||
Напишите код для генерации случайного значения в диапазоне от `0` до `max`, не включая `max`.
|
|
@ -0,0 +1,9 @@
|
|||
Сгенерируем значение из интервала `0..max-min`, а затем сдвинем на `min`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var min=5, max = 10;
|
||||
|
||||
alert( min + Math.random()*(max-min) );
|
||||
```
|
||||
|
5
1-js/4-data-structures/2-number/8-random-min-max/task.md
Normal file
5
1-js/4-data-structures/2-number/8-random-min-max/task.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Случайное из интервала (min, max)
|
||||
|
||||
[importance 2]
|
||||
|
||||
Напишите код для генерации случайного числа от `min` до `max`, не включая `max`.
|
|
@ -0,0 +1,78 @@
|
|||
# Очевидное неверное решение (round)
|
||||
|
||||
Самый простой, но неверный способ -- это сгенерировать значение в интервале `min..max` и округлить его `Math.round`, вот так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function randomInteger(min, max) {
|
||||
var rand = min + Math.random()*(max-min)
|
||||
rand = Math.round(rand);
|
||||
return rand;
|
||||
}
|
||||
|
||||
alert( randomInteger(1, 3) );
|
||||
```
|
||||
|
||||
Эта функция работает. Но при этом она некорректна: вероятность получить крайние значения `min` и `max` будет в два раза меньше, чем любые другие.
|
||||
|
||||
При многократном запуске этого кода вы легко заметите, что `2` выпадает чаще всех.
|
||||
|
||||
Это происходит из-за того, что `Math.round()` получает разнообразные случайные числа из интервала от `1` до `3`, но при округлении до ближайшего целого получится, что:
|
||||
|
||||
```js
|
||||
значения из диапазона 1 ... 1.49999.. станут 1
|
||||
значения из диапазона 1.5 ... 2.49999.. станут 2
|
||||
значения из диапазона 2.5 ... 2.99999.. станут 3
|
||||
```
|
||||
|
||||
Отсюда явно видно, что в `1` (как и `3`) попадает диапазон значений в два раза меньший, чем в `2`. Из-за этого такой перекос.
|
||||
|
||||
# Верное решение с round
|
||||
|
||||
Правильный способ: `Math.round(случайное от min-0.5 до max+0.5)`
|
||||
|
||||
```js
|
||||
//+ run
|
||||
*!*
|
||||
function randomInteger(min, max) {
|
||||
var rand = min - 0.5 + Math.random()*(max-min+1)
|
||||
rand = Math.round(rand);
|
||||
return rand;
|
||||
}
|
||||
*/!*
|
||||
|
||||
alert( randomInteger(5, 10) );
|
||||
```
|
||||
|
||||
В этом случае диапазон будет тот же (`max-min+1`), но учтена механика округления `round`.
|
||||
|
||||
# Решение с floor
|
||||
|
||||
Альтернативный путь - применить округление `Math.floor()` к случайному числу от `min` до `max+1`.
|
||||
|
||||
Например, для генерации целого числа от `1` до `3`, создадим вспомогательное случайное значение от `1` до `4` (не включая `4`).
|
||||
|
||||
Тогда `Math.floor()` округлит их так:
|
||||
|
||||
```js
|
||||
1 ... 1.999+ станет 1
|
||||
2 ... 2.999+ станет 2
|
||||
3 ... 3.999+ станет 3
|
||||
```
|
||||
|
||||
Все диапазоны одинаковы.
|
||||
Итак, код:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
*!*
|
||||
function randomInteger(min, max) {
|
||||
var rand = min + Math.random() * (max+1-min);
|
||||
rand = Math.floor(rand);
|
||||
return rand;
|
||||
}
|
||||
*/!*
|
||||
|
||||
alert( randomInteger(5, 10) );
|
||||
```
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Случайное целое от min до max
|
||||
|
||||
[importance 2]
|
||||
|
||||
Напишите функцию `randomInteger(min, max)` для генерации случайного **целого** числа между `min` и `max`, включая `min,max` как возможные значения.
|
||||
|
||||
Любое число из интервала `min..max` должно иметь одинаковую вероятность.
|
615
1-js/4-data-structures/2-number/article.md
Normal file
615
1-js/4-data-structures/2-number/article.md
Normal file
|
@ -0,0 +1,615 @@
|
|||
# Числа
|
||||
|
||||
Все числа в JavaScript, как целые так и дробные, имеют тип `Number` и хранятся в 64-битном формате [IEEE-754](http://en.wikipedia.org/wiki/IEEE_754-1985), также известном как "double precision".
|
||||
|
||||
Здесь мы рассмотрим различные тонкости, связанные с работой с числами в JavaScript.
|
||||
|
||||
## Способы записи
|
||||
|
||||
В JavaScript можно записывать числа не только в десятичной, но и в шестнадцатеричной (начинается с `0x`), а также восьмеричной (начинается с `0`) системах счисления:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( 0xFF ); // 255 в шестнадцатиричной системе
|
||||
alert( 010 ); // 8 в восьмеричной системе
|
||||
```
|
||||
|
||||
Также доступна запись в *"научном формате"* (ещё говорят "запись с плавающей точкой"), который выглядит как `<число>e<кол-во нулей>`.
|
||||
|
||||
Например, `1e3` -- это `1` с `3` нулями, то есть `1000`.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// еще пример научной формы: 3 с 5 нулями
|
||||
alert( 3e5 ); // 300000
|
||||
```
|
||||
|
||||
Если количество нулей отрицательно, то число сдвигается вправо за десятичную точку, так что получается десятичная дробь:
|
||||
|
||||
```js
|
||||
//+ 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"`.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert(1/0); // Infinity
|
||||
alert(12345/0); // Infinity
|
||||
```
|
||||
|
||||
**`Infinity` -- особенное численное значение, которое ведет себя в точности как математическая бесконечность `∞`.**
|
||||
<ul>
|
||||
<li>`Infinity` больше любого числа.</li>
|
||||
<li>Добавление к бесконечности не меняет её.</li>
|
||||
</ul>
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert(Infinity > 1234567890); // true
|
||||
alert(Infinity + 5 == Infinity); // true
|
||||
```
|
||||
|
||||
**Бесконечность можно присвоить и в явном виде: `var x = Infinity`.**
|
||||
|
||||
Бывает и минус бесконечность `-Infinity`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( -1 / 0 ); // -Infinity
|
||||
```
|
||||
|
||||
Бесконечность можно получить также, если сделать ну очень большое число, для которого количество разрядов в двоичном представлении не помещается в соответствующую часть стандартного 64-битного формата, например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( 1e500 ); // Infinity
|
||||
```
|
||||
|
||||
## NaN
|
||||
|
||||
Если математическая операция не может быть совершена, то возвращается специальное значение `NaN` (Not-A-Number).
|
||||
|
||||
Например, деление `0/0` в математическом смысле неопределено, поэтому возвращает `NaN`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( 0 / 0 ); // NaN
|
||||
```
|
||||
|
||||
Значение `NaN` используется для обозначения математической ошибки и обладает следующими свойствами:
|
||||
|
||||
<ul>
|
||||
<li>Значение `NaN` -- единственное, в своем роде, которое *не равно ничему, включая себя*.
|
||||
|
||||
Следующий код ничего не выведет:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
if (NaN == NaN) alert("=="); // Ни один вызов
|
||||
if (NaN === NaN) alert("==="); // не сработает
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>Значение `NaN` можно проверить специальной функцией `isNaN(n)`, которая возвращает `true` если аргумент -- `NaN` и `false` для любого другого значения.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var n = 0/0;
|
||||
|
||||
alert( isNaN(n) ); // true
|
||||
```
|
||||
|
||||
[smart]
|
||||
Отсюда вытекает забавный способ проверки значения на `NaN`: можно проверить его на равенство самому себе, вот так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var n = 0/0;
|
||||
|
||||
if (n !== n) alert('n = NaN!');
|
||||
```
|
||||
|
||||
Это работает, но для наглядности лучше использовать `isNaN(n)`.
|
||||
[/smart]
|
||||
</li>
|
||||
<li>Значение `NaN` "прилипчиво". Любая операция с `NaN` возвращает `NaN`.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( NaN + 1 ); // NaN
|
||||
```
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
Если аргумент `isNaN` -- не число, то он автоматически преобразуется к числу.
|
||||
|
||||
|
||||
[summary]Никакие математические операции в JavaScript не могут привести к ошибке или "обрушить" программу.
|
||||
|
||||
В худшем случае, результат будет `NaN`.
|
||||
[/summary]
|
||||
|
||||
## isFinite(n)
|
||||
|
||||
Итак, в JavaScript есть обычные числа и три специальных числовых значения: `NaN`, `Infinity` и `-Infinity`.
|
||||
|
||||
**Функция `isFinite(n)` возвращает `true` только тогда, когда `n` -- обычное число, а не одно из этих значений:**
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( isFinite(1) ); // true
|
||||
alert( isFinite(Infinity) ); // false
|
||||
alert( isFinite(NaN) ); // false
|
||||
```
|
||||
|
||||
Если аргумент `isFinite` -- не число, то он автоматически преобразуется к числу.
|
||||
|
||||
|
||||
## Преобразование к числу
|
||||
|
||||
Строгое преобразование можно осуществить унарным плюсом `'+'`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var s = "12.34";
|
||||
alert( +s ); // 12.34
|
||||
```
|
||||
|
||||
*Строгое* -- означает, что если строка не является в точности числом, то результат будет `NaN`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( +"12test" ); // NaN
|
||||
```
|
||||
|
||||
Единственное исключение -- пробельные символы в начале и в конце строки, которые игнорируются:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( +" -12"); // -12
|
||||
alert( +" \n34 \n"); // 34, перевод строки \n является пробельным символом
|
||||
alert( +"" ); // 0, пустая строка становится нулем
|
||||
alert( +"1 2" ); // NaN, пробел посередине числа - ошибка
|
||||
```
|
||||
|
||||
Аналогичным образом происходит преобразование и в других математических операторах и функциях:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( '12.34' / "-2" ); // -6.17
|
||||
```
|
||||
|
||||
### isNaN -- проверка на число для строк
|
||||
|
||||
Функция `isNaN` является математической, она преобразует аргумент в число, а затем проверяет, `NaN` это или нет.
|
||||
|
||||
Поэтому можно использовать ее для проверки:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var x = "-11.5";
|
||||
if (isNaN(x)) {
|
||||
alert("Строка преобразовалась в NaN. Не число");
|
||||
} else {
|
||||
alert("Число");
|
||||
}
|
||||
```
|
||||
|
||||
Единственный тонкий момент -- в том, что пустая строка и строка из пробельных символов преобразуются к `0`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert(isNaN(" \n\n ")) // false, т.к. строка из пробелов преобразуется к 0
|
||||
```
|
||||
|
||||
В случае, если применить такую проверку не к строке, то могут быть сюрпризы, в частности `isNaN` посчитает числами значения `false, true, null`, так как они хотя и не числа, но преобразуются к ним:
|
||||
|
||||
```js
|
||||
+false = 0 // isNaN(false) преобразует false в число, получится 0 - ок
|
||||
+true = 1 // тоже ок
|
||||
+null = 0 // тоже ок
|
||||
+undefined = NaN; // а вот это точно не число
|
||||
```
|
||||
|
||||
## Мягкое преобразование: parseInt и parseFloat
|
||||
|
||||
В мире HTML/CSS многие значения не являются в точности числами. Например, метрики CSS: `10pt` или `-12px`.
|
||||
|
||||
Оператор `'+'` для таких значений возвратит `NaN`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( +"12px" ) // NaN
|
||||
```
|
||||
|
||||
Для удобного чтения таких значений существует функция `parseInt`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( parseInt('12px') ); // 12
|
||||
```
|
||||
|
||||
**`parseInt` и ее аналог `parseFloat` преобразуют строку символ за символом, пока это возможно.**
|
||||
|
||||
При возникновении ошибки возвращается число, которое получилось. `parseInt` читает из строки целое число, а `parseFloat` -- дробное.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( parseInt('12px') ) // 12, ошибка на символе 'p'
|
||||
alert( parseFloat('12.3.4') ) // 12.3, ошибка на второй точке
|
||||
```
|
||||
|
||||
Конечно, существуют ситуации, когда `parseInt/parseFloat` возвращают `NaN`. Это происходит при ошибке на первом же символе:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( parseInt('a123') ); // NaN
|
||||
```
|
||||
|
||||
[warn header="Ошибка `parseInt('0..')`"]
|
||||
|
||||
`parseInt` (но не `parseFloat`) понимает 16-ричную систему счисления:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( parseInt('0xFF') ) // 255
|
||||
```
|
||||
|
||||
В старом стандарте JavaScript он умел понимать и восьмеричную:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( parseInt('010') ) // в некоторых браузерах 8
|
||||
```
|
||||
|
||||
Если вы хотите быть уверенным, что число, начинающееся с нуля, будет интерпретировано верно -- используйте второй необязательный аргумент `parseInt` -- основание системы счисления:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( parseInt('010', 10) ); // во всех браузерах 10
|
||||
```
|
||||
|
||||
[/warn]
|
||||
|
||||
## Проверка на число для всех типов
|
||||
|
||||
Если вам нужна действительно точная проверка на число, которая не считает числом строку из пробелов, логические и специальные значения -- используйте следующую функцию `isNumeric`:
|
||||
|
||||
```js
|
||||
function isNumeric(n) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n);
|
||||
}
|
||||
```
|
||||
|
||||
Разберёмся, как она работает. Начнём справа.
|
||||
|
||||
<ul>
|
||||
<li>Функция `isFinite(n)` преобразует аргумент к числу и возвращает `true`, если это не `Infinity/-Infinity/NaN`.
|
||||
|
||||
Таким образом, правая часть отсеет заведомо не-числа, но оставит такие значения как `true/false/null` и пустую строку `''`, т.к. они корректно преобразуются в числа.
|
||||
</li>
|
||||
<li>Для их проверки нужна левая часть. Вызов `parseFloat(true/false/null/'')` вернёт `NaN` для этих значений.
|
||||
|
||||
Так устроена функция `parseFloat`: она преобразует аргумент к строке, т.е. `true/false/null` становятся `"true"/"false"/"null"`, а затем считывает из неё число, при этом пустая строка даёт `NaN`.</li>
|
||||
</ul>
|
||||
|
||||
В результате отсеивается всё, кроме строк-чисел и обычных чисел.
|
||||
|
||||
## toString(система счисления)
|
||||
|
||||
Как показано выше, числа можно записывать не только в 10-чной, но и в 16-ричной системе. Но бывает и противоположная задача: получить 16-ричное представление числа. Для этого используется метод `toString(основание системы)`, например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var n = 255;
|
||||
|
||||
alert( n.toString(16) ); // ff
|
||||
```
|
||||
|
||||
Основание может быть любым от `2` до `36`.
|
||||
|
||||
<ul>
|
||||
<li>Основание `2` бывает полезно для отладки битовых операций, которые мы пройдём чуть позже:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var n = 4;
|
||||
alert( n.toString(2) ); // 100
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>Основание `36` (по количеству букв в английском алфавите -- 26, вместе с цифрами, которых 10) используется для того, чтобы "кодировать" число в виде буквенно-цифровой строки. В этой системе счисления сначала используются цифры, а затем буквы от `a` до `z`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var n = 1234567890;
|
||||
alert( n.toString(36) ); // kf12oi
|
||||
```
|
||||
|
||||
При помощи такого кодирования можно сделать длинный цифровой идентификатор короче, чтобы затем использовать его в URL.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
|
||||
## Округление
|
||||
|
||||
Одна из самых частых операций с числом -- округление. В JavaScript существуют целых 3 функции для этого.
|
||||
|
||||
<dl>
|
||||
<dt>`Math.floor`</dt>
|
||||
<dd>Округляет вниз</dd>
|
||||
<dt>`Math.ceil`</dt>
|
||||
<dd>Округляет вверх</dd>
|
||||
<dt>`Math.round`</dt>
|
||||
<dd>Округляет до ближайшего целого</dd>
|
||||
</dl>
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( Math.floor(3.1) ); // 3
|
||||
alert( Math.ceil(3.1) ); // 4
|
||||
alert( Math.round(3.1) ); // 3
|
||||
```
|
||||
|
||||
[smart header="Округление битовыми операторами"]
|
||||
[Битовые операторы](/bitwise-operators) делают любое число 32-битным целым, обрезая десятичную часть.
|
||||
|
||||
В результате побитовая операция, которая не изменяет число, например, двойное битовое НЕ -- округляет его:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( ~~12.3 ); // 12
|
||||
```
|
||||
|
||||
Любая побитовая операция такого рода подойдет, например XOR (исключающее ИЛИ, `"^"`) с нулем:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( 12.3 ^ 0 ); // 12
|
||||
alert( 1.2 + 1.3 ^ 0); // 2, приоритет ^ меньше, чем +
|
||||
```
|
||||
|
||||
Это удобно в первую очередь тем, что легко читается и не заставляет ставить дополнительные скобки как `Math.floor(...)`:
|
||||
|
||||
```js
|
||||
var x = a * b / c ^ 0; // читается так: "a*b/c *!*и округлить*/!*"
|
||||
```
|
||||
|
||||
[/smart]
|
||||
|
||||
### Округление до заданной точности
|
||||
|
||||
Обычный трюк -- это умножить и поделить на 10 с нужным количеством нулей. Например, округлим `3.456` до 2го знака после запятой:
|
||||
|
||||
```js
|
||||
//+ 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` и возвращает результат *в виде строки*:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var n = 12.34;
|
||||
alert( n.toFixed(1) ); // "12.3"
|
||||
```
|
||||
|
||||
Округление идёт до ближайшего значения, аналогично `Math.round`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var n = 12.36;
|
||||
alert( n.toFixed(1) ); // "12.4"
|
||||
```
|
||||
|
||||
Итоговая строка, при необходимости, дополняется нулями до нужной точности:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var n = 12.34;
|
||||
alert( n.toFixed(5) ); // "12.34000", добавлены нули до 5 знаков после запятой
|
||||
```
|
||||
|
||||
Если нам нужно именно число, то мы можем получить его, применив `'+'` к результату `n.toFixed(..)`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var n = 12.34;
|
||||
alert( +n.toFixed(5) ); // 12.34
|
||||
```
|
||||
|
||||
[warn header="Метод `toFixed` не эквивалентен `Math.round`!"]
|
||||
Например, произведём округление до одного знака после запятой с использованием двух способов:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var price = 6.35;
|
||||
|
||||
alert( price.toFixed(1) ); // 6.3
|
||||
alert( Math.round(price*10)/10 ); // 6.4
|
||||
```
|
||||
|
||||
Как видно, результат разный! Вариант округления через `Math.round` получился более корректным, так как по общепринятым правилам `5` округляется вверх. А `toFixed` может округлить его как вверх, так и вниз. Почему? Скоро узнаем!
|
||||
[/warn]
|
||||
|
||||
|
||||
## Неточные вычисления
|
||||
|
||||
Запустите этот пример:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert(0.1 + 0.2 == 0.3);
|
||||
```
|
||||
|
||||
Запустили? Если нет -- все же сделайте это.
|
||||
|
||||
Ок, вы запустили его. Результат несколько странный, не так ли? Возможно, ошибка в браузере? Поменяйте браузер, запустите еще раз.
|
||||
|
||||
Хорошо, теперь мы можем быть уверены: `0.1 + 0.2` это не `0.3`. Но тогда что же это?
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert(0.1 + 0.2); // 0.30000000000000004
|
||||
```
|
||||
|
||||
Как видите, произошла небольшая вычислительная ошибка.
|
||||
|
||||
Дело в том, что в стандарте IEEE 754 на число выделяется ровно 8 байт(=64 бита), не больше и не меньше.
|
||||
|
||||
Число `0.1 (=1/10)` короткое в десятичном формате, а в двоичной системе счисления это бесконечная дробь ([перевод десятичной дроби в двоичную систему](http://www.klgtu.ru/students/literature/inf_asu/1760.html)). Также бесконечной дробью является `0.2 (=2/10)`.
|
||||
|
||||
Двоичное значение бесконечных дробей хранится только до определенного знака, поэтому возникает неточность. Это даже можно увидеть:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
|
||||
```
|
||||
|
||||
Когда мы складываем `0.1` и `0.2`, то две неточности складываются, получаем третью.
|
||||
|
||||
Конечно, это не означает, что точные вычисления для таких чисел невозможны. Они возможны. И даже необходимы.
|
||||
|
||||
Например, есть два способа сложить `0.1` и `0.2`:
|
||||
<ol>
|
||||
<li>Сделать их целыми, сложить, а потом поделить:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( (0.1*10 + 0.2*10) / 10 ); // 0.3
|
||||
```
|
||||
|
||||
Это работает, т.к. числа `0.1*10 = 1` и `0.2*10 = 2` могут быть точно представлены в двоичной системе.
|
||||
</li>
|
||||
<li>Сложить, а затем округлить до разумного знака после запятой. Округления до 10-го знака обычно бывает достаточно, чтобы отсечь ошибку вычислений:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var result = 0.1 + 0.2;
|
||||
alert( +result.toFixed(10) ); // 0.3
|
||||
```
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
|
||||
|
||||
[smart header="Забавный пример"]
|
||||
Привет! Я -- число, растущее само по себе!
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert(9999999999999999);
|
||||
```
|
||||
|
||||
Причина та же -- потеря точности.
|
||||
|
||||
Из `64` бит, отведённых на число, сами цифры числа занимают до `52` бит, остальные `11` бит хранят позицию десятичной точки и один бит -- знак. Так что если `52` бит не хватает на цифры, то при записи пропадут младшие разряды.
|
||||
|
||||
Интерпретатор не выдаст ошибку, но в результате получится "не совсем то число", что мы и видим в примере выше. Как говорится: "как смог, так записал".
|
||||
|
||||
[/smart]
|
||||
|
||||
Ради справедливости заметим, что в точности то же самое происходит в любом другом языке, где используется формат IEEE 754, включая Java, C, PHP, Ruby, Perl.
|
||||
|
||||
## Другие математические методы
|
||||
|
||||
JavaScript предоставляет базовые тригонометрические и некоторые другие функции для работы с числами.
|
||||
|
||||
### Тригонометрия
|
||||
|
||||
Встроенные функции для тригонометрических вычислений:
|
||||
|
||||
<dl>
|
||||
<dt>`Math.acos(x)`</dt>
|
||||
<dd>Возвращает арккосинус `x` (в радианах)</dd>
|
||||
<dt>`Math.asin(x)`</dt>
|
||||
<dd>Возвращает арксинус `x` (в радианах)</dd>
|
||||
<dt>`Math.atan`</dt>
|
||||
<dd>Возвращает арктангенс `x` (в радианах)</dd>
|
||||
<dt>`Math.atan2(y, x)`</dt>
|
||||
<dd>Возвращает угол до точки `(y, x)`. Описание функции: [Atan2](http://en.wikipedia.org/wiki/Atan2).</dd>
|
||||
<dt>`Math.sin(x)`</dt>
|
||||
<dd>Вычисляет синус `x` (в радианах)</dd>
|
||||
<dt>`Math.cos(x)`</dt>
|
||||
<dd>Вычисляет косинус `x` (в радианах)</dd>
|
||||
<dt>`Math.tan(x)`</dt>
|
||||
<dd>Возвращает тангенс `x` (в радианах)</dd>
|
||||
</dl>
|
||||
|
||||
### Функции общего назначения
|
||||
|
||||
Разные полезные функции:
|
||||
<dl>
|
||||
<dt>`Math.sqrt(x)`</dt>
|
||||
<dd>Возвращает квадратный корень из `x`.</dd>
|
||||
<dt>`Math.log(x)`</dt>
|
||||
<dd>Возвращает натуральный (по основанию <code>e</code>) логарифм `x`.</dd>
|
||||
<dt>`Math.pow(x, exp)`</dt>
|
||||
<dd>Возводит число в степень, возвращает <code>x<sup>exp</sup></code>, например `Math.pow(2,3) = 8`. Работает в том числе с дробными и отрицательными степенями, например: `Math.pow(4, -1/2) = 0.5`.</dd>
|
||||
<dt>`Math.abs(x)`</dt>
|
||||
<dd>Возвращает абсолютное значение числа</dd>
|
||||
<dt>`Math.exp(x)`</dt>
|
||||
<dd>Возвращает <code>e<sup>x</sup></code>, где <code>e</code> -- основание натуральных логарифмов.</dd>
|
||||
<dt>`Math.max(a, b, c...)`</dt>
|
||||
<dd>Возвращает наибольший из списка аргументов</dd>
|
||||
<dt>`Math.min(a, b, c...)`</dt>
|
||||
<dd>Возвращает наименьший из списка аргументов</dd>
|
||||
<dt>`Math.random()`</dt>
|
||||
<dd>Возвращает псевдо-случайное число в интервале [0,1) - то есть между 0(включительно) и 1(не включая). Генератор случайных чисел инициализуется текущим временем.</dd>
|
||||
</dl>
|
||||
|
||||
### Форматирование
|
||||
|
||||
Для красивого вывода чисел в стандарте [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf) есть метод `toLocaleString()`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var number = 123456789;
|
||||
|
||||
alert( number.toLocaleString() ); // 123 456 789
|
||||
```
|
||||
|
||||
Его поддерживают все современные браузеры, кроме IE10- (для которых нужно подключить библиотеку [Intl.JS](https://github.com/andyearnshaw/Intl.js/)). Он также умеет форматировать валюту и проценты. Более подробно про устройство этого метода можно будет узнать в статье [](/intl), когда это вам понадобится.
|
||||
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>Числа могут быть записаны в шестнадцатиричной, восьмеричной системе, а также "научным" способом.</li>
|
||||
<li>В JavaScript существует числовое значение бесконечность `Infinity`.</li>
|
||||
<li>Ошибка вычислений дает `NaN`.</li>
|
||||
<li>Арифметические и математические функции преобразуют строку в точности в число, игнорируя начальные и конечные пробелы.</li>
|
||||
<li>Функции `parseInt/parseFloat` делают числа из строк, которые начинаются с числа.</li>
|
||||
<li>Есть четыре способа округления: `Math.floor`, `Math.round`, `Math.ceil` и битовый оператор. Для округления до нужного знака используйте `+n.toFixed(p)` или трюк с умножением и делением на <code>10<sup>p</sup></code>.</li>
|
||||
<li>Дробные числа дают ошибку вычислений. При необходимости ее можно отсечь округлением до нужного знака.</li>
|
||||
<li>Случайные числа от `0` до `1` генерируются с помощью `Math.random()`, остальные -- преобразованием из них.</li>
|
||||
</ul>
|
||||
|
||||
Существуют и другие математические функции. Вы можете ознакомиться с ними в справочнике в разделах <a href="http://javascript.ru/Number">Number</a> и <a href="http://javascript.ru/Math">Math</a>.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue