Сбой PayPal на $92 квадриллиона и предел 64 бит

В июне 2013 года Крис Рейнольдс из Пенсильвании открыл месячную выписку PayPal и увидел баланс 92 233 720 368 547 800 долларов. Он работал в PR, а через PayPal продавал детали к винтажным автомобилям, и до того дня на счёте у него никогда не лежало больше тысячи. На пару минут он формально стал самым богатым человеком в истории: сумма примерно в миллион раз превышала состояние тогдашнего лидера списка Forbes и в тысячу раз мировой ВВП.

Самая странная деталь выписки: число стояло со знаком минус. То есть по бумагам Рейнольдс нёс самый крупный долг в истории человечества. Он зашёл в аккаунт проверить, и там было ровно 0.00.

Сумма кажется случайной ровно до того момента, как вспомнишь максимум знакового 64-битного целого: 9 223 372 036 854 775 807. Платёжные системы обычно держат деньги в центах целым числом, чтобы не копить ошибки округления плавающей точки. Возьмите этот максимум как центы, поделите на сто и получите 92 233 720 368 547 758,07. Первые цифры и порядок совпадают с выпиской с точностью до центов.

Значит, баланс уехал не куда попало. Он упёрся в математический потолок разрядности, и это отпечаток конкретного класса дефектов: переполнение целочисленного типа. Когда знаковый int64 пересекает верхнюю границу, значение перескакивает в самое начало диапазона, и плюс превращается в минус. Отсюда и квадриллионный долг в письме от PayPal.

Такие ошибки редко заканчиваются забавной выпиской. В 1996 году ракета Ariane 5 развалилась через 40 секунд после старта: инерциальная система переводила 64-битное число с плавающей точкой в 16-битное целое, горизонтальная скорость у нового носителя оказалась выше расчётной, значение не влезло в тип. Обработчик остановил вычисления и выдал диагностические байты в канал, который система управления считала полётными данными. Программа стоимостью порядка 8 миллиардов долларов потеряла первый же пуск из-за числа, которому не хватило битов.

В 2012 году трейдинговая компания Knight Capital за 45 минут слила около 440 миллионов долларов. Виноват оказался забытый кусок старого кода, который восемь лет лежал в репозитории без дела. При выкатке новой логики его случайно активировали на одном из восьми серверов, и роутер начал отправлять на биржу ордера бесконечным потоком. За полчаса компания стоимостью почти миллиард перестала существовать самостоятельно.

Практический вывод для всех, кто сегодня считает деньги в коде, скучный и полезный. Центы в int64 закрывают потребности почти любого бизнеса, но границы стоит проверять там, где числа перемножаются: проценты, конвертация валют, накопительные счётчики, агрегаты за всё время. Тесты на граничных значениях (максимум типа, максимум минус единица, переход через нуль) обходятся дешевле, чем разбор инцидента на проде.

С языками ситуация разная, и знать поведение своего стека полезно до инцидента, а не после. В C и C++ знаковое переполнение остаётся undefined behavior, и компилятор имеет право выбросить вашу проверку как мёртвый код. В Java тихий перескок лечится Math.addExact и Math.multiplyExact. В Rust релизная сборка по умолчанию заворачивает значение молча, так что нужны checked_add и overflow-checks. В Python целые произвольной точности снимают вопрос в самом языке, зато он возвращается на границе с BIGINT в базе и в JSON, где на стороне JavaScript целое теряет точность после 2^53.

С Рейнольдсом всё закончилось мирно: PayPal извинился и предложил перевести некоторую сумму на любую благотворительность на его выбор. На вопрос журналистов, что бы он сделал с деньгами, Крис ответил, что закрыл бы госдолг США, а потом купил себе бейсбольную команду, «если сошлись бы в цене». Две минуты он стоил больше, чем все экономики планеты вместе, и не мог купить на эти деньги даже чашку кофе.

Историю напомнил Sweep: https://x.com/0xSweep/status/2085024544201576719

Поделиться:

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *