본문으로 건너뛰기

Beancount에서 금액을 반올림하고 허용 오차를 설정하는 방법

Beancount가 숫자로부터 허용 오차를 추론하는 방식, 거래나 잔액 검증이 통과되는 시점, 명시적 허용 오차를 설정하고 반올림을 추적하는 방법을 알아봅니다.

수치 정밀도 관리는 복식부기의 핵심입니다. 디지털 부기, 특히 여러 통화, 주식 가격, 분수 주식을 다룰 때 작은 반올림 불일치가 곧 좌절스러운 균형 오류로 이어질 수 있습니다. Beancount는 정밀도 처리 및 허용 가능한 허용 오차 설정을 위한 정교하면서도 직관적인 시스템을 제공합니다. 이 가이드는 그 작동 방식을 안내합니다. ⚙️

이 페이지의 모든 숫자는 Beancount 3.2.3에서 확인되었으며, 경계값도 포함합니다: 각 예제는 어떤 잔차가 허용되고 어떤 것이 한 자리 더 많은지 명시합니다.

핵심 정밀도 개념​

Beancount의 주요 목표는 모든 거래가 0으로 균형을 맞추도록 하는 것입니다. 그러나 가격 또는 원가 계산은 종종 기록하기에 실용적인 것보다 더 많은 소수점 자리를 가진 결과를 생성합니다. 허용 오차 시스템은 작고 허용 가능한 불균형을 허용합니다.

자동 허용 오차 추론​

기본적으로 Beancount는 각 거래에 필요한 허용 오차를 자동으로 추론합니다. 이 추론은 각 거래마다 개별적으로 처리되며 관련된 각 통화에 대해 별도로 계산됩니다.

규칙은 하나의 곱셈입니다: 통화의 허용 오차는 해당 통화의 포스팅 금액에서 보이는 가장 작은 자릿수에 tolerance_multiplier 옵션(기본값 0.5)을 곱한 값입니다. 이 기본값에서 허용 오차는 마지막 유효 숫자의 절반입니다.

예를 들어, 다음 구매를 고려해 보세요:

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

Beancount는 다음과 같이 허용 오차를 추론합니다:

  • 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가 0보다 부족하며 로드됩니다. 현금 항목을 -384.60으로 반올림하면 차이는 0.0096386 USD가 되어 0.005 허용 오차를 초과하고, Beancount는 Transaction does not balance를 보고합니다.

거래 가중치 규칙​

