Naar hoofdinhoud springen

Precisie & Toleranties

Leer hoe Beancount's precisie- en tolerantiesystemen helpen om het evenwicht te bewaren in dubbel boekhouden, vooral bij complexe transacties met meerdere valuta's en fractionele waarden.

Het beheren van numerieke precisie is een hoeksteen van dubbel boekhouden. In digitale boekhouding, vooral bij meerdere valuta's, aandelenkoersen en fractionele aandelen, kunnen kleine afrondingsverschillen snel leiden tot frustrerende balanceringsfouten. Beancount biedt een geavanceerd maar intuïtief systeem voor het verwerken van precisie en het instellen van acceptabele toleranties. Deze gids leidt je door de werking ervan. ⚙️

Elk getal op deze pagina is gecontroleerd met Beancount 3.2.3, inclusief de grenzen: elk voorbeeld vermeldt welk residu wordt geaccepteerd en welke één cijfer te ver gaat.

Kernconcepten van Precisie

Het primaire doel van Beancount is ervoor te zorgen dat elke transactie tot nul balanceert. Echter, berekeningen met prijzen of kosten leveren vaak resultaten op met meer decimalen dan praktisch is om vast te leggen. Het tolerantiesysteem staat kleine, acceptabele onevenwichtigheden toe.

Automatische Tolerantie-Afleiding

Standaard leidt Beancount de vereiste tolerantie voor elke transactie automatisch af. Deze afleiding wordt individueel voor elke transactie uitgevoerd en afzonderlijk berekend voor elke betrokken valuta.

De regel is één vermenigvuldiging: de tolerantie voor een valuta is het kleinste cijfer dat in de boekingsbedragen van die valuta wordt gezien, maal de tolerance_multiplier-optie, die standaard 0.5 is. Bij die standaard is de tolerantie de helft van het laatste significante cijfer.

Beschouw bijvoorbeeld deze aankoop:

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

Beancount leidt de toleranties als volgt af:

  • Voor de FUND-grondstof heeft het getal 10.22626 5 decimalen. De tolerantie is de helft van het laatste cijfer, dus $0.00001 \div 2 = 0.000005$ FUND.
  • Voor de USD-valuta heeft het getal -384.61 2 decimalen. De tolerantie is de helft van het laatste cijfer, dus $0.01 \div 2 = 0.005$ USD.

Het kasdeel is waartegen de tolerantie wordt gemeten: 10.22626 × 37.61 is 384.6096386, dus deze transactie is 0.0003614 USD tekort om op nul uit te komen en wordt geladen. Rond het kasdeel af naar -384.60 en het verschil wordt 0.0096386 USD, voorbij de 0.005-tolerantie, en Beancount meldt Transaction does not balance.

Transactiegewichtsregels

Bij het controleren of een transactie balanceert, berekent Beancount het "gewicht" van elke boeking. De regels voor deze berekening zijn:

  1. Eenvoudig Bedrag: Als een boeking alleen een bedrag heeft (bijv. Assets:Cash -100.00 USD), is het gewicht dat exacte bedrag.
  2. Prijsboeking: Als een boeking een prijs per eenheid heeft (bijv. 10 FUND @ 38.46 USD), is het gewicht amount × price.
  3. Kosten per Eenheid: Enkele accolades bevatten de kosten van één eenheid, dus 10 FUND {384.61 USD} weegt 10 × 384.61 = 3,846.10 USD, niet 384.61 USD.
  4. Totale Kosten: Dubbele accolades bevatten de kosten van de volledige boeking, dus 10 FUND {{384.61 USD}} weegt 384.61 USD. Beancount converteert dit naar kosten per eenheid van 38.461 USD wanneer het de partij opslaat.
  5. Kosten en Prijs: Als een boeking zowel kosten als een prijs per eenheid heeft (bijv. 10 FUND {384.61 USD} @ 400.00 USD), worden alleen de kosten gebruikt voor de balancering. De prijs wordt vastgelegd voor rapportage, niet voor rekenkunde.

Regels 3 en 4 zijn degenen die mensen een middag kosten, dus hier staan ze naast elkaar in een bestand dat laadt:

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

