-
-
Notifications
You must be signed in to change notification settings - Fork 342
Description
Problem
The current @ syntax requires expressing unit cost as the value of 1 unit of the commodity being purchased. For low-value currencies, this creates precision problems.
Concrete example: I recently used a payment app (Peanut/Mercado Pago) in Argentina and am now trying to reconcile my books. The app reported rates like 1 USD = 1479.42 ARS (this rate varies per transaction) and, while the app reported transaction totals rounded to cents, it tracked sub-cent balances internally. After a week of transactions, cumulative rounding errors were around $0.05 because I was only capturing the rounded totals.
The issue is that each canonical rate was something like 1 USD = 1479.42 ARS (2 decimal places), but hledger forces me to express this as 1 ARS = 0.0006759... USD (which needs 7+ decimals to preserve equivalent precision).
Current workarounds and their limitations:
expenses:food 10000 ARS @ 0.00067594 USD # loses precision, cognitively annoying
expenses:food 10000 ARS @@ 6.76 USD # loses sub-cent amounts, cumulative error
assets:cash -49.816820 USD @ 1479.42 ARS # requires computing high-precision USD amount first
expenses:food 73700 ARS
The @@ approach doesn't really solve it because it only records the rounded total from my statement, not the actual exchange rate that was used. Multiple transactions with rounded totals accumulate error over time.
The swapped-posting approach requires manually computing the high-precision USD amount (or leaving it blank and having hledger infer it), which is an extra step and makes the posting less intuitive to read.
Proposal
Add reciprocal unit cost syntax. A few options:
EDIT: Realized my initial option 1 didn't specify the target currency. Updated options:
Option 1: @/ RATE COMMODITY/$
expenses:food 10000 ARS @/ 1479.42 ARS/$
assets:cash
Option 2: = (most clear semantically, but may conflict with balance assertions)
expenses:food 10000 ARS @ $1 = 1479.42 ARS
assets:cash
Semantics for option 1: AMOUNT COMMODITY @/ RATE COMMODITY/TARGET means the cost is AMOUNT / RATE in TARGET currency. This preserves the exchange rate precision as naturally expressed, preventing cumulative errors.
Why this matters
Exchange rates are often quoted in the reciprocal direction from what @ requires. This particularly affects:
- Low-value/high-inflation currencies (ARS, JPY, IDR, KRW, et al.)
- Importing from financial apps/statements that report reciprocal rates
- Anyone tracking expenses in a weak currency but maintaining balance sheets in a strong reference currency
Draft documentation
Reciprocal unit cost: The @/ syntax specifies how many units of the commodity equal one unit of target currency:
AMOUNT COMMODITY @/ RATE COMMODITY/TARGET
This is equivalent to @ (1/RATE) TARGET but preserves the precision of the exchange rate as typically quoted. Example: 10000 ARS @/ 1479.42 ARS/$ equals 10000 / 1479.42 = 6.759405... USD.
Open questions
- Syntax:
@/ RATE COMMODITY/TARGETvs@ X TARGET = Y COMMODITY(though=likely conflicts with balance assertions)? - Should there be a reciprocal total cost
@@/as well? (This use case is less clear to me) - Are there any parser/compatibility concerns that affect potential solutions?