← JavaScript/Числа#72 из 383← ПредыдущийСледующий →+20 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: JS базаПрактика: async и сетьТермин: Closure

Числа

Реальная проблема: деньги и округление

В интернет-магазине цена товара — 29.99 ₽. Пользователь берёт три штуки. Вы считаете: 29.99 * 3 = 89.97. Но JavaScript выдаёт 89.97000000000001. Отображаете в чеке — и клиент видит некрасивое число. Это не баг — это особенность хранения чисел в памяти компьютера (IEEE 754). Знание числовых методов позволяет работать с этим правильно.

Что решают числовые методы

  • Форматирование для отображения (цены, проценты, координаты)
  • Математические операции (округление, случайные числа)
  • Проверка валидности числовых данных
  • На основе предыдущих уроков

  • «Типы данных» — number как тип, NaN, Infinity
  • «Преобразование типов» — Number(), parseInt(), parseFloat()
  • «Сравнения» — NaN !== NaN (особенность)
  • Проблема точности: IEEE 754

    JavaScript использует 64-битные числа с плавающей точкой:

    0.1 + 0.2           // 0.30000000000000004 — не 0.3!
    0.1 + 0.2 === 0.3   // false
    
    // Решения:
    // 1. toFixed — для отображения (возвращает строку!)
    (0.1 + 0.2).toFixed(2)   // '0.30'
    
    // 2. Храни деньги в целых (копейки, центы)
    const priceKopecks = 2999  // 29.99 ₽
    const totalKopecks = priceKopecks * 3  // 8997 — точно!
    const display = (totalKopecks / 100).toFixed(2)  // '89.97'
    
    // 3. Math.round с масштабированием
    Math.round(0.1 + 0.2, 10) // не работает так
    Math.round((0.1 + 0.2) * 10) / 10  // 0.3

    Объект Math

    Math.round(4.5)     // 5   — математическое округление
    Math.floor(4.9)     // 4   — вниз (всегда меньше)
    Math.ceil(4.1)      // 5   — вверх (всегда больше)
    Math.trunc(4.9)     // 4   — отбросить дробную часть (не округлять)
    Math.trunc(-4.9)    // -4  — в отличие от floor(-4.9) = -5
    
    Math.abs(-7)        // 7   — модуль
    Math.max(1, 5, 3)   // 5
    Math.min(1, 5, 3)   // 1
    Math.sqrt(9)        // 3
    Math.pow(2, 10)     // 1024
    Math.log(Math.E)    // 1
    Math.PI             // 3.141592653589793
    
    // Случайное число
    Math.random()       // [0, 1) — от 0 включительно до 1 не включительно

    Методы числа

    const n = 1234567.891
    
    // Форматирование в строку
    n.toFixed(2)          // '1234567.89' — фиксированная точность (строка!)
    n.toPrecision(6)      // '1234570' — общее кол-во значимых цифр
    
    // Конвертация системы счисления
    (255).toString(16)    // 'ff' — шестнадцатеричное
    (255).toString(2)     // '11111111' — двоичное
    
    // Форматирование для отображения (учитывает локаль)
    n.toLocaleString('ru-RU')  // '1 234 567,891' — рубли
    n.toLocaleString('en-US')  // '1,234,567.891' — доллары

    Специальные значения

    Infinity          // бесконечность (1/0)
    -Infinity         // отрицательная бесконечность
    NaN               // Not a Number — результат некорректной операции
    
    // Проверки
    isNaN(NaN)                // true — но: isNaN('hello') тоже true!
    Number.isNaN(NaN)         // true — строгая проверка
    Number.isNaN('hello')     // false — это строка, не NaN
    
    isFinite(Infinity)        // false
    Number.isFinite(1000)     // true
    
    Number.isInteger(4.0)     // true
    Number.isInteger(4.5)     // false
    
    Number.MAX_SAFE_INTEGER   // 9007199254740991 (2^53 - 1)
    Number.MIN_SAFE_INTEGER   // -9007199254740991

    Типичные ошибки

    Ошибка 1: toFixed возвращает строку

    // Сломано:
    const price = (1499.9).toFixed(2)
    console.log(price + 0.1)  // '1499.900.1' — конкатенация!
    
    // Исправлено — конвертируй в число:
    const price = +(1499.9).toFixed(2)  // унарный +
    // или
    const price = parseFloat((1499.9).toFixed(2))

    Ошибка 2: Math.random() для безопасности

    // Math.random() НЕ криптографически безопасен
    // Для генерации токенов, паролей используй:
    crypto.getRandomValues(new Uint32Array(1))[0]  // в браузере

    Ошибка 3: NaN в вычислениях молча всё ломает

    const price = Number('неверный ввод')  // NaN
    const total = price * 3               // NaN — тихо распространяется!
    console.log(total > 0)                // false — но не ошибка
    
    // Защита:
    if (!Number.isFinite(price)) throw new Error('Некорректная цена')

    В реальных проектах

  • Цены: хранить в копейках/центах (целые числа), отображать через toFixed(2)
  • Рейтинги: Math.round(rating * 10) / 10 — округление до 1 знака
  • Прогресс: Math.min(100, Math.floor(done / total * 100)) — процент прогресса
  • Пагинация: Math.ceil(total / perPage) — количество страниц
  • Координаты: lat.toFixed(6) — 6 знаков для GPS (точность ~11 см)
  • Примеры

    Финансовые вычисления и форматирование для магазина

    // Точные финансовые вычисления (цены в копейках)
    const cartItems = [
      { name: 'Ноутбук',  priceKopecks: 7500000, qty: 1 },
      { name: 'Мышь',     priceKopecks:  150000, qty: 2 },
      { name: 'Коврик',   priceKopecks:   49900, qty: 3 },
    ]
    
    const subtotalKopecks = cartItems.reduce((sum, item) => {
      return sum + item.priceKopecks * item.qty
    }, 0)
    // 7500000 + 300000 + 149700 = 7949700 — точно, нет проблем с float
    
    // Скидка 15%
    const discountKopecks = Math.round(subtotalKopecks * 0.15)
    const totalKopecks    = subtotalKopecks - discountKopecks
    
    // Форматирование для отображения
    function formatPrice(kopecks, locale = 'ru-RU') {
      return (kopecks / 100).toLocaleString(locale, {
        style: 'currency',
        currency: 'RUB',
        minimumFractionDigits: 2,
      })
    }
    
    console.log(formatPrice(subtotalKopecks))  // '79 497,00 ₽'
    console.log(formatPrice(discountKopecks))  // '11 924,55 ₽'
    console.log(formatPrice(totalKopecks))     // '67 572,45 ₽'
    
    // Случайная цена для акции (от 100 до 500 рублей)
    function randomPrice(minRub, maxRub) {
      const minK = minRub * 100
      const maxK = maxRub * 100
      return Math.floor(Math.random() * (maxK - minK + 1)) + minK
    }
    console.log(formatPrice(randomPrice(100, 500)))  // случайная цена
    
    // Прогресс накопления бонусов
    const bonusGoal    = 100000  // копеек
    const bonusCurrent = 67572
    const progressPct  = Math.min(100, Math.floor(bonusCurrent / bonusGoal * 100))
    console.log(`Прогресс: ${progressPct}%`)  // 'Прогресс: 67%'
    
    // Количество страниц пагинации
    const totalProducts = 247
    const perPage       = 20
    const pages         = Math.ceil(totalProducts / perPage)
    console.log(`Страниц: ${pages}`)  // 'Страниц: 13'

    Числа

    Реальная проблема: деньги и округление

    В интернет-магазине цена товара — 29.99 ₽. Пользователь берёт три штуки. Вы считаете: 29.99 * 3 = 89.97. Но JavaScript выдаёт 89.97000000000001. Отображаете в чеке — и клиент видит некрасивое число. Это не баг — это особенность хранения чисел в памяти компьютера (IEEE 754). Знание числовых методов позволяет работать с этим правильно.

    Что решают числовые методы

  • Форматирование для отображения (цены, проценты, координаты)
  • Математические операции (округление, случайные числа)
  • Проверка валидности числовых данных
  • На основе предыдущих уроков

  • «Типы данных» — number как тип, NaN, Infinity
  • «Преобразование типов» — Number(), parseInt(), parseFloat()
  • «Сравнения» — NaN !== NaN (особенность)
  • Проблема точности: IEEE 754

    JavaScript использует 64-битные числа с плавающей точкой:

    0.1 + 0.2           // 0.30000000000000004 — не 0.3!
    0.1 + 0.2 === 0.3   // false
    
    // Решения:
    // 1. toFixed — для отображения (возвращает строку!)
    (0.1 + 0.2).toFixed(2)   // '0.30'
    
    // 2. Храни деньги в целых (копейки, центы)
    const priceKopecks = 2999  // 29.99 ₽
    const totalKopecks = priceKopecks * 3  // 8997 — точно!
    const display = (totalKopecks / 100).toFixed(2)  // '89.97'
    
    // 3. Math.round с масштабированием
    Math.round(0.1 + 0.2, 10) // не работает так
    Math.round((0.1 + 0.2) * 10) / 10  // 0.3

    Объект Math

    Math.round(4.5)     // 5   — математическое округление
    Math.floor(4.9)     // 4   — вниз (всегда меньше)
    Math.ceil(4.1)      // 5   — вверх (всегда больше)
    Math.trunc(4.9)     // 4   — отбросить дробную часть (не округлять)
    Math.trunc(-4.9)    // -4  — в отличие от floor(-4.9) = -5
    
    Math.abs(-7)        // 7   — модуль
    Math.max(1, 5, 3)   // 5
    Math.min(1, 5, 3)   // 1
    Math.sqrt(9)        // 3
    Math.pow(2, 10)     // 1024
    Math.log(Math.E)    // 1
    Math.PI             // 3.141592653589793
    
    // Случайное число
    Math.random()       // [0, 1) — от 0 включительно до 1 не включительно

    Методы числа

    const n = 1234567.891
    
    // Форматирование в строку
    n.toFixed(2)          // '1234567.89' — фиксированная точность (строка!)
    n.toPrecision(6)      // '1234570' — общее кол-во значимых цифр
    
    // Конвертация системы счисления
    (255).toString(16)    // 'ff' — шестнадцатеричное
    (255).toString(2)     // '11111111' — двоичное
    
    // Форматирование для отображения (учитывает локаль)
    n.toLocaleString('ru-RU')  // '1 234 567,891' — рубли
    n.toLocaleString('en-US')  // '1,234,567.891' — доллары

    Специальные значения

    Infinity          // бесконечность (1/0)
    -Infinity         // отрицательная бесконечность
    NaN               // Not a Number — результат некорректной операции
    
    // Проверки
    isNaN(NaN)                // true — но: isNaN('hello') тоже true!
    Number.isNaN(NaN)         // true — строгая проверка
    Number.isNaN('hello')     // false — это строка, не NaN
    
    isFinite(Infinity)        // false
    Number.isFinite(1000)     // true
    
    Number.isInteger(4.0)     // true
    Number.isInteger(4.5)     // false
    
    Number.MAX_SAFE_INTEGER   // 9007199254740991 (2^53 - 1)
    Number.MIN_SAFE_INTEGER   // -9007199254740991

    Типичные ошибки

    Ошибка 1: toFixed возвращает строку

    // Сломано:
    const price = (1499.9).toFixed(2)
    console.log(price + 0.1)  // '1499.900.1' — конкатенация!
    
    // Исправлено — конвертируй в число:
    const price = +(1499.9).toFixed(2)  // унарный +
    // или
    const price = parseFloat((1499.9).toFixed(2))

    Ошибка 2: Math.random() для безопасности

    // Math.random() НЕ криптографически безопасен
    // Для генерации токенов, паролей используй:
    crypto.getRandomValues(new Uint32Array(1))[0]  // в браузере

    Ошибка 3: NaN в вычислениях молча всё ломает

    const price = Number('неверный ввод')  // NaN
    const total = price * 3               // NaN — тихо распространяется!
    console.log(total > 0)                // false — но не ошибка
    
    // Защита:
    if (!Number.isFinite(price)) throw new Error('Некорректная цена')

    В реальных проектах

  • Цены: хранить в копейках/центах (целые числа), отображать через toFixed(2)
  • Рейтинги: Math.round(rating * 10) / 10 — округление до 1 знака
  • Прогресс: Math.min(100, Math.floor(done / total * 100)) — процент прогресса
  • Пагинация: Math.ceil(total / perPage) — количество страниц
  • Координаты: lat.toFixed(6) — 6 знаков для GPS (точность ~11 см)
  • Примеры

    Финансовые вычисления и форматирование для магазина

    // Точные финансовые вычисления (цены в копейках)
    const cartItems = [
      { name: 'Ноутбук',  priceKopecks: 7500000, qty: 1 },
      { name: 'Мышь',     priceKopecks:  150000, qty: 2 },
      { name: 'Коврик',   priceKopecks:   49900, qty: 3 },
    ]
    
    const subtotalKopecks = cartItems.reduce((sum, item) => {
      return sum + item.priceKopecks * item.qty
    }, 0)
    // 7500000 + 300000 + 149700 = 7949700 — точно, нет проблем с float
    
    // Скидка 15%
    const discountKopecks = Math.round(subtotalKopecks * 0.15)
    const totalKopecks    = subtotalKopecks - discountKopecks
    
    // Форматирование для отображения
    function formatPrice(kopecks, locale = 'ru-RU') {
      return (kopecks / 100).toLocaleString(locale, {
        style: 'currency',
        currency: 'RUB',
        minimumFractionDigits: 2,
      })
    }
    
    console.log(formatPrice(subtotalKopecks))  // '79 497,00 ₽'
    console.log(formatPrice(discountKopecks))  // '11 924,55 ₽'
    console.log(formatPrice(totalKopecks))     // '67 572,45 ₽'
    
    // Случайная цена для акции (от 100 до 500 рублей)
    function randomPrice(minRub, maxRub) {
      const minK = minRub * 100
      const maxK = maxRub * 100
      return Math.floor(Math.random() * (maxK - minK + 1)) + minK
    }
    console.log(formatPrice(randomPrice(100, 500)))  // случайная цена
    
    // Прогресс накопления бонусов
    const bonusGoal    = 100000  // копеек
    const bonusCurrent = 67572
    const progressPct  = Math.min(100, Math.floor(bonusCurrent / bonusGoal * 100))
    console.log(`Прогресс: ${progressPct}%`)  // 'Прогресс: 67%'
    
    // Количество страниц пагинации
    const totalProducts = 247
    const perPage       = 20
    const pages         = Math.ceil(totalProducts / perPage)
    console.log(`Страниц: ${pages}`)  // 'Страниц: 13'

    Задание

    Напиши функцию calcShipping(weightGrams, distanceKm), которая считает стоимость доставки. Правила: базовая ставка 50 ₽, за каждый кг (или его часть) сверх первого — плюс 30 ₽, за каждые 100 км (или их часть) — плюс 20 ₽. Вернуть итоговую стоимость, округлённую до целого рубля. Также напиши formatShipping(price), которая форматирует цену как строку '150 ₽'.

    Подсказка

    extraKg = Math.max(0, Math.ceil(weightKg) - 1). weightFee = extraKg * 30. distFee = Math.ceil(distanceKm / 100) * 20. Итог = 50 + weightFee + distFee.

    Загружаем среду выполнения...
    Загружаем AI-помощника...