De rekening eindigt met 20 FUND in twee partijen, met 4,230.71 USD aan kostprijsbasis tussen hen.

Precisie-Afleidingsregels

Het automatische afleidingssysteem volgt een paar specifieke regels:

  1. Getalnotatie
  • Gehele bedragen (bijv. 10 USD) dragen niet bij aan precisie-afleiding.
  • Eén decimaal is het grofst dat een bedrag kan impliceren: 0.1 × 0.5 = 0.05 eenheden. Daarbuiten heb je tolerance_multiplier of een standaard per valuta nodig, beide hieronder.
  • Kosten en prijzen (bijv. {37.61 USD}) zijn uitgesloten van tolerantie-afleiding standaard. Alleen de primaire bedragen van de boekingen worden gebruikt.
  • Als boekingen voor dezelfde valuta verschillende precisies hebben (bijv. -10.10 USD en 5.123 USD), gebruikt Beancount de grofste (grootste) tolerantie. In dit geval zou het gebaseerd zijn op -10.10 USD, wat een tolerantie van $0.005$ USD oplevert.
  1. Standaardafhandeling Je kunt een globale of valuta-specifieke standaardtolerantie instellen als een transactie geen getallen met decimalen heeft waaruit deze kan worden afgeleid.

    ; 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. Tolerantievermenigvuldiger De optie is tolerance_multiplier, en het is de fractie van het kleinste cijfer dat als tolerant wordt beschouwd — niet een percentage dat erbovenop komt. De standaard is 0.5, dus het instellen van 1.2 maakt controles niet 20% losser: het maakt elke afgeleide tolerantie 2,4 keer de standaard.

    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

    De oudere naam inferred_tolerance_multiplier stelt dezelfde waarde in maar meldt Renamed to 'tolerance_multiplier'. als laadfout.

  3. Kostengebaseerde Afleiding Hoewel kosten normaal worden genegeerd voor tolerantie-afleiding, kun je Beancount instrueren ze te gebruiken. Dit is handig wanneer het uiteindelijke bedrag (bijv. een contante opname) het meest precieze getal in een transactie is.

    option "infer_tolerance_from_cost" "TRUE"

Hier is de gewone standaard, zonder opties, op zijn exacte grens:

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

Saldo-Asserties

Saldo-asserties (balance) worden gebruikt om te verifiëren dat het saldo van je rekening overeenkomt met een bekende waarde op een specifieke datum. Ze hebben ook een bijbehorende tolerantie.

Basisaanduiding

De tolerantie voor een balance-assertie wordt afgeleid uit het aantal decimalen in het bedrag, maar is twee keer zo gul als die binnen een transactie: tolerance_multiplier × 2 × the smallest digit. Bij de standaardvermenigvuldiger is dat precies één eenheid van het laatste decimaal dat je schreef.

; 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

De vergelijking is inclusief: een verschil dat exact gelijk is aan de tolerantie slaagt nog steeds. Voor het tweede voorbeeld slaagt elk saldo van $4.26$ tot $4.28$ voor de controle, en 4.2801 faalt met 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

Expliciete Toleranties

Als de afgeleide tolerantie niet geschikt is, kun je er expliciet een specificeren met het tilde (~)-teken. Dit is de enige expliciete tolerantiesyntaxis die Beancount heeft, en het werkt alleen op balance-richtlijnen — een tilde binnen een transactieboeking is een syntaxisfout.

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

Verhoog het bezit naar 4.2811 en dezelfde assertie faalt met 0.0101.

Afrondingsbeheer

Kleine residuen van kosten- en prijsberekeningen zijn normaal. Wat Beancount ermee doet is smaller dan het lijkt.

Tracking van Afrondingsfouten

De account_rounding-optie benoemt een rekening bedoeld om residuen te absorberen. Het neemt een volledige rekeningnaam en wordt exact opgeslagen zoals je het schrijft — geen eigen vermogen-voorvoegsel wordt toegevoegd, in tegenstelling tot de eigen vermogen rekeningopties.

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

In deze transactie is 1.245×43.23=53.821351.245 \times 43.23 = 53.82135. De transactie is onevenwichtig met $-0.00135$ USD, wat binnen de afgeleide 0.005 USD-tolerantie valt, dus het laadt.

