Тема 71 з 91· Інженерія програмного забезпечення та цифрова доставка

Валютно безпечне агрегування витрат

Підсумовування багатьох грошових позицій — місячних рахунків, витрат за майданчиками, багаторічних цифр впливу на бюджет — звичайними двійковими числами з плаваючою комою (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 Treasury про резерви й коригування на optimism bias, саме ту цифру, до якої застосовують цей крок округлення.

Джерела