Управление числовой точностью является краеугольным камнем бухгалтерии с двойной записью. В цифровом учете, особенно при работе с несколькими валютами, ценами на акции и дробными акциями, небольшие ошибки округления могут быстро привести к неприятным ошибкам балансировки. 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 USDBeancount выводит допустимые отклонения следующим образом:
- Для товара
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 рассчитывает «вес» каждой проводки. Правила для этого расчета:
- Простая сумма: Если проводка имеет только сумму (например,
Assets:Cash -100.00 USD), ее вес равен этой точной сумме. - Проводка с ценой: Если проводка имеет цену за единицу (например,
10 FUND @ 38.46 USD), ее вес равенamount × price. - Себестоимость за единицу: Одиночные фигурные скобки содержат себестоимость одной единицы, поэтому
10 FUND {384.61 USD}весит10 × 384.61 = 3,846.10 USD, а не384.61 USD. - Общая себестоимость: Двойные фигурные скобки содержат себестоимость всей проводки, поэтому
10 FUND {{384.61 USD}}весит384.61 USD. Beancount преобразует это в себестоимость за единицу38.461 USDпри хранении партии. - Себестоимость и цена: Если проводка имеет и себестоимость, и цену за единицу (например,
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.
Правила вывода точности
Система автоматического вывода следует нескольким конкретным правилам:
- Формат числа
- Целые суммы (например,
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.
-
Обработка по умолчанию Вы можете установить глобальное или специфичное для валюты допустимое отклонение по умолчанию, если в транзакции нет чисел с десятичными знаками, из которых можно его вывести.
; 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" -
Множитель допустимого отклонения Опция называется
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'.как ошибку загрузки. -
Вывод на основе себестоимости Хотя себестоимость обычно игнорируется при выводе допустимого отклонения, вы можете указать 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В этой транзакции . Транзакция имеет дисбаланс $-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' — и нет настройки, которая квантует сохраненные суммы.
-
Хранение всегда точное. Напишите
53.82135 USD, и в журнале будет храниться53.82135 USD, независимо от ваших настроек допустимых отклонений. Допустимое отклонение решает, принимается ли транзакция; оно никогда не редактирует число. -
Отображение — это отдельная настройка.
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 -
Округление — это ваша проводка, которую нужно написать. Если вы хотите убрать остаток из арифметики, округлите сумму в исходном коде и явно проведите разницу, как в примере с тремя проводками выше.
Детали реализации
Несколько технических моментов проясняют, как Beancount достигает такой надежности.
-
Представление чисел: Beancount использует модуль
decimalPython, а не числа с плавающей точкой. Контекст по умолчанию имеет 28 значащих цифр — всего цифр, а не цифр после запятой, — что позволяет избежать ошибок двоичного представления, распространенных для чисел с плавающей точкой. -
Класс DisplayContext: Этот внутренний класс обрабатывает все форматирование чисел для целей отображения. Он выводит точность каждой валюты из чисел в вашем файле, если только
display_precisionне закрепляет ее, и может форматировать вывод с выровненными столбцами и запятыми. -
Точность против допустимого отклонения: Крайне важно различать эти две концепции:
- Точность относится к формату отображения числа (сколько десятичных знаков показано).
- Допустимое отклонение — это допустимый дисбаланс, используемый при проверках.
Лучшие практики ✨
Вот несколько практических рекомендаций по управлению точностью в вашем журнале.
Первоначальная настройка
Для большинства новых журналов это надежная стартовая конфигурация:
; 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 нет десятичных знаков).
Стратегия миграции
При применении этих концепций к существующему, запутанному журналу:
- Начните с щедрого глобального допустимого отклонения (например,
*:0.05) и более высокогоtolerance_multiplier, чтобы файл прошел проверку. - Постепенно ужесточайте допустимые отклонения и исправляйте появляющиеся ошибки.
- Добавляйте явные цифры к суммам в проблемных транзакциях, чтобы вывод работал правильно.
- Следите за остатком счета округления. Большой или быстро растущий остаток может сигнализировать о системной проблеме, требующей расследования.