Gerenciar a precisão numérica é uma pedra angular da contabilidade de partidas dobradas. Na escrituração digital, especialmente ao lidar com múltiplas moedas, preços de ações e frações de ações, pequenas discrepâncias de arredondamento podem rapidamente levar a erros de balanceamento frustrantes. O Beancount fornece um sistema sofisticado, mas intuitivo, para lidar com precisão e definir tolerâncias aceitáveis. Este guia explicará como ele funciona. ⚙️
Cada número nesta página foi verificado com o Beancount 3.2.3, incluindo os limites: cada exemplo indica qual resíduo é aceito e qual está um dígito além.
Conceitos Centrais de Precisão
O objetivo principal do Beancount é garantir que cada transação seja balanceada a zero. No entanto, cálculos envolvendo preços ou custos frequentemente produzem resultados com mais casas decimais do que é prático registrar. O sistema de tolerâncias permite pequenos desequilíbrios aceitáveis.
Inferência Automática de Tolerância
Por padrão, o Beancount infere a tolerância necessária para cada transação automaticamente. Essa inferência é tratada individualmente para cada transação e calculada separadamente para cada moeda envolvida.
A regra é uma multiplicação: a tolerância para uma moeda é o menor dígito visto nos valores dos lançamentos dessa moeda, vezes a opção tolerance_multiplier, que tem como padrão 0.5. Com esse padrão, a tolerância é metade do último dígito significativo.
Por exemplo, considere esta compra:
2013-04-03 * "Buy Fund"
Assets:Fund 10.22626 FUND {37.61 USD}
Assets:Cash -384.61 USDO Beancount infere as tolerâncias da seguinte forma:
- Para a commodity
FUND, o número10.22626tem 5 casas decimais. A tolerância é metade do último dígito, então $0.00001 \div 2 = 0.000005$FUND. - Para a moeda
USD, o número-384.61tem 2 casas decimais. A tolerância é metade do último dígito, então $0.01 \div 2 = 0.005$USD.
A perna de caixa é contra a qual a tolerância é medida: 10.22626 × 37.61 é 384.6096386, então esta transação está 0.0003614 USD aquém de zero e carrega. Arredonde a perna de caixa para -384.60 e a diferença se torna 0.0096386 USD, ultrapassando a tolerância de 0.005, e o Beancount relata Transaction does not balance.
Regras de Peso da Transação
Ao verificar se uma transação está balanceada, o Beancount calcula o "peso" de cada lançamento. As regras para este cálculo são:
- Valor Simples: Se um lançamento tem apenas um valor (ex.:
Assets:Cash -100.00 USD), seu peso é exatamente esse valor. - Lançamento com Preço: Se um lançamento tem um preço por unidade (ex.:
10 FUND @ 38.46 USD), seu peso éamount × price. - Custo por Unidade: Chaves simples contêm o custo de uma unidade, então
10 FUND {384.61 USD}pesa10 × 384.61 = 3,846.10 USD, não384.61 USD. - Custo Total: Chaves duplas contêm o custo de todo o lançamento, então
10 FUND {{384.61 USD}}pesa384.61 USD. O Beancount converte isso para um custo por unidade de38.461 USDao armazenar o lote. - Custo e Preço: Se um lançamento tem tanto um custo quanto um preço por unidade (ex.:
10 FUND {384.61 USD} @ 400.00 USD), apenas o custo é usado para o balanceamento. O preço é registrado para relatórios, não para aritmética.
As regras 3 e 4 são as que custam uma tarde às pessoas, então aqui estão lado a lado em um arquivo que carrega:
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 USDA conta termina com 20 FUND em dois lotes, com 4,230.71 USD de custo base entre eles.
Regras de Inferência de Precisão
O sistema de inferência automática segue algumas regras específicas:
- Formato do Número
- Valores inteiros (ex.:
10 USD) não contribuem para a inferência de precisão. - Uma casa decimal é o mais grosseiro que um valor pode implicar:
0.1 × 0.5 = 0.05unidades. Além disso, você precisa detolerance_multiplierou de um padrão por moeda, ambos abaixo. - Custos e preços (ex.:
{37.61 USD}) são excluídos da inferência de tolerância por padrão. Apenas os valores primários dos lançamentos são usados. - Se lançamentos para a mesma moeda têm precisões diferentes (ex.:
-10.10 USDe5.123 USD), o Beancount usa a tolerância mais grosseira (maior). Neste caso, seria baseada em-10.10 USD, resultando em uma tolerância de $0.005$USD.
-
Tratamento Padrão Você pode definir uma tolerância padrão global ou específica por moeda se uma transação não tiver números com casas decimais a partir dos quais inferir.
; 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" -
Multiplicador de Tolerância A opção é
tolerance_multiplier, e é a fração do menor dígito que conta como tolerável — não uma porcentagem adicionada em cima. Seu padrão é0.5, então definir1.2não afrouxa as verificações em 20%: torna cada tolerância inferida 2,4 vezes a padrão.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 USDO nome mais antigo
inferred_tolerance_multiplierdefine o mesmo valor, mas relataRenamed to 'tolerance_multiplier'.como um erro de carregamento. -
Inferência Baseada em Custo Embora os custos sejam normalmente ignorados para a inferência de tolerância, você pode instruir o Beancount a usá-los. Isso é útil quando o valor final (ex.: um saque em dinheiro) é o número mais preciso em uma transação.
option "infer_tolerance_from_cost" "TRUE"
Aqui está o padrão simples, sem nenhuma opção, no seu limite exato:
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 USDAsserções de Saldo
Asserções de saldo (balance) são usadas para verificar se o saldo da sua conta corresponde a um valor conhecido em uma data específica. Elas também têm uma tolerância associada.
Formato Básico
A tolerância para uma asserção balance é inferida a partir do número de casas decimais no valor, mas é duas vezes mais generosa que a usada dentro de uma transação: tolerance_multiplier × 2 × the smallest digit. Com o multiplicador padrão, isso é exatamente uma unidade da última casa decimal que você escreveu.
; 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 RGAGXA comparação é inclusiva: uma diferença exatamente igual à tolerância ainda passa. Para o segundo exemplo, qualquer saldo de $4.26$ a $4.28$ passa na verificação, e 4.2801 falha com 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 RGAGXTolerâncias Explícitas
Se a tolerância inferida não for adequada, você pode especificar uma explicitamente usando o caractere til (~). Esta é a única sintaxe de tolerância explícita que o Beancount tem, e funciona apenas em diretivas balance — um til dentro de um lançamento de transação é um erro de sintaxe.
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 RGAGXAumente a participação para 4.2811 e a mesma asserção falha por 0.0101.
Gerenciamento de Arredondamento
Pequenos resíduos da aritmética de custos e preços são normais. O que o Beancount faz com eles é mais restrito do que parece.
Rastreamento de Erros de Arredondamento
A opção account_rounding nomeia uma conta destinada a absorver resíduos. Ela aceita um nome de conta completo e é armazenada exatamente como você a escreve — nenhum prefixo de patrimônio líquido é adicionado, ao contrário das opções de conta de patrimônio líquido.
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 USDNesta transação, . A transação está desbalanceada por $-0.00135$ USD, que está dentro da tolerância inferida de 0.005 USD, então ela carrega.
No Beancount 3.2.3, nada é lançado em Equity:Rounding. A opção é analisada e armazenada, mas nenhuma etapa do carregador insere o lançamento residual, então a conta termina em zero e um resíduo que está fora da tolerância ainda é um erro, em vez de ser varrido:
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 USDPortanto, trate account_rounding como inerte nesta versão. Se você quiser que um resíduo seja registrado em vez de tolerado, escreva o terceiro lançamento você mesmo:
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 USDEssa versão balanceia exatamente a zero, e o resíduo é visível em uma conta sobre a qual você pode relatar.
Precisão Numérica Inferida
O Beancount não arredonda os números que você escreve. Não há opção default_tolerance — ela não existe e falha o carregamento com Invalid option: 'default_tolerance' — e nenhuma configuração quantiza os valores armazenados.
-
O armazenamento é sempre exato. Escreva
53.82135 USDe o livro-razão mantém53.82135 USD, quaisquer que sejam suas configurações de tolerância. A tolerância decide se uma transação é aceita; ela nunca edita um número. -
A exibição é uma configuração separada.
display_precisionfixa quantos dígitos fracionários uma moeda é renderizada, e não muda nada sobre o valor armazenado ou a verificação de saldo.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 -
O arredondamento é um lançamento seu para escrever. Se você quiser o resíduo fora da aritmética, arredonde o valor na fonte e registre a diferença explicitamente, como no exemplo de três lançamentos acima.
Detalhes de Implementação
Alguns pontos técnicos esclarecem como o Beancount alcança essa confiabilidade.
-
Representação de Números: O Beancount usa o módulo
decimaldo Python, não números de ponto flutuante. O contexto padrão carrega 28 dígitos significativos — dígitos totais, não dígitos após o ponto — o que evita os erros de representação binária comuns aos floats. -
Classe DisplayContext: Esta classe interna lida com toda a formatação de números para fins de exibição. Ela infere a precisão de cada moeda a partir dos números no seu arquivo, a menos que
display_precisiona fixe, e pode formatar a saída com colunas alinhadas e vírgulas. -
Precisão vs. Tolerância: É crucial distinguir esses dois conceitos:
- Precisão refere-se ao formato de exibição de um número (quantas casas decimais são mostradas).
- Tolerância é a margem para desequilíbrio usada durante verificações.
Melhores Práticas ✨
Aqui estão algumas recomendações práticas para gerenciar a precisão no seu livro-razão.
Configuração Inicial
Para a maioria dos novos livros-razão, esta é uma configuração inicial robusta:
; 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"Dicas de Solução de Problemas
Se você encontrar erros de balanceamento:
- Adicione dígitos decimais ao valor de um lançamento para criar uma inferência de tolerância local mais precisa e restrita.
- Use tolerâncias explícitas (
~) em asserçõesbalanceque falham devido a discrepâncias previsíveis. - Registre o resíduo em uma conta dedicada com um terceiro lançamento real, para que você possa relatar com que frequência isso acontece.
- Considere definir padrões específicos por moeda se você lida frequentemente com moedas que têm convenções diferentes (ex.: JPY não tem decimais).
Estratégia de Migração
Ao aplicar esses conceitos a um livro-razão existente e bagunçado:
- Comece com uma tolerância global generosa (ex.:
*:0.05) e umtolerance_multipliermais alto para fazer o arquivo validar. - Gradualmente aperte as tolerâncias e corrija os erros que aparecerem.
- Adicione dígitos explícitos aos valores em transações problemáticas para deixar a inferência fazer seu trabalho.
- Monitore o saldo da conta de arredondamento. Um saldo grande ou crescendo rapidamente pode sinalizar um problema sistêmico que precisa de investigação.