Перейти до основного вмісту

Точність і Допуски

Дізнайтеся, як системи точності та допусків Beancount допомагають підтримувати баланс у подвійній бухгалтерії, особливо при роботі зі складними транзакціями, що включають кілька валют та дробові значення.

Управління числовою точністю є основою подвійного бухгалтерського обліку. У цифровому веденні бухгалтерії, особливо при роботі з кількома валютами, цінами акцій та дробовими частинами акцій, невеликі похибки округлення можуть швидко призвести до фруструючих помилок балансування. Beancount надає складну, але інтуїтивну систему обробки точності та встановлення прийнятних допусків. Цей посібник проведе вас через принципи її роботи. ⚙️

Кожне число на цій сторінці перевірено за допомогою Beancount 3.2.3, включаючи межі: у кожному прикладі зазначено, який залишок вважається прийнятним, а який — на один розряд занадто великим.

Основні поняття точності

Головна мета Beancount — забезпечити баланс кожної транзакції до нуля. Однак розрахунки із цінами або витратами часто дають результати з більшою кількістю десяткових розрядів, ніж практично записувати. Система допусків дозволяє допускати невеликі, прийнятні дисбаланси.

Автоматичне виведення допуску

За замовчуванням Beancount автоматично визначає необхідний допуск для кожної транзакції. Це визначення відбувається індивідуально для кожної транзакції та обчислюється окремо для кожної залученої валюти.

Правило — одне множення: допуск для валюти — це найменший розряд, що зустрічається у сумі руху в цій валюті, помножений на параметр tolerance_multiplier, що за замовчуванням дорівнює 0.5. При цьому допуск становить половину останньої значущої цифри.

Наприклад, розглянемо цю покупку:

2013-04-03 * "Buy Fund"
  Assets:Fund     10.22626 FUND {37.61 USD}
  Assets:Cash     -384.61 USD

Beancount виводить допуски таким чином:

  • Для товару FUND число 10.22626 має 5 десяткових розрядів. Допуск — половина останньої цифри, тобто $0.00001 ÷ 2 = 0.000005$ FUND.
  • Для товару USD число -384.61 має 2 десяткових розряди. Допуск — половина останньої цифри, тобто $0.01 ÷ 2 = 0.005$ USD.

Грошовий складовий елемент — це по відношенню до якого вимірюється допуск: 10.22626 × 37.61 дорівнює 384.6096386, тож ця операція недобалансує на 0.0003614 USD, що прийнятно. Якщо округлити грошовий складник до -384.60, то розрив становитиме 0.0096386 USD, що перевищує допуск 0.005, і Beancount повідомить Transaction does not balance.

Правила ваги транзакції

При перевірці балансу транзакції Beancount обчислює "вагу" кожного руху. Правила такої оцінки:

  1. Проста сума: якщо у руху є тільки сума (наприклад, Assets:Cash -100.00 USD), його вага дорівнює цій точній сумі.
  2. Ціна за одиницю: якщо рух має ціну за одиницю (наприклад, 10 FUND @ 38.46 USD), його вага — це сума × ціна.
  3. Вартість за одиницю: одинарні дужки {} позначають вартість однієї одиниці, тож 10 FUND {384.61 USD} має вагу 10 × 384.61 = 3,846.10 USD, а не 384.61 USD.
  4. Загальна вартість: подвійні дужки {{}} містять вартість всіх одиниць у цілому, тож 10 FUND {{384.61 USD}} має вагу 384.61 USD. Beancount конвертує це у вартість за одиницю 38.461 USD при зберіганні лоту.
  5. Вартість і ціна: якщо рух має і вартість, і ціну за одиницю (наприклад, 10 FUND {384.61 USD} @ 400.00 USD), для балансування враховується тільки вартість. Ціна зберігається для звіту, а не для арифметики.

Правила 3 і 4 — це найчастіше причина головного болю, тому ось вони поруч у файлі, який завантажується:

1970-01-01 open Assets:Fund
1970-01-01 open Assets:Cash
 
