Числа
Представьте калькулятор, на экране которого помещается только девять цифр после запятой. Разделите на нём 1 на 3: он покажет 0,333333333, а остальные тройки отбросит. Сложите три такие трети на бумаге, и получится 0,999999999, а не единица. Ни одна цифра не посчитана неправильно: на экране закончилось место.
Компьютер тоже хранит число в ограниченном месте. Поэтому сложение двух дробных чисел в JavaScript может дать не тот результат, который получается на бумаге:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(36.6 * 1.8 + 32); // 97.88000000000001
console.log(2 + 3); // 5Давайте разберём вывод:
0.1 + 0.2даёт не0.3, а число с лишней4в семнадцатом знаке после точки.36.6 * 1.8 + 32тоже получает лишнюю1в конце.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) вычисляет квадратный корень:
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.
Частая ошибка
Дробную часть отделили запятой, как принято в русском и многих других языках:
const price = 3,5; // ✗
console.log(price);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 и подпишись на канал — откроются все уроки.