Числа и Math в JavaScript: округление и точность

Содержание

О проверке кода. Примеры не запускались — в отличие от материалов по Node.js с фактическим выводом. Говорим прямо, а не ставим бейдж «проверено».

Числа в JavaScript — источник знаменитых мемов вроде 0.1 + 0.2. За «странностями» стоит понятная логика. Разберём, как считать правильно.

Знаменитое 0.1 + 0.2

0.1 + 0.2;              // 0.30000000000000004  ❌
0.1 + 0.2 === 0.3;      // false

Это не баг JavaScript. Все числа хранятся в двоичном формате с плавающей точкой (стандарт IEEE 754, один тип number для всего). В двоичной системе 0.1 и 0.2 не представимы точно — как 1/3 не записать конечной десятичной дробью. Отсюда микроскопическая погрешность. То же самое в Python, Java, C — везде, где float.

Правильное сравнение дробей — через допуск:

Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON;   // true — с учётом погрешности

Округление

Math.round(4.5);    // 5  — к ближайшему целому
Math.round(4.4);    // 4
Math.floor(4.9);    // 4  — всегда вниз
Math.ceil(4.1);     // 5  — всегда вверх
Math.trunc(4.9);    // 4  — отбросить дробную часть
Math.trunc(-4.9);   // -4 — (floor дал бы -5!)

(3.14159).toFixed(2);  // '3.14' — ⚠️ СТРОКА, не число!
Округление: какой метод когда

Math-методы → число

Возвращают number

  • round — к ближайшему
  • floor — вниз (к -∞)
  • ceil — вверх (к +∞)
  • trunc — отбросить дробь (к нулю)

toFixed → строка

Осторожно с типом

  • (1.5).toFixed(2)'1.50'
  • Результат — СТРОКА
  • Для вывода/форматирования — ок
  • Для расчётов сначала Number(...)

Главная ловушка toFixed — он возвращает строку. (3.14159).toFixed(2) даёт '3.14', а не 3.14. Для отображения это нормально, но если результат идёт в дальнейшие расчёты — оберните в Number(). Разница floor и trunc видна на отрицательных: floor(-4.9) = -5, trunc(-4.9) = -4.

Парсинг строк в числа

Number('42');       // 42
Number('42px');     // NaN   — строгий, весь текст должен быть числом
Number('');         // 0     — пустая строка → 0 (сюрприз)
Number('  42  ');   // 42    — пробелы обрезает

parseInt('42px');   // 42    — терпимый, читает с начала
parseInt('3.9');    // 3     — только целая часть
parseFloat('3.9kg'); // 3.9  — с дробной частью

Number строгий — при любом мусоре в строке даёт NaN. parseInt/parseFloat терпимые — читают число с начала и останавливаются на первом нечисловом символе. Для пользовательского ввода вроде '42px' берут parseInt; для строгой валидации — Number с проверкой на NaN.

NaN и проверки

NaN === NaN;            // false ❌ — NaN не равен ДАЖЕ САМОМУ СЕБЕ
Number.isNaN(NaN);      // true  ✅ — надёжная проверка
Number.isNaN('текст');  // false — не приводит типы (в отличие от старого isNaN)

Number.isFinite(42);       // true
Number.isFinite(Infinity); // false
Number.isInteger(3.0);     // true

NaN — единственное значение, не равное самому себе, поэтому x === NaN бесполезно. Проверяют через Number.isNaN(x). Берите именно Number.isNaN, а не старый глобальный isNaN — тот приводит типы и даёт ложные срабатывания (isNaN('текст')true). Number.isFinite отсекает NaN и Infinity разом.

Деньги: считайте в копейках

// ❌ дробные рубли — накопление ошибок
let total = 0;
total += 19.99;
total += 19.99;   // может получиться 39.980000000000004

// ✅ копейки целыми числами
let cents = 0;
cents += 1999;
cents += 1999;    // 3998 — точно
const rubles = (cents / 100).toFixed(2);   // '39.98' только при выводе

Универсальное правило финансовых расчётов: храните суммы в наименьших единицах целыми числами — копейках, центах. Целочисленная арифметика точна, погрешность плавающей точки исключена. Деление на 100 — только в момент отображения. Так делают в банковских системах и платёжных API.

Форматирование и большие числа

(1234567.89).toLocaleString('ru-RU');   // '1 234 567,89' — с разделителями

Number.MAX_SAFE_INTEGER;    // 9007199254740991 — предел точных целых
9007199254740991 + 2;       // 9007199254740992 — ❌ потеря точности!

9007199254740991n + 2n;     // 9007199254740993n — ✅ BigInt для огромных целых

toLocaleString форматирует число под локаль (разделители разрядов, валюта). Для целых чисел больше MAX_SAFE_INTEGER (≈9 квадриллионов) обычный number теряет точность — тогда используют BigInt (суффикс n), например для ID из базы или криптографии.


Смежные темы: типы данных — number среди прочих; операторы — арифметика и сравнение; строки — форматирование вывода. Полный список — в уроках JavaScript.

Частые вопросы

Почему 0.1 + 0.2 не равно 0.3?
Числа в JavaScript хранятся в двоичном формате с плавающей точкой, где 0.1 и 0.2 не представимы точно. Результат получается 0.30000000000000004. Это не баг JavaScript, а свойство стандарта IEEE 754 во всех языках.
Как правильно округлить число?
Math.round округляет до целого, Math.floor вниз, Math.ceil вверх. Для фиксированного числа знаков после запятой — toFixed(2), но он возвращает строку, а не число. Для денег считают в копейках целыми числами.
Чем parseInt отличается от Number?
Number преобразует всю строку и даёт NaN при мусоре: Number(‘12px’) это NaN. parseInt читает число с начала и останавливается: parseInt(‘12px’) это 12. Number строже, parseInt терпимее к хвосту.
Как проверить, что значение — не число?
Number.isNaN(x) — надёжная проверка на NaN. Обычный x === NaN не работает, потому что NaN не равен даже самому себе. Number.isFinite(x) проверяет, что это конечное число, а не NaN или Infinity.
Как считать деньги без ошибок округления?
Хранить сумму в наименьших единицах целыми числами — копейках или центах, а не рублях с дробью. 19.99 рубля становится 1999 копеек. Целочисленная арифметика точна, деление на 100 только при выводе.