거래 균형을 확인할 때 Beancount는 각 포스팅의 "가중치"를 계산합니다. 이 계산 규칙은 다음과 같습니다:

  1. 단순 금액: 포스팅에 금액만 있는 경우(예: Assets:Cash -100.00 USD), 가중치는 해당 정확한 금액입니다.
  2. 가격 포스팅: 포스팅에 단위당 가격이 있는 경우(예: 10 FUND @ 38.46 USD), 가중치는 amount × price입니다.
  3. 단위당 원가: 중괄호 하나는 한 단위의 원가를 나타내므로 10 FUND {384.61 USD}는 10 × 384.61 = 3,846.10 USD가 아닌 384.61 USD로 가중치가 계산됩니다.
  4. 총 원가: 이중 중괄호는 전체 포스팅의 원가를 나타내므로 10 FUND {{384.61 USD}}는 384.61 USD로 가중치가 계산됩니다. Beancount는 보관할 때 이를 단위당 원가 38.461 USD로 변환합니다.
  5. 원가와 가격: 포스팅에 원가와 단위당 가격이 모두 있는 경우(예: 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의 원가 기준이 있습니다.

정밀도 추론 규칙​

자동 추론 시스템은 몇 가지 특정 규칙을 따릅니다:

  1. 숫자 형식
  • 정수 금액(예: 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의 허용 오차가 생성됩니다.
  1. 기본 처리 추론할 소수점 자리가 없는 거래의 경우 전역 또는 통화별 기본 허용 오차를 설정할 수 있습니다.

    ; 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. 허용 오차 배수 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'.를 보고합니다.

  3. 원가 기반 추론 일반적으로 원가는 허용 오차 추론에서 무시되지만, 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 옵션은 잔차를 흡수하기 위한 계정을 지정합니다. 전체 계정 이름을 사용하며 작성한 그대로 저장됩니다 — equity 계정 옵션과 달리 equity 접두사가 추가되지 않습니다.

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

이 거래에서 1.245×43.23=53.821351.245 \times 43.23 = 53.82135입니다. 거래는 $-0.00135$ USD의 불균형이 있으며, 이는 추론된 0.005 USD 허용 오차 내에 있으므로 로드됩니다.

Beancount 3.2.3에서는 Equity:Rounding에 아무것도 전기되지 않습니다. 옵션은 구문 분석되어 저장되지만 로더의 어떤 단계에서도 잔차 포스팅을 삽입하지 않으므로 계정은 0으로 끝나고 허용 오차 밖에 있는 잔차는 여전히 정리되지 않고 오류로 처리됩니다:

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

이 버전은 정확히 0으로 균형을 맞추며, 먼지는 보고할 수 있는 계정에 표시됩니다.

추론된 숫자 정밀도​

Beancount는 작성한 숫자를 반올림하지 않습니다. default_tolerance 옵션은 존재하지 않으며 존재하지 않는 옵션은 Invalid option: 'default_tolerance'로 로드에 실패하고, 저장된 금액을 양자화하는 설정도 없습니다.

  1. 저장은 항상 정확합니다. 53.82135 USD를 작성하면 원장은 허용 오차 설정과 관계없이 53.82135 USD를 보유합니다. 허용 오차는 거래가 수락되는지 여부를 결정할 뿐 숫자를 편집하지 않습니다.

  2. 표시는 별도의 설정입니다. 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
  3. 반올림은 직접 작성해야 할 포스팅입니다. 산술에서 잔차를 제거하려면 위의 세 포스팅 예제처럼 소스에서 금액을 반올림하고 차이를 명시적으로 기록하세요.

구현 세부 사항​

Beancount가 이 신뢰성을 달성하는 방법을 명확히 하는 몇 가지 기술적 요점이 있습니다.

  1. 숫자 표현: Beancount는 Python의 decimal 모듈을 사용하며 부동 소수점 숫자가 아닙니다. 기본 컨텍스트는 28개의 유효 숫자를 보유합니다 — 소수점 이후 자릿수가 아닌 총 자릿수 — 이는 부동 소수점에서 흔한 이진 표현 오류를 방지합니다.

  2. DisplayContext 클래스: 이 내부 클래스는 표시 목적으로 모든 숫자 형식을 처리합니다. display_precision이 고정하지 않는 한 파일의 숫자에서 각 통화의 정밀도를 추론하며, 정렬된 열과 쉼표로 출력을 형식화할 수 있습니다.

  3. 정밀도와 허용 오차: 이 두 개념을 구분하는 것이 중요합니다:

  • 정밀도는 숫자의 표시 형식 과 관련이 있습니다(표시되는 소수 자릿수).
  • 허용 오차는 검증 중에 사용되는 불균형 허용 한도 입니다.

모범 사례 ✨​

원장에서 정밀도를 관리하기 위한 몇 가지 실용적인 권장 사항입니다.

초기 설정​

대부분의 새 원장에서 이것은 견고한 시작 구성입니다:

; 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는 소수점 없음)를 자주 다루는 경우 통화별 기본값 설정을 고려하세요.

마이그레이션 전략​

이 개념을 기존의 지저분한 원장에 적용할 때:

  1. 넉넉한 전역 허용 오차(예: *:0.05)와 더 높은 tolerance_multiplier로 시작하여 파일이 검증되도록 하세요.
  2. 점진적으로 허용 오차를 좁히고 나타나는 오류를 수정하세요.
  3. 문제가 있는 거래의 금액에 명시적 자릿수를 추가하여 추론이 제 역할을 하게 하세요.
  4. 반올림 계정의 잔액을 모니터링하세요. 크거나 빠르게 증가하는 잔액은 조사가 필요한 시스템적 문제를 나타낼 수 있습니다.

출처: https://beancount.io/ko/docs/Basics/precision