Saltar al contenido principal

Precisión y Tolerancias

Aprende cómo los sistemas de precisión y tolerancias de Beancount ayudan a mantener el equilibrio en la contabilidad de partida doble, especialmente al tratar con transacciones complejas que involucran múltiples divisas y valores fraccionarios.

Gestionar la precisión numérica es una piedra angular de la contabilidad de partida doble. En la contabilidad digital, especialmente al tratar con múltiples divisas, precios de acciones y acciones fraccionarias, pequeñas discrepancias de redondeo pueden llevar rápidamente a frustrantes errores de balanceo. Beancount proporciona un sistema sofisticado pero intuitivo para manejar la precisión y establecer tolerancias aceptables. Esta guía te explicará cómo funciona. ⚙️

Cada número en esta página fue verificado contra Beancount 3.2.3, incluyendo los límites: cada ejemplo indica qué residual es aceptado y cuál está un dígito más allá.

Conceptos Centrales de Precisión

El objetivo principal de Beancount es asegurar que cada transacción se equilibre a cero. Sin embargo, los cálculos que involucran precios o costos a menudo producen resultados con más decimales de los que es práctico registrar. El sistema de tolerancias permite pequeños desequilibrios aceptables.

Inferencia Automática de Tolerancias

Por defecto, Beancount infiere automáticamente la tolerancia requerida para cada transacción. Esta inferencia se maneja individualmente para cada transacción y se calcula por separado para cada divisa involucrada.

La regla es una multiplicación: la tolerancia para una divisa es el dígito más pequeño visto en los importes de los asientos de esa divisa, multiplicado por la opción tolerance_multiplier, que por defecto es 0.5. Con ese valor predeterminado, la tolerancia es la mitad del último dígito significativo.

Por ejemplo, considera esta compra:

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

Beancount infiere las tolerancias de la siguiente manera:

  • Para el commodity FUND, el número 10.22626 tiene 5 decimales. La tolerancia es la mitad del último dígito, por lo que $0.00001 \div 2 = 0.000005$ FUND.
  • Para la divisa USD, el número -384.61 tiene 2 decimales. La tolerancia es la mitad del último dígito, por lo que $0.01 \div 2 = 0.005$ USD.

La parte de efectivo es contra la que se mide la tolerancia: 10.22626 × 37.61 es 384.6096386, por lo que esta transacción está 0.0003614 USD por debajo de cero y carga. Redondea la parte de efectivo a -384.60 y la brecha se convierte en 0.0096386 USD, superando la tolerancia de 0.005, y Beancount reporta Transaction does not balance.

Reglas de Peso de Transacción

Al verificar si una transacción se equilibra, Beancount calcula el "peso" de cada asiento. Las reglas para este cálculo son:

  1. Importe Simple: Si un asiento tiene solo un importe (ej., Assets:Cash -100.00 USD), su peso es ese importe exacto.
  2. Asiento con Precio: Si un asiento tiene un precio por unidad (ej., 10 FUND @ 38.46 USD), su peso es amount × price.
  3. Costo por Unidad: Las llaves simples contienen el costo de una unidad, por lo que 10 FUND {384.61 USD} pesa 10 × 384.61 = 3,846.10 USD, no 384.61 USD.
  4. Costo Total: Las llaves dobles contienen el costo de todo el asiento, por lo que 10 FUND {{384.61 USD}} pesa 384.61 USD. Beancount lo convierte a un costo por unidad de 38.461 USD cuando almacena el lote.
  5. Costo y Precio: Si un asiento tiene tanto un costo como un precio por unidad (ej., 10 FUND {384.61 USD} @ 400.00 USD), solo el costo se usa para el balanceo. El precio se registra para informes, no para aritmética.

Las reglas 3 y 4 son las que le cuestan a la gente una tarde, así que aquí están lado a lado en un archivo que carga:

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

La cuenta termina con 20 FUND en dos lotes, 4,230.71 USD de base de costo entre ellos.

Reglas de Inferencia de Precisión

El sistema de inferencia automática sigue algunas reglas específicas:

  1. Formato de Número
  • Los importes enteros (ej., 10 USD) no contribuyen a la inferencia de precisión.
  • Un decimal es lo más grueso que un importe puede implicar: 0.1 × 0.5 = 0.05 unidades. Más allá de eso necesitas tolerance_multiplier o un valor predeterminado por divisa, ambos a continuación.
  • Los costos y precios (ej., {37.61 USD}) están excluidos de la inferencia de tolerancias por defecto. Solo se usan los importes principales de los asientos.
  • Si los asientos para la misma divisa tienen diferentes precisiones (ej., -10.10 USD y 5.123 USD), Beancount usa la tolerancia más gruesa (mayor). En este caso, se basaría en -10.10 USD, produciendo una tolerancia de $0.005$ USD.
  1. Manejo por Defecto Puedes establecer una tolerancia predeterminada global o específica por divisa si una transacción no tiene números con decimales de los cuales inferirla.

    ; 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 Tolerancia La opción es tolerance_multiplier, y es la fracción del dígito más pequeño que cuenta como tolerable — no un porcentaje añadido. Su valor predeterminado es 0.5, por lo que establecer 1.2 no afloja las verificaciones en un 20%: hace que cada tolerancia inferida sea 2.4 veces la predeterminada.

    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

    El nombre más antiguo inferred_tolerance_multiplier establece el mismo valor pero reporta Renamed to 'tolerance_multiplier'. como un error de carga.

  3. Inferencia Basada en Costos Aunque los costos normalmente se ignoran para la inferencia de tolerancias, puedes indicar a Beancount que los use. Esto es útil cuando el importe final (ej., un retiro de efectivo) es el número más preciso en una transacción.

    option "infer_tolerance_from_cost" "TRUE"

Aquí está el valor predeterminado simple, sin opciones, en su límite exacto:

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

Aserciones de Saldo

Las aserciones de saldo (balance) se usan para verificar que el saldo de tu cuenta coincide con un valor conocido en una fecha específica. También tienen una tolerancia asociada.

Formato Básico

La tolerancia para una aserción de balance se infiere del número de decimales en el importe, pero es dos veces más generosa que la usada dentro de una transacción: tolerance_multiplier × 2 × the smallest digit. Con el multiplicador predeterminado, eso es exactamente una unidad del último decimal que escribiste.

; 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

La comparación es inclusiva: una diferencia exactamente igual a la tolerancia aún pasa. Para el segundo ejemplo, cualquier saldo de $4.26$ a $4.28$ pasa la verificación, y 4.2801 falla con 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

Tolerancias Explícitas

Si la tolerancia inferida no es adecuada, puedes especificar una explícitamente usando el carácter de tilde (~). Esta es la única sintaxis de tolerancia explícita que Beancount tiene, y funciona solo en directivas balance — una tilde dentro de un asiento de transacción es un error de sintaxis.

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

Empuja la tenencia a 4.2811 y la misma aserción falla por 0.0101.

Gestión de Redondeo

Los pequeños residuales de la aritmética de costos y precios son normales. Lo que Beancount hace con ellos es más limitado de lo que parece.

Seguimiento de Errores de Redondeo

La opción account_rounding nombra una cuenta destinada a absorber residuales. Acepta un nombre de cuenta completo y se almacena exactamente como lo escribes — no se añade ningún prefijo de patrimonio, a diferencia de las opciones de cuentas de patrimonio.

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

En esta transacción, 1.245×43.23=53.821351.245 \times 43.23 = 53.82135. La transacción está desequilibrada por $-0.00135$ USD, que está dentro de la tolerancia inferida de 0.005 USD, por lo que carga.

En Beancount 3.2.3 no se publica nada en Equity:Rounding. La opción se analiza y se almacena, pero ninguna etapa del cargador inserta el asiento residual, por lo que la cuenta termina en cero y un residual que está fuera de tolerancia sigue siendo un error en lugar de ser barrido:

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

Así que trata account_rounding como inerte en esta versión. Si quieres que un residual se registre en lugar de tolerarse, escribe el tercer asiento tú mismo:

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

Esa versión equilibra a exactamente cero, y el polvo es visible en una cuenta sobre la que puedes informar.

Precisión Numérica Inferida

Beancount no redondea los números que escribes. No existe la opción default_tolerance — no existe y falla la carga con Invalid option: 'default_tolerance' — y no hay ningún ajuste que cuantice los importes almacenados.

  1. El almacenamiento es siempre exacto. Escribe 53.82135 USD y el libro mayor mantiene 53.82135 USD, lo que sea que digan tus ajustes de tolerancia. La tolerancia decide si una transacción es aceptada; nunca edita un número.

  2. La visualización es un ajuste separado. display_precision fija cuántos dígitos fraccionarios se muestran para una divisa, y no cambia nada sobre el valor almacenado o la verificación 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. El redondeo es un asiento que debes escribir tú. Si quieres el residual fuera de la aritmética, redondea el importe en el archivo fuente y registra la diferencia explícitamente, como en el ejemplo de tres asientos anterior.

Detalles de Implementación

Algunos puntos técnicos aclaran cómo Beancount logra esta fiabilidad.

  1. Representación de Números: Beancount usa el módulo decimal de Python, no números de punto flotante. El contexto predeterminado lleva 28 dígitos significativos — dígitos totales, no dígitos después del punto — lo que evita los errores de representación binaria comunes en los flotantes.

  2. Clase DisplayContext: Esta clase interna maneja todo el formato de números para fines de visualización. Infiere la precisión de cada divisa a partir de los números en tu archivo a menos que display_precision lo fije, y puede formatear la salida con columnas alineadas y comas.

  3. Precisión vs. Tolerancia: Es crucial distinguir estos dos conceptos:

  • Precisión se relaciona con el formato de visualización de un número (cuántos decimales se muestran).
  • Tolerancia es la permitancia por desequilibrio usada durante las verificaciones.

Mejores Prácticas ✨

Aquí hay algunas recomendaciones prácticas para gestionar la precisión en tu libro mayor.

Configuración Inicial

Para la mayoría de los libros mayores nuevos, esta es una configuración 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"

Consejos de Solución de Problemas

Si encuentras errores de balanceo:

  • Añade dígitos decimales al importe de un asiento para crear una inferencia de tolerancia local más ajustada y precisa.
  • Usa tolerancias explícitas (~) en aserciones de balance que fallen debido a discrepancias predecibles.
  • Registra el residual en una cuenta dedicada con un tercer asiento real, para que puedas informar sobre cuán a menudo ocurre.
  • Considera establecer valores predeterminados específicos por divisa si tratas frecuentemente con divisas que tienen diferentes convenciones (ej., JPY no tiene decimales).

Estrategia de Migración

Al aplicar estos conceptos a un libro mayor existente y desordenado:

  1. Comienza con una tolerancia global generosa (ej., *:0.05) y un tolerance_multiplier más alto para que el archivo valide.
  2. Gradualmente aprieta las tolerancias y corrige los errores que aparezcan.
  3. Añade dígitos explícitos a los importes en transacciones problemáticas para que la inferencia haga su trabajo.
  4. Monitorea el saldo de la cuenta de redondeo. Un saldo grande o que crece rápidamente puede señalar un problema sistémico que necesita investigación.

Fuente: https://beancount.io/es/docs/Basics/precision