Перейти к основному содержимому

Точность и допустимые отклонения

Узнайте, как системы точности и допустимых отклонений 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 \div 2 = 0.000005$ FUND.
  • Для валюты USD число -384.61 имеет 2 десятичных знака. Допустимое отклонение составляет половину последней цифры, то есть $0.01 \div 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), ее вес равен amount × price.
  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. Точность против допустимого отклонения: Крайне важно различать эти две концепции:

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

Лучшие практики ✨

Вот несколько практических рекомендаций по управлению точностью в вашем журнале.

Первоначальная настройка

Для большинства новых журналов это надежная стартовая конфигурация:

; 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/ru/docs/Basics/precision