Op Beancount 3.2.3 wordt er niets geboekt naar Equity:Rounding. De optie wordt geparset en opgeslagen, maar geen enkele fase van de lader voegt de residuele boeking in, dus de rekening eindigt op nul en een residu dat buiten tolerantie valt is nog steeds een fout in plaats van te worden opgeveegd:

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

Behandel account_rounding dus als inert op deze versie. Als je een residu vastgelegd wilt hebben in plaats van getolereerd, schrijf dan zelf de derde boeking:

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

Die versie balanceert naar exact nul, en het stof is zichtbaar in een rekening waarover je kunt rapporteren.

Afgeleide Getalprecisie

Beancount rondt de getallen die je schrijft niet af. Er is geen default_tolerance-optie — die bestaat niet en faalt de laad met Invalid option: 'default_tolerance' — en geen instelling kwantiseert opgeslagen bedragen.

  1. Opslag is altijd exact. Schrijf 53.82135 USD en de grootboek houdt 53.82135 USD vast, ongeacht je tolerantie-instellingen. Tolerantie bepaalt of een transactie wordt geaccepteerd; het bewerkt nooit een getal.

  2. Weergave is een aparte instelling. display_precision bepaalt met hoeveel fractionele cijfers een valuta wordt weergegeven, en verandert niets aan de opgeslagen waarde of de saldocontrole.

    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. Afronding is jouw boeking om te schrijven. Als je het residu uit de rekenkunde wilt, rond het bedrag dan af in de bron en boek het verschil expliciet, zoals in het voorbeeld met drie boekingen hierboven.

Implementatiedetails

Een paar technische punten verduidelijken hoe Beancount deze betrouwbaarheid bereikt.

  1. Getalrepresentatie: Beancount gebruikt Python's decimal-module, geen drijvende-kommagetallen. De standaardcontext draagt 28 significante cijfers — totale cijfers, niet cijfers na de komma — wat de binaire representatiefouten vermijdt die gebruikelijk zijn bij floats.

  2. DisplayContext-klasse: Deze interne klasse handelt alle getalopmaak af voor weergavedoeleinden. Het leidt de precisie van elke valuta af uit de getallen in je bestand, tenzij display_precision het vastpint, en kan uitvoer opmaken met uitgelijnde kolommen en komma's.

  3. Precisie vs. Tolerantie: Het is cruciaal om deze twee concepten te onderscheiden:

  • Precisie heeft betrekking op het weergaveformaat van een getal (hoeveel decimalen worden getoond).
  • Tolerantie is de toegestane onevenwichtigheid die wordt gebruikt tijdens verificatiecontroles.

Beste Praktijken ✨

Hier zijn enkele praktische aanbevelingen voor het beheren van precisie in je grootboek.

Initiële Configuratie

Voor de meeste nieuwe grootboeken is dit een robuuste startconfiguratie:

; 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"

Probleemoplossingstips

Als je balanceringsfouten tegenkomt:

  • Voeg decimalen toe aan het bedrag van een boeking om een strakkere, nauwkeurigere lokale tolerantie-afleiding te creëren.
  • Gebruik expliciete toleranties (~) op balance-asserties die falen vanwege voorspelbare verschillen.
  • Boek het residu naar een toegewijde rekening met een echte derde boeking, zodat je kunt rapporteren hoe vaak het voorkomt.
  • Overweeg het instellen van valuta-specifieke standaarden als je vaak te maken hebt met valuta's met verschillende conventies (bijv. JPY heeft geen decimalen).

Migratiestrategie

Bij het toepassen van deze concepten op een bestaand, rommelig grootboek:

  1. Begin met een ruime globale tolerantie (bijv. *:0.05) en een hogere tolerance_multiplier om het bestand te laten valideren.
  2. Verklein de toleranties geleidelijk en los de fouten op die verschijnen.
  3. Voeg expliciete cijfers toe aan bedragen in problematische transacties zodat afleiding zijn werk kan doen.
  4. Monitor het saldo van de afrondingsrekening. Een groot of snelgroeiend saldo kan wijzen op een systemisch probleem dat onderzoek vereist.

Bron: https://beancount.io/nl/docs/Basics/precision