Перейти к содержимому
Теория · Переменные и типы · урок 5 из 9

Числа

Представьте калькулятор, на экране которого помещается только девять цифр после запятой. Разделите на нём 1 на 3: он покажет 0,333333333, а остальные тройки отбросит. Сложите три такие трети на бумаге, и получится 0,999999999, а не единица. Ни одна цифра не посчитана неправильно: на экране закончилось место.

Компьютер тоже хранит число в ограниченном месте. Поэтому сложение двух дробных чисел в JavaScript может дать не тот результат, который получается на бумаге:

JavaScript
console.log(0.1 + 0.2);  // 0.30000000000000004
console.log(36.6 * 1.8 + 32);  // 97.88000000000001
console.log(2 + 3);  // 5

Давайте разберём вывод:

  1. 0.1 + 0.2 даёт не 0.3, а число с лишней 4 в семнадцатом знаке после точки.
  2. 36.6 * 1.8 + 32 тоже получает лишнюю 1 в конце.
  3. 2 + 3 даёт ровно 5.

С целыми числами такого не происходит, а с дробными лишняя цифра в конце появляется регулярно. Причина в том, как устроен тип number.

Один тип для всех чисел

Целые и дробные числа в JavaScript — значения одного типа number. Внутри каждое такое число хранится в двоичном виде, и на него отведено ограниченное число знаков. Двоичный вид — запись числа только цифрами 0 и 1: так компьютер хранит любые данные.

Целые числа хранятся точно, пока по величине не превышают 9 007 199 254 740 991, поэтому 2 + 3 даёт ровно 5.

Погрешность округления

Дробь 0.1 в двоичном виде бесконечна, как 1/3 в десятичном, и её приходится обрезать. Получается погрешность округления: число очень близко к 0.1, но не равно ему. При сложении погрешности накапливаются и становятся видны в выводе, как в первой строке программы.

Здесь аналогия с калькулятором перестаёт работать. Калькулятор считает в десятичном виде, и 0.1 помещается на его экран точно. Компьютер считает в двоичном виде, поэтому обрезает даже 0.1.

Из-за погрешности дробные результаты сравнивают не на точное равенство, а с допуском. Два числа считаются равными, если разница между ними меньше, например, 0.000000001. Разницу при этом берут без знака: Math.abs(x) возвращает число x без минуса, и Math.abs(2 - 5) даёт 3. Так проверяют ответы и в задачах курса.

Суммы денег часто хранят в копейках, то есть целыми числами. Тогда погрешности не возникает вовсе.

Этот вопрос часто задают на собеседовании: «Почему 0.1 + 0.2 не равно 0.3?» Ответ состоит из двух частей:

  • число хранится в двоичном виде с ограниченным числом знаков, и 0.1 в нём записывается только приближённо;
  • поэтому дробные результаты сравнивают с допуском, а деньги считают в целых копейках.

Infinity и NaN

Не каждое вычисление даёт обычное число. Для таких случаев у типа number есть особые значения. Оператор / делит одно число на другое, а Math.sqrt(x) вычисляет квадратный корень:

JavaScript
console.log(1 / 0);  // Infinity
console.log(-1 / 0);  // -Infinity
console.log(0 / 0);  // NaN
console.log(Math.sqrt(-1));  // NaN

Разберём, что здесь происходит:

  • деление ненулевого числа на ноль не останавливает программу: результат — бесконечность, Infinity или -Infinity;
  • у вычислений 0 / 0 и Math.sqrt(-1) числового ответа нет, и JavaScript возвращает NaN.

Вы уже встречали NaN: это значение «не число», которое тоже относится к типу number. Ни одно из этих вычислений не вызывает ошибку: программа продолжает работать с полученным Infinity или NaN. Если сложить NaN с числом, умножить или разделить, результат тоже будет NaN.

Частая ошибка

Дробную часть отделили запятой, как принято в русском и многих других языках:

JavaScript
const price = 3,5;  // ✗
console.log(price);
TEXT
SyntaxError: Unexpected number

В JavaScript запятая разделяет значения, а дробную часть отделяет точка. Программа не запускается вовсе: SyntaxError обнаруживается до первой строки. Исправленная строка — const price = 3.5;.

Главное

  • Целые и дробные числа — один тип number; целые до 9 007 199 254 740 991 хранятся точно.
  • Дробные числа хранятся приближённо, поэтому 0.1 + 0.2 даёт 0.30000000000000004, а сравнивают их с допуском.
  • Деление ненулевого числа на ноль даёт Infinity или -Infinity, а 0 / 0 и Math.sqrt(-1) — NaN, и программа не останавливается.
  • Дробную часть в коде отделяют точкой, а не запятой.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом