JSON Toolkit

Почему большие целые теряют точность

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

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

Главное: относитесь к snowflake 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.

Какие поля страдают

Поле Цифры Риск
Snowflake 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.

Дополнительное чтение