Pular para o conteúdo principal

Precisão e Tolerâncias

Aprenda como os sistemas de precisão e tolerâncias do Beancount ajudam a manter o equilíbrio na contabilidade de partidas dobradas, especialmente ao lidar com transações complexas envolvendo múltiplas moedas e valores fracionários.

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 USD

O Beancount infere as tolerâncias da seguinte forma:

  • Para a commodity FUND, o número 10.22626 tem 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.61 tem 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:

  1. Valor Simples: Se um lançamento tem apenas um valor (ex.: Assets:Cash -100.00 USD), seu peso é exatamente esse valor.
  2. 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.
  3. Custo por Unidade: Chaves simples contêm o custo de uma unidade, então 10 FUND {384.61 USD} pesa 10 × 384.61 = 3,846.10 USD, não 384.61 USD.
  4. Custo Total: Chaves duplas contêm o custo de todo o lançamento, então 10 FUND {{384.61 USD}} pesa 384.61 USD. O Beancount converte isso para um custo por unidade de 38.461 USD ao armazenar o lote.
  5. 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 USD

A 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:

  1. 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.05 unidades. Além disso, você precisa de tolerance_multiplier ou 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 USD e 5.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.
  1. 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"
  2. 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 definir 1.2 nã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 USD

    O nome mais antigo inferred_tolerance_multiplier define o mesmo valor, mas relata Renamed to 'tolerance_multiplier'. como um erro de carregamento.

  3. 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 USD

Asserçõ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 RGAGX

A 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 RGAGX

Tolerâ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 RGAGX

Aumente 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 USD

Nesta transação, 1.245×43.23=53.821351.245 \times 43.23 = 53.82135. 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 USD

Portanto, 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 USD

Essa 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.

  1. O armazenamento é sempre exato. Escreva 53.82135 USD e o livro-razão mantém 53.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.

  2. A exibição é uma configuração separada. display_precision fixa 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
  3. 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.

  1. Representação de Números: O Beancount usa o módulo decimal do 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.

  2. 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_precision a fixe, e pode formatar a saída com colunas alinhadas e vírgulas.

  3. 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ções balance que 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:

  1. Comece com uma tolerância global generosa (ex.: *:0.05) e um tolerance_multiplier mais alto para fazer o arquivo validar.
  2. Gradualmente aperte as tolerâncias e corrija os erros que aparecerem.
  3. Adicione dígitos explícitos aos valores em transações problemáticas para deixar a inferência fazer seu trabalho.
  4. Monitore o saldo da conta de arredondamento. Um saldo grande ou crescendo rapidamente pode sinalizar um problema sistêmico que precisa de investigação.

Fonte: https://beancount.io/pt/docs/Basics/precision