; Per-unit cost: ten units at 384.61 each, so 3,846.10 USD leaves the
; cash account.
2013-04-03 * "Broker" "Buy at a per-unit cost"
  Assets:Fund     10 FUND {384.61 USD}
  Assets:Cash  -3846.10 USD
 
; Total cost: the braces double and 384.61 USD is the entire purchase.
; The lot is stored at 38.461 USD per unit.
2013-04-04 * "Broker" "Buy at a total cost"
  Assets:Fund      10 FUND {{384.61 USD}}
  Assets:Cash   -384.61 USD

Рахунок закінчується 20 FUND у двох лотах, між якими сумарна вартість 4,230.71 USD.

Правила виведення точності

Система автоматичного виведення дотримується кількох конкретних правил:

  1. Формат числа
  • Цілі суми (наприклад, 10 USD) не впливають на виведення точності.
  • Одна десяткова цифра — це максимальна груба точність, яку може означати сума: 0.1 × 0.5 = 0.05 одиниць. Якщо потрібно точніше — використовуйте tolerance_multiplier або значення за замовчуванням для валюти, описані нижче.
  • Витрати й ціни (наприклад, {37.61 USD}) за замовчуванням виключені з виведення допусків. Лише основні значення рухів використовуються.
  • Якщо рухи в одній валюті мають різну точність (наприклад, -10.10 USD і 5.123 USD), Beancount обирає найгрубіший (найбільший) допуск. У цьому випадку він базуватиметься на -10.10 USD, даючи допуск $0.005$ USD.
  1. Обробка за замовчуванням Ви можете задати глобальний або валютно-специфічний допуск за замовчуванням, якщо транзакція не містить чисел із десятковими розрядами для виведення значення.

    ; Sets a default tolerance for all currencies without explicit rules
    option "inferred_tolerance_default" "*:0.001"
     
    ; Sets a specific default tolerance for USD
    option "inferred_tolerance_default" "USD:0.003"
  2. Множник допуску Параметр tolerance_multiplier — це частка найменшої цифри, яку вважають допустимою, а не відсоток, доданий зверху. За замовчуванням — 0.5, тож встановлення 1.2 не послабить перевірки на 20%: воно збільшить всі виведені допуски в 2.4 раза від стандартних.

    option "tolerance_multiplier" "1.2"
     
    1970-01-01 open Assets:Cash
    1970-01-01 open Expenses:Fees
     
    ; The coarsest amount has two decimals, so the tolerance is
    ; 1.2 x 0.01 = 0.012 USD, and this residual of exactly 0.012 passes.
    ; At the default 0.5 the tolerance would be 0.005 and this would fail.
    2024-05-01 * "Bank" "Wire fee"
      Expenses:Fees      100.00 USD
      Assets:Cash       -99.988 USD

    Старе ім’я inferred_tolerance_multiplier задає те саме значення, але під час завантаження видає помилку Renamed to 'tolerance_multiplier'..

  3. Виведення на основі вартості Хоча витрати зазвичай ігноруються у виведенні допусків, можна вказати Beancount використовувати їх. Це корисно, коли найточнішим числом у транзакції є остаточна сума (наприклад, зняття готівки).

    option "infer_tolerance_from_cost" "TRUE"

Ось базове значення за замовчуванням без жодних опцій, на його точно межі:

1970-01-01 open Assets:Cash
1970-01-01 open Expenses:Fees
 
; Two decimals on the coarsest amount, so the tolerance is
; 0.5 x 0.01 = 0.005 USD. This residual is exactly 0.005 and passes;
; -99.994 would be 0.006 and would fail.
2024-05-01 * "Bank" "Wire fee"
  Expenses:Fees      100.00 USD
  Assets:Cash       -99.995 USD

Перевірки балансу

Перевірки балансу (balance) використовуються, щоб переконатися, що баланс вашого рахунку відповідає відомому значенню на певну дату. Для них також застосовується пов’язаний допуск.

Базовий формат

Допуск для перевірки balance виводиться з кількості десяткових розрядів у сумі, але він у двічі більший, ніж той, що використовується всередині транзакції: tolerance_multiplier × 2 × the smallest digit. При стандартному множнику це рівно одна одиниця останнього десяткового розряду, який ви записали.

