Методика и точность
Как устроены расчёты Amortiva и почему им можно верить до копейки
На главной странице мы обещаем: «Расчёты — до копейки». Это не фигура речи, а техническое свойство движка, которое можно проверить. Ниже — откуда в привычных калькуляторах берутся расхождения на копейки, что здесь сделано иначе и чем это доказано.
Коротко: суммы хранятся целыми числами, промежуточный расчёт идёт в точных дробях без потерь, округление выполняется один раз — в самом конце, а браузер и сервер считают по одним и тем же зафиксированным правилам, за которыми следят автоматические проверки.
Проверки в цифрах
- 7 436вызовов арифметики зафиксировано и сверяется с сервером
- 5 003операций с деньгами зафиксировано и сверяется с сервером
- 12тестов точности покрывают все расчётные движки
Эти цифры не набраны руками: страница читает их при сборке из тех самых файлов-фикстур и тестов, по которым проверяются движки. Изменилась фикстура — изменится и страница.
Почему калькуляторы теряют копейки
Большинство калькуляторов считает в числах с плавающей запятой. У таких чисел ошибка относительная: на небольших суммах она незаметна, но чем больше сумма, тем крупнее «цена деления» — на очень больших остатках она превышает копейку, и точнее число записать просто нельзя. А привычные 0,1 и 0,01 в двоичном виде вообще бесконечны, поэтому даже простое сложение цен накапливает пыль в младших разрядах.
Второй путь — считать в копейках или с фиксированным числом знаков после запятой. Он честнее, но ломается на ставках: месячная доля годовой ставки — это деление на 1200, а у дроби 1/1200 нет конечной десятичной записи. Сколько знаков ни оставь, каждый месяц расчёт отбрасывает хвост — и на длинном сроке копейки утекают.
Самый короткий пример: 1 рубль под 1% годовых на полгода. Истинный итог — ровно 1,005 рубля: это «ничья» округления, которая по общему правилу идёт вверх, к 1,01. Любой расчёт с фиксированной точностью приходит чуть ниже — 1,00499… — и отдаёт 1,00, на копейку меньше. Числа с плавающей запятой ошибаются на похожих примерах с другой стороны.
Как считает Amortiva
Сумма «в покое» — сохранённая, показанная на экране или отправленная на сервер — хранится как целое число единиц валюты плюс целое число миллиардных долей единицы, без двоичных дробей. Арифметика над такими суммами идёт в целых числах произвольной длины: десять миллионов рублей в миллиардных долях уже не помещаются в обычное число, поэтому обычные числа тут не участвуют вовсе.
Пока расчёт идёт, сумма живёт в виде точной дроби «числитель / знаменатель». Сложение, вычитание, умножение и деление точных дробей точны по определению: ничего не отбрасывается и не накапливается, сколько бы шагов ни было в графике.
Округление выполняется один раз, в самом конце, от истинного значения — до числа знаков конкретной валюты: два у рубля, ноль у иены, три у динара. Правило одно на весь продукт: половина округляется вверх. Двойных округлений — сначала до промежуточной точности, потом до копеек — нет, а именно они дают знакомое «где-то потерялась копейка».
Ставка никогда не делится заранее. Месячное начисление — это остаток * ставка / 1200, вычисленное как одна точная дробь, а не заранее округлённая «месячная ставка», в которой ошибка сидела бы ещё до умножения на остаток.
Почему браузер и сервер дают один результат
Расчёт выполняется дважды: в браузере — мгновенно, пока вы вводите цифры, и на сервере — когда отчёт сохраняется в аккаунт. Это разные языки программирования, и «почти одинаковая» арифметика в них разошлась бы на копейку уже на первом округлении.
Поэтому равенство не предполагается, а зафиксировано. Из браузерного движка записаны тысячи реальных вызовов арифметики — аргументы и точный ответ каждого. Эти записи лежат в обоих репозиториях, и серверная реализация обязана воспроизвести каждый ответ в точности; контроль — отпечаток SHA-256 файла, прошитый в проверки обеих сторон. Изменение одной копии без другой роняет сборку ещё до выхода изменения.
Арифметика ставок и долей — 7 436 вызовов, SHA-256: be56be53bfd0a6a6ec5e7fb66a9097ae96d427aad12b84a8cd7f63a0a32212c8
Денежный тип и точные дроби — 5 003 операций, SHA-256: 2f2c42246a0fa42c4550a6bc1997d9e7311037496c803f6b5b58a284a64cda36
Проверено на каждом движке
У каждого расчётного движка есть тест точности: он независимо повторяет ту же проекцию в точных дробях и требует совпадения до последней копейки. Сравниваются целые копейки, а не приближённые числа — на суммах больше 2^53 копеек приближённое сравнение молча пропустило бы ровно те случаи, ради которых проверка существует.
Эта проверка — не формальность. Когда движки переводились на точную арифметику, старую «плавающую» версию прогнали против точного эталона на 3 024 сочетаниях входных данных: она ошиблась в 22 из них, в худшем случае — на 2,93 рубля. Текущий движок на тех же данных не ошибается ни разу.
Если график не совпал с банковским
Точность арифметики не означает, что цифры совпадут с выпиской любого банка: банк считает по своему договору, и в нём могут быть другие правила. Обычные источники расхождений:
- База года. Мы начисляем проценты по фактическим дням и фактической длине года — 365 или 366. Часть договоров считает год за 360 или всегда за 365 дней; на високосном году это уже даёт разницу.
- Перенос платежа. Если дата платежа выпала на выходной, банк списывает деньги в ближайший рабочий день — и за добавочные дни набегают проценты, которых в «календарном» графике нет.
- Порядок списания. При частичном платеже банк распределяет деньги по правилам договора — комиссии, неустойка, проценты, тело долга — и его внутренние округления на каждом шаге могут отличаться от наших.
- Дата расчёта. График строится на дату, которую вы указали; выписка банка — на дату фактической операции. День разницы — это день процентов.
Если расхождение больше нескольких копеек — это повод открыть договор и уточнить у банка, по каким правилам он считает. Наш график считается ровно по введённым условиям, а формулу каждой цифры можно проверить вручную по подсказкам «как считается» рядом с результатами.
Результаты носят справочный характер и не являются финансовой рекомендацией.