Тема 71 из 91· Разработка ПО и цифровая доставка

Валютно-безопасное агрегирование затрат (Currency-Safe Cost Rollup)

Суммирование множества денежных статей — ежемесячных счетов, затрат по площадкам, многолетних данных о влиянии на бюджет — обычными двоичными числами с плавающей запятой (f64) накапливает мелкие ошибки представления, потому что большинство десятичных дробей (например, $1 234,56) нельзя точно представить в двоичной плавающей запятой. Каждая ошибка мала, но большая модель, суммирующая сотни или тысячи статей за годы, может отклониться на долю цента, причём отклонение зависит от порядка сложения и потому невоспроизводимо. Валютное агрегирование, выполненное точной десятичной арифметикой (или целыми числами наименьшей единицы), складывается точно, согласуясь с тем, как бухгалтерские системы и двойная запись обязаны сходиться до цента.

Почему это важно

Это хорошо задокументированный и фундаментальный тип программных ошибок: статья Голдберга 1991 года в ACM Computing Surveys "What Every Computer Scientist Should Know About Floating-Point Arithmetic" — стандартный источник о том, почему двоичная плавающая запятая не может точно представить большинство десятичных денежных сумм и почему сложение многих из них накапливает ошибку. Модели экономики здравоохранения и финансов NHS регулярно суммируют несколько лет и несколько категорий затрат: и совокупная стоимость владения, и анализ влияния на бюджет суммируют много статей затрат f64 за годы. Когда модель должна сходиться до цента — аудит, считающий итог вручную, должен прийти к точно такому же числу, — сама арифметика должна быть точной десятичной, а не с плавающей запятой.

Математика

Наивное агрегирование:            итог = Σ f64(статья_i)       — дрейф, зависящий от порядка
Валютно-безопасное агрегирование: итог = Σ Decimal(статья_i)   — точное, воспроизводимое

Применение процентной корректировки (например, резерва):
  скорректировано = итог × множитель   — точный результат Decimal, может иметь
                                         больше десятичных знаков, чем показатель
                                         наименьшей единицы валюты
  округлено = округлить(скорректировано, показатель_валюты, правило_округления)
                                       — правило округления (half-up против
                                         half-even/банковского округления)
                                         нужно указывать явно

Обратите внимание на двухшаговую дисциплину: умножение точного Decimal на множитель может дать больше десятичных знаков, чем реально использует валюта (например, три знака из суммы с двумя знаками, умноженной на множитель с двумя знаками); эта промежуточная точность не отбрасывается автоматически — только явный шаг округления с указанным правилом округления приводит её к реальному показателю наименьшей единицы валюты.

Практический пример

Двенадцать одинаковых ежемесячных счетов по $1 234,56, сложенных точной десятичной арифметикой: $1 234,56 × 12 = $14 814,72 точно, против двенадцатикратного сложения константы f64 1234.56 с двойной точностью IEEE-754, которое может отклониться на долю цента в зависимости от порядка сложения, — реальный и задокументированный тип ошибки, не проблема для модели, построенной на точной десятичной арифметике Money.

Теперь применим стандартный резерв на влияние на бюджет в 5 % (множитель 1,05) к этому итогу $14 814,72: $14 814,72 × 1,05 = $15 555,456 — три десятичных знака, потому что умножение точное и автоматически не округляется до двух знаков валюты. Явное округление до 2 знаков банковским методом (half-even) даёт ровно $15 555,46.

Связь с разработкой программного обеспечения

Это прямой фундаментальный урок за принципом «финансовое ПО использует Decimal, а не float» — явно связанный с модулями совокупной стоимости владения и анализа влияния на бюджет этого репозитория, которые сейчас агрегируют затраты обычной плавающей запятой. Аргумент о корректности не требует немедленной миграции этих моделей; он точно указывает, когда система должна сходиться до цента и потому не должна использовать двоичную плавающую запятую для денежной арифметики. См. Точное до цента распределение затрат для парной задачи — деления (а не суммирования) итога без потери цента.

Подводные камни

  • Перевод в float посреди цепочки: извлечение денежного значения как числа с плавающей запятой посреди вычисления (некоторые библиотеки Money даже называют такой метод преобразования «lossy» в качестве явного предупреждения) молча отказывается от гарантии точности для всех последующих вычислений.
  • «Decimal слишком медленный, чтобы волноваться»: отмахивание от точной десятичной арифметики как от лишнего бремени, хотя финансовой отчётности нужны корректность и аудируемость, а не сырая пропускная способность.
  • Применение процента резерва без указания правила округления: half-up против half-even (банковского округления) может изменить последний цент; само соглашение об округлении должно быть заявленным и проверяемым выбором — см. Анализ «затраты-выгоды» о руководстве Green Book Казначейства HM по поправкам на резерв и оптимистичное смещение, то есть о тех числах, к которым применяется этот шаг округления.

Источники