; Asserts the balance is 4.271 RGAGX with a tolerance of +/-0.001
2015-05-08 balance Assets:Fund  4.271 RGAGX
 
; Asserts the balance is 4.27 RGAGX with a tolerance of +/-0.01
2015-05-08 balance Assets:Fund  4.27 RGAGX

Порівняння включне: різниця, що точно дорівнює допуску, проходить перевірку. Для другого прикладу будь-який баланс від $4.26$ до $4.28$ проходить перевірку, а 4.2801 дає помилку Balance failed for 'Assets:Fund': expected 4.27 RGAGX != accumulated 4.2801 RGAGX (0.0101 too much).

1970-01-01 open Assets:Fund
1970-01-01 open Equity:Opening-Balances
 
1970-01-02 * "Broker" "Opening position"
  Assets:Fund                4.28 RGAGX
  Equity:Opening-Balances   -4.28 RGAGX
 
; 4.28 is 0.01 away from the asserted 4.27, which is the whole tolerance.
2015-05-08 balance Assets:Fund   4.27 RGAGX

Явні допуски

Якщо виведений допуск не підходить, можна встановити його явно за допомогою символу тильди (~). Це єдине явне позначення допуску, яке підтримує Beancount, і воно працює лише з директивами balance — тильда всередині руху є синтаксичною помилкою.

1970-01-01 open Assets:Fund
1970-01-01 open Equity:Opening-Balances
 
1970-01-02 * "Broker" "Opening position"
  Assets:Fund                4.281 RGAGX
  Equity:Opening-Balances   -4.281 RGAGX
 
; Asserts the balance is 4.271 RGAGX with a custom tolerance of
; +/-0.01 RGAGX, so anything from 4.261 to 4.281 passes.
2015-05-08 balance Assets:Fund   4.271 ~ 0.01 RGAGX

Підвищте утримання до 4.2811, і перевірка з тильною заданою помилкою не пройде через 0.0101.

Управління округленням

Невеликі залишкові похибки внаслідок арифметики вартості й ціни є нормальними. Те, що робить з ними Beancount, має більш вузький обсяг.

Відстеження помилок округлення

Опція account_rounding задає рахунок, призначений для поглинання залишків. Вона приймає повну назву рахунку і зберігається точно так, як ви її записали — префікс власного капіталу не додається, на відміну від опцій рахунків власного капіталу.

option "account_rounding" "Equity:Rounding"
 
1970-01-01 open Assets:Invest
1970-01-01 open Assets:Cash
1970-01-01 open Equity:Rounding
 
; 1.245 x 43.23 = 53.82135, so this is 0.00135 USD short of balancing.
2013-02-23 * "Broker" "Purchase"
  Assets:Invest     1.245 RGAGX {43.23 USD}
  Assets:Cash      -53.82 USD

У цій транзакції 1.245×43.23=53.821351.245 \times 43.23 = 53.82135. Транзакція має дисбаланс у розмірі $-0.00135$ USD, що знаходиться всередині виведеного допуску 0.005 USD, тому вона приймається.

У Beancount 3.2.3 нічого не записується на Equity:Rounding. Опція розбирається і зберігається, але жоден етап завантажувача не вставляє рух з залишком, тому рахунок закінчується на нулі, і залишок, який виходить за межі допуску, лишається помилкою замість того, щоб бути поглиненим:

option "account_rounding" "Equity:Rounding"
 
1970-01-01 open Assets:Invest
1970-01-01 open Assets:Cash
1970-01-01 open Equity:Rounding
 
; 0.10135 USD out, far past the 0.005 tolerance. Setting
; account_rounding does not rescue it:
;   Transaction does not balance: (0.10135 USD)
2013-02-23 * "Broker" "Purchase"
  Assets:Invest     1.245 RGAGX {43.23 USD}
  Assets:Cash      -53.72 USD

Отже, розглядайте account_rounding в цій версії як неактивну. Якщо бажаєте записати залишок явно, а не терпляче ігнорувати, створіть третій рух власноруч:

1970-01-01 open Assets:Invest
1970-01-01 open Assets:Cash
1970-01-01 open Equity:Rounding
 
