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

BigInt

Бэкенд на Java вернул ID транзакции: { id: 9007199254740999 }. JavaScript молча сохранил его как 9007199254740992 — другое число! Это не баг сервера и не баг бэкенда: это ограничение типа Number. Для работы с такими числами существует BigInt.

На основе предыдущих уроков

  • «Типы данных» — семь примитивных типов; bigint — восьмой
  • «Числа» — IEEE 754, Number.MAX_SAFE_INTEGER, потеря точности
  • Проблема: Number.MAX_SAFE_INTEGER и потеря точности

    Тип Number может точно представлять целые числа только до определённого предела:

    console.log(Number.MAX_SAFE_INTEGER)        // 9007199254740991 (2^53 - 1)
    console.log(Number.MAX_SAFE_INTEGER + 1)    // 9007199254740992 — ещё точно
    console.log(Number.MAX_SAFE_INTEGER + 2)    // 9007199254740992 — ОШИБКА!
    console.log(Number.MAX_SAFE_INTEGER + 3)    // 9007199254740994

    Это критично для работы с ID из базы данных: если сервер возвращает { id: 9007199254740993 }, JavaScript сохранит его как 9007199254740992.

    Синтаксис BigInt

    Создать BigInt можно двумя способами:

    // 1. Суффикс n
    const bigId = 9007199254740993n
    const balance = 100_000_000n  // разделители для читаемости
    
    // 2. Функция BigInt()
    const fromNumber = BigInt(42)                         // 42n
    const fromString = BigInt('12345678901234567890')     // очень большое число
    
    console.log(typeof bigId)  // 'bigint'
    console.log(bigId)         // 9007199254740993n

    Арифметика с BigInt

    const a = 1000000000000000000n
    const b = 2000000000000000000n
    
    console.log(a + b)    // 3000000000000000000n
    console.log(b - a)    // 1000000000000000000n
    console.log(a * 3n)   // 3000000000000000000n
    console.log(b / a)    // 2n  (деление всегда целочисленное!)
    console.log(b % 3n)   // 2n
    console.log(2n ** 64n) // 18446744073709551616n (2^64)

    Важно: деление BigInt всегда целочисленное — дробная часть отбрасывается:

    console.log(7n / 2n)   // 3n, а не 3.5n!
    console.log(10n / 3n)  // 3n

    Нельзя смешивать BigInt и Number

    const big = 100n
    const num = 5
    
    // big + num  // TypeError: Cannot mix BigInt and other types!
    
    // Нужно явное преобразование:
    console.log(big + BigInt(num))   // 105n
    console.log(Number(big) + num)   // 105 (но рискуем потерять точность!)

    Сравнение BigInt и Number

    // Нестрогое сравнение (==) — работает:
    console.log(100n == 100)   // true
    console.log(100n == '100') // true
    
    // Строгое сравнение (===) — всегда false:
    console.log(100n === 100)  // false (разные типы!)
    console.log(typeof 100n)   // 'bigint'
    console.log(typeof 100)    // 'number'
    
    // Операторы сравнения работают корректно:
    console.log(100n > 50)     // true
    console.log(50n < 100)     // true

    Реальные примеры: финансы в копейках

    // Финансовые расчёты в копейках (избегаем ошибок float)
    const price = 1999n         // 19.99 руб в копейках
    const quantity = 1000n
    const discount = 15n        // 15%
    
    const subtotal = price * quantity                  // 1999000n
    const discountAmount = subtotal * discount / 100n  // 299850n
    const total = subtotal - discountAmount            // 1699150n
    
    // Форматирование для вывода:
    function formatKopecks(kopecks) {
      const rub = kopecks / 100n
      const kop = kopecks % 100n
      return `${rub}.${String(kop).padStart(2, '0')} ₽`
    }
    
    console.log(formatKopecks(total))  // '16991.50 ₽'

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

    1. Смешивание BigInt и Number в арифметике:

    // Плохо: TypeError
    const id = 9007199254740993n
    const offset = 10
    console.log(id + offset)  // TypeError: Cannot mix BigInt and other types
    
    // Хорошо: явное преобразование
    console.log(id + BigInt(offset))  // 9007199254741003n

    2. Использование Math-функций с BigInt:

    // Плохо: Math не работает с BigInt
    Math.max(1n, 2n)   // TypeError
    Math.abs(-5n)      // TypeError
    
    // Хорошо: ручное сравнение
    function maxBigInt(a, b) { return a > b ? a : b }
    console.log(maxBigInt(1n, 2n))  // 2n

    3. JSON.stringify не сериализует BigInt:

    // Плохо:
    JSON.stringify({ id: 9007199254740993n })
    // TypeError: Do not know how to serialize a BigInt
    
    // Хорошо: конвертируй в строку для сериализации
    JSON.stringify({ id: String(9007199254740993n) })
    // '{"id":"9007199254740993"}'

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

  • ID из БД — PostgreSQL bigint может быть больше MAX_SAFE_INTEGER; ID Твиттера уже требует BigInt
  • Криптография — большие простые числа, RSA-ключи
  • Финансовые расчёты — суммы в копейках/центах без ошибок плавающей точки
  • Временные метки — наносекундные timestamps в системах с высокой точностью
  • Примеры

    Потеря точности с большими числами vs BigInt, вычисление факториала

    // 1. Демонстрация потери точности
    console.log('--- Проблема Number ---')
    const maxSafe = Number.MAX_SAFE_INTEGER
    console.log(maxSafe)         // 9007199254740991
    console.log(maxSafe + 1)     // 9007199254740992
    console.log(maxSafe + 2)     // 9007199254740992  (должно быть 9007199254740993!)
    console.log(maxSafe + 3)     // 9007199254740994
    console.log(maxSafe + 4)     // 9007199254740996  (должно быть 9007199254740995!)
    
    // То же самое с BigInt — без потери точности
    console.log('\n--- BigInt точен ---')
    const bigMax = BigInt(maxSafe)
    console.log(bigMax)          // 9007199254740991n
    console.log(bigMax + 1n)     // 9007199254740992n
    console.log(bigMax + 2n)     // 9007199254740993n  (точно!)
    console.log(bigMax + 3n)     // 9007199254740994n
    console.log(bigMax + 4n)     // 9007199254740995n  (точно!)
    
    // 2. Факториал — число быстро выходит за MAX_SAFE_INTEGER
    console.log('\n--- Факториал с Number ---')
    function factorialNumber(n) {
      if (n <= 1) return 1
      return n * factorialNumber(n - 1)
    }
    
    console.log(factorialNumber(20))  // 2432902008176640000  — выглядит правильно
    console.log(factorialNumber(21))  // 51090942171709440000 — уже неточно!
    
    // 3. Факториал с BigInt — точный результат
    console.log('\n--- Факториал с BigInt ---')
    function factorialBigInt(n) {
      if (n <= 1n) return 1n
      return n * factorialBigInt(n - 1n)
    }
    
    console.log(factorialBigInt(20n))
    // 2432902008176640000n — точно!
    
    console.log(factorialBigInt(50n))
    // 30414093201713378043612608166979581188299763898377856000000000000n
    
    // 4. Финансы в копейках
    console.log('\n--- Финансы ---')
    const price = 1999n      // 19.99 руб
    const qty = 500n
    const discount = 10n     // 10%
    
    const subtotal = price * qty
    const discounted = subtotal - subtotal * discount / 100n
    console.log(`Итого: ${discounted / 100n}.${discounted % 100n} ₽`)
    // Итого: 8996.50 ₽  (нет ошибок плавающей точки!)
    
    // 5. Сравнение BigInt и Number
    console.log('\n--- Сравнение ---')
    console.log(100n == 100)    // true  (нестрогое)
    console.log(100n === 100)   // false (строгое — разные типы!)
    console.log(100n > 50)      // true
    console.log(typeof 42n)     // 'bigint'

    BigInt

    Бэкенд на Java вернул ID транзакции: { id: 9007199254740999 }. JavaScript молча сохранил его как 9007199254740992 — другое число! Это не баг сервера и не баг бэкенда: это ограничение типа Number. Для работы с такими числами существует BigInt.

    На основе предыдущих уроков

  • «Типы данных» — семь примитивных типов; bigint — восьмой
  • «Числа» — IEEE 754, Number.MAX_SAFE_INTEGER, потеря точности
  • Проблема: Number.MAX_SAFE_INTEGER и потеря точности

    Тип Number может точно представлять целые числа только до определённого предела:

    console.log(Number.MAX_SAFE_INTEGER)        // 9007199254740991 (2^53 - 1)
    console.log(Number.MAX_SAFE_INTEGER + 1)    // 9007199254740992 — ещё точно
    console.log(Number.MAX_SAFE_INTEGER + 2)    // 9007199254740992 — ОШИБКА!
    console.log(Number.MAX_SAFE_INTEGER + 3)    // 9007199254740994

    Это критично для работы с ID из базы данных: если сервер возвращает { id: 9007199254740993 }, JavaScript сохранит его как 9007199254740992.

    Синтаксис BigInt

    Создать BigInt можно двумя способами:

    // 1. Суффикс n
    const bigId = 9007199254740993n
    const balance = 100_000_000n  // разделители для читаемости
    
    // 2. Функция BigInt()
    const fromNumber = BigInt(42)                         // 42n
    const fromString = BigInt('12345678901234567890')     // очень большое число
    
    console.log(typeof bigId)  // 'bigint'
    console.log(bigId)         // 9007199254740993n

    Арифметика с BigInt

    const a = 1000000000000000000n
    const b = 2000000000000000000n
    
    console.log(a + b)    // 3000000000000000000n
    console.log(b - a)    // 1000000000000000000n
    console.log(a * 3n)   // 3000000000000000000n
    console.log(b / a)    // 2n  (деление всегда целочисленное!)
    console.log(b % 3n)   // 2n
    console.log(2n ** 64n) // 18446744073709551616n (2^64)

    Важно: деление BigInt всегда целочисленное — дробная часть отбрасывается:

    console.log(7n / 2n)   // 3n, а не 3.5n!
    console.log(10n / 3n)  // 3n

    Нельзя смешивать BigInt и Number

    const big = 100n
    const num = 5
    
    // big + num  // TypeError: Cannot mix BigInt and other types!
    
    // Нужно явное преобразование:
    console.log(big + BigInt(num))   // 105n
    console.log(Number(big) + num)   // 105 (но рискуем потерять точность!)

    Сравнение BigInt и Number

    // Нестрогое сравнение (==) — работает:
    console.log(100n == 100)   // true
    console.log(100n == '100') // true
    
    // Строгое сравнение (===) — всегда false:
    console.log(100n === 100)  // false (разные типы!)
    console.log(typeof 100n)   // 'bigint'
    console.log(typeof 100)    // 'number'
    
    // Операторы сравнения работают корректно:
    console.log(100n > 50)     // true
    console.log(50n < 100)     // true

    Реальные примеры: финансы в копейках

    // Финансовые расчёты в копейках (избегаем ошибок float)
    const price = 1999n         // 19.99 руб в копейках
    const quantity = 1000n
    const discount = 15n        // 15%
    
    const subtotal = price * quantity                  // 1999000n
    const discountAmount = subtotal * discount / 100n  // 299850n
    const total = subtotal - discountAmount            // 1699150n
    
    // Форматирование для вывода:
    function formatKopecks(kopecks) {
      const rub = kopecks / 100n
      const kop = kopecks % 100n
      return `${rub}.${String(kop).padStart(2, '0')} ₽`
    }
    
    console.log(formatKopecks(total))  // '16991.50 ₽'

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

    1. Смешивание BigInt и Number в арифметике:

    // Плохо: TypeError
    const id = 9007199254740993n
    const offset = 10
    console.log(id + offset)  // TypeError: Cannot mix BigInt and other types
    
    // Хорошо: явное преобразование
    console.log(id + BigInt(offset))  // 9007199254741003n

    2. Использование Math-функций с BigInt:

    // Плохо: Math не работает с BigInt
    Math.max(1n, 2n)   // TypeError
    Math.abs(-5n)      // TypeError
    
    // Хорошо: ручное сравнение
    function maxBigInt(a, b) { return a > b ? a : b }
    console.log(maxBigInt(1n, 2n))  // 2n

    3. JSON.stringify не сериализует BigInt:

    // Плохо:
    JSON.stringify({ id: 9007199254740993n })
    // TypeError: Do not know how to serialize a BigInt
    
    // Хорошо: конвертируй в строку для сериализации
    JSON.stringify({ id: String(9007199254740993n) })
    // '{"id":"9007199254740993"}'

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

  • ID из БД — PostgreSQL bigint может быть больше MAX_SAFE_INTEGER; ID Твиттера уже требует BigInt
  • Криптография — большие простые числа, RSA-ключи
  • Финансовые расчёты — суммы в копейках/центах без ошибок плавающей точки
  • Временные метки — наносекундные timestamps в системах с высокой точностью
  • Примеры

    Потеря точности с большими числами vs BigInt, вычисление факториала

    // 1. Демонстрация потери точности
    console.log('--- Проблема Number ---')
    const maxSafe = Number.MAX_SAFE_INTEGER
    console.log(maxSafe)         // 9007199254740991
    console.log(maxSafe + 1)     // 9007199254740992
    console.log(maxSafe + 2)     // 9007199254740992  (должно быть 9007199254740993!)
    console.log(maxSafe + 3)     // 9007199254740994
    console.log(maxSafe + 4)     // 9007199254740996  (должно быть 9007199254740995!)
    
    // То же самое с BigInt — без потери точности
    console.log('\n--- BigInt точен ---')
    const bigMax = BigInt(maxSafe)
    console.log(bigMax)          // 9007199254740991n
    console.log(bigMax + 1n)     // 9007199254740992n
    console.log(bigMax + 2n)     // 9007199254740993n  (точно!)
    console.log(bigMax + 3n)     // 9007199254740994n
    console.log(bigMax + 4n)     // 9007199254740995n  (точно!)
    
    // 2. Факториал — число быстро выходит за MAX_SAFE_INTEGER
    console.log('\n--- Факториал с Number ---')
    function factorialNumber(n) {
      if (n <= 1) return 1
      return n * factorialNumber(n - 1)
    }
    
    console.log(factorialNumber(20))  // 2432902008176640000  — выглядит правильно
    console.log(factorialNumber(21))  // 51090942171709440000 — уже неточно!
    
    // 3. Факториал с BigInt — точный результат
    console.log('\n--- Факториал с BigInt ---')
    function factorialBigInt(n) {
      if (n <= 1n) return 1n
      return n * factorialBigInt(n - 1n)
    }
    
    console.log(factorialBigInt(20n))
    // 2432902008176640000n — точно!
    
    console.log(factorialBigInt(50n))
    // 30414093201713378043612608166979581188299763898377856000000000000n
    
    // 4. Финансы в копейках
    console.log('\n--- Финансы ---')
    const price = 1999n      // 19.99 руб
    const qty = 500n
    const discount = 10n     // 10%
    
    const subtotal = price * qty
    const discounted = subtotal - subtotal * discount / 100n
    console.log(`Итого: ${discounted / 100n}.${discounted % 100n} ₽`)
    // Итого: 8996.50 ₽  (нет ошибок плавающей точки!)
    
    // 5. Сравнение BigInt и Number
    console.log('\n--- Сравнение ---')
    console.log(100n == 100)    // true  (нестрогое)
    console.log(100n === 100)   // false (строгое — разные типы!)
    console.log(100n > 50)      // true
    console.log(typeof 42n)     // 'bigint'

    Задание

    В криптографическом модуле нужна точная математика. Напиши функцию factorial(n), которая принимает BigInt и возвращает точный факториал. Функция должна корректно вычислять 20! = 2432902008176640000n.

    Подсказка

    Базовый случай: if (n <= 1n) return 1n — сравниваем BigInt с BigInt. Рекурсия: return n * factorial(n - 1n) — все операции с BigInt. Убедись, что аргумент функции тоже BigInt: factorial(20n), а не factorial(20).

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