JSON Toolkit

Чому великі цілі числа втрачають точність

Головне: Цілі числа понад 2^53-1 втрачають точність у JSON.parse. ID-сніжинки, номери замовлень і bigint із бази даних усі на це натрапляють, тож тримайте їх рядками від початку до кінця.

Середній~4 хв читанняОпубліковано 2026-10-30Оновлено 2026-11-04
Чому великі цілі числа втрачають точність у JSON і як їх зберегти

Коротко: трактуйте ID-сніжинки, номери замовлень і первинні ключі bigint як рядки під час серіалізації, передачі й розбору; тримайте числовий тип, лише коли доведено, що поле залишається в межах 2^53-1, і тоді забезпечуйте це обмеженням maximum у Schema.

Бо пошкодження відбувається під час розбору, мовчки.

JSON.parse('{"id": 9007199254740993}');
// { id: 9007199254740992 }

Пропущена цифра зникла; відновити нічого.

Звідки береться 2^53

У JavaScript один числовий тип: подвійна точність IEEE 754 з 53 бітами значущості (один неявний, 52 явні). Точні цілі тому закінчуються на ±(2^53-1):

-9007199254740991 .. 9007199254740991

Вище цієї межі сусідні подавані значення різняться на 2, 4, 8. 9007199254740993 непарне, а на такій величині кожне подаване значення парне, тож воно округлюється до 9007199254740992.

Які поля страждають

Поле Цифри Ризик
ID-сніжинки 18–19 Високий, останні цифри змінюються
Номери замовлень / транзакцій 16–20 Високий
Первинні ключі bigint бази даних 19 Високий
Гроші в дрібних одиницях — Низький
Часові мітки в мілісекундах 13 Безпечно

Спільна нитка: це ідентифікатори, а не кількості. Ідентифікатор передають, його ніколи не додають і не множать, тож рядок — правильний тип.

Чотири виправлення

Серіалізуйте як рядок. Бекенд конвертує поля bigint перед записом відповіді. Просто й довговічно; документуйте, що ці поля — рядки.

Дозвольте серіалізатору це зробити. ToStringSerializer для Long у Jackson, тег json:",string" у Go. Та сама ідея: тримайте значення поза числовим шляхом.

Розбирайте з підтримкою BigInt. json-bigint і подібні бібліотеки. Зауважте, що BigInt не можна змішувати з арифметикою Number, а JSON.stringify його відхиляє.

Обмежте діапазон. Якщо поле справді не може перевищувати 2^53, додайте maximum у Schema, щоб позадіапазонні дані провалювали перевірку, а не зазнавали збою мовчки в браузері.

Як визначити, чи у вас цей баг

Візьміть відомий ID і проженіть його туди-назад: серіалізуйте, передайте, розберіть, порівняйте символ за символом. Або огляньте сире тіло відповіді — 19-значне число без лапок, що приходить із неправильними останніми цифрами, майже напевно є втратою під час розбору.

Часті питання

Чому JSON.parse змінює останні цифри без помилки?

У JavaScript один числовий тип — подвійна точність IEEE 754, тож цілі числа понад 2^53-1 можуть потрапити лише на найближче подаване значення. JSON.parse трактує це як звичайне числове перетворення: він ні не кидає виняток, ні не попереджає.

Як фронтенд може зберегти великі цілі цілими?

Використовуйте бібліотеку на кшталт json-bigint, бо JSON.parse цього не може. Ціна — BigInt не можна змішувати з арифметикою Number, а JSON.stringify його відхиляє, тож оточуючий код теж має змінитися.

Чи має документація API заявляти, що ці поля — рядки?

Так. Серіалізація в рядки — найпростіше й найдовговічніше виправлення, але воно працює, лише якщо споживачі трактують значення як текст, тож тип поля треба записати в контракт API.

Додаткове читання