2013-02-23 * "Broker" "Purchase"
  Assets:Invest      1.245 RGAGX {43.23 USD}
  Assets:Cash       -53.82 USD
  Equity:Rounding   -0.00135 USD

Ця версія балансується точно до нуля, і пил видно у звіті по цьому рахунку.

Виведена точність числа

Beancount не округлює числа, які ви записуєте. Опції default_tolerance не існує — її спроба завантажити дасть помилку Invalid option: 'default_tolerance' — і жодна настройка не квантізує збережені суми.

  1. Зберігання завжди точне. Запишіть 53.82135 USD, і в реєстрі він збережеться саме як 53.82135 USD, незалежно від налаштувань допусків. Допуск визначає, чи транзакція приймається; він ніколи не редагує число.

  2. Відображення — окрема настройка. display_precision фіксує скільки дробових розрядів відображається для валюти, і не впливає на збережене значення чи перевірку балансу.

    option "display_precision" "USD:0.01"
     
    1970-01-01 open Assets:Cash
    1970-01-01 open Income:Interest
     
    ; Rendered as 53.82 USD, stored as 53.82135 USD.
    2024-06-30 * "Bank" "Interest"
      Assets:Cash          53.82135 USD
      Income:Interest     -53.82135 USD
  3. Округлення – це рух, який ви записуєте. Якщо хочете усунути залишок арифметики, округліть суму у джерелі і зафіксуйте різницю окремим записом, як у прикладі з трьома рухами вище.

Технічні подробиці

Декілька технічних моментів про те, як Beancount досягає цієї надійності.

  1. Подання чисел: Beancount використовує модуль decimal Python, не числа з плаваючою точкою. Контекст за замовчуванням має 28 значущих цифр — загальна кількість цифр, а не кількість знаків після коми — що уникає стандартних помилок бінарного представлення чисел з плаваючою точкою.

  2. Клас DisplayContext: внутрішній клас обробляє форматування чисел для відображення. Він виводить точність кожної валюти з чисел файлу, якщо display_precision не фіксує її, і може форматувати вивід з вирівнюванням стовпчиків і роздільниками тисяч.

  3. Точність vs. Допуск: важливо розрізняти ці поняття:

  • Точність стосується формату відображення числа (кількість показаних десяткових розрядів).
  • Допуск — це припустиме розбіжність при перевірках балансу.

Кращі практики ✨

Ось кілька практичних рекомендацій для управління точністю у вашому реєстрі.

Початкове налаштування

Для більшості нових реєстрів ця конфігурація є надійним стартом:

; A floor for currencies that have no decimals to infer from
option "inferred_tolerance_default" "*:0.005"
 
; Leave the multiplier at its 0.5 default unless a real institution
; forces your hand; 1.2 would mean 2.4x the usual tolerance.
option "tolerance_multiplier" "0.5"

Поради з усунення неполадок

Якщо виникають помилки балансування:

  • Додавайте десяткові розряди у суму руху, щоб забезпечити більш точне локальне виведення допуску.
  • Використовуйте явні допуски (~) у перевірках balance, що не проходять через передбачувані розбіжності.
  • Обробляйте залишок на окремому рахунку, з реальним третім рухом, щоб мати змогу аналізувати частоту таких подій.
  • Розгляньте можливість встановлення валютно-специфічних допусків, якщо часто працюєте з валютами з різними правилами (наприклад, у JPY немає десяткових розрядів).

Стратегія міграції

Коли застосовуєте ці концепції до існуючого, можливо, хаотичного реєстру:

  1. Почніть з щедрого глобального допуску (наприклад, *:0.05) і вищого tolerance_multiplier, щоб через це пройшла валідація файлу.
  2. Поступово звужуйте допуски і виправляйте помилки, що з’являються.
  3. Додавайте явні цифри до сум у проблемних транзакціях, щоб дозволити виведенню виконувати свою роль.
  4. Слідкуйте за балансом рахунку для округлення. Великий або швидко зростаючий баланс може сигналізувати про системну проблему, яку треба дослідити.

Джерело: https://beancount.io/uk/docs/Basics/precision