Skip to content

Money and exchange rates

Amounts are exact integers in micros, rounding happens only at defined points with a declared mode, and currency conversion uses ECB reference rates with exact fractions.

Ownrecord never stores or computes money with floating-point numbers. Every amount is an integer, every rounding is deliberate, and every currency conversion is an exact fraction rounded once.

Amounts are micros

An amount is an integer number of micros, millionths of the currency's major unit, together with its currency code. €12.50 is 12500000 EUR micros.

AmountMicrosWhy
€12.501250000012.50 × 1,000,000
¥500500000000The yen has no minor unit; micros are still 1/1,000,000
BHD 1.0051005000The Bahraini dinar has three decimals
€0.000002 per call2Usage prices can be finer than a cent
−€12.50 (a refund)-12500000Negative amounts work the same way

Micros are fine enough for usage prices well below a cent, and wallet balances are kept in micros, so tiny usage charges add up exactly. For even finer prices, price per 1,000 or per 1,000,000 units.

When you write an amount as a decimal string, for example in a data file, it's read digit by digit, never through a floating-point number. Ownrecord accepts at most 6 decimals, or at most the currency's minor-unit decimals where a whole number of minor units is required. Anything finer is refused with amount_invalid, not rounded silently.

Amounts in different currencies are never added, subtracted or compared. Trying it fails with currency_mismatch. Prices are set explicitly per currency; checkout never converts them.

Minor units

Each currency's smallest unit comes from ISO 4217: cents for EUR and USD (2 decimals), none for JPY and KRW (0), fils for BHD, KWD and JOD (3). Payment processors take whole minor units, so €12.50 is sent to them as 1250. An amount is rounded to whole minor units before it's charged, invoiced or paid out.

Rounding

Rounding happens only at defined points:

  • invoice line totals;
  • tax amounts, per invoice line or per tax rate, as the tax regime requires;
  • payment amounts.

Everywhere else, amounts keep their full precision in micros. Each rounding has a declared mode. The default is half-up, and a tax regime can require another one.

ModeRule2.5 →−2.5 →1.6 →1.1 →
half_upNearest; ties away from zero3−321
half_evenNearest; ties to the even neighbour2−221
half_downNearest; ties toward zero2−221
upAway from zero3−322
downToward zero2−211

Every mode treats negative amounts as the mirror image of positive ones, so a credit note always matches the invoice it reverses: €1.005 rounds to €1.01 and −€1.005 to −€1.01. A calculation such as "19% of this amount" or "this price for 7 of 30 days" is one exact fraction, rounded once to the precision needed. It's never rounded to micros first and then again to cents. Any rounding difference that remains in the books is posted to a rounding account in the ledger.

Splitting an amount

When an amount is split, for example a discount across invoice lines, the parts always add up to the original amount exactly. Ownrecord uses the largest-remainder method: each part gets its proportional share rounded down, and the units left over go one each to the parts with the largest remainders (the earlier part wins a tie). No part is off by more than one unit from its exact share.

Exchange rates

Ownrecord uses the euro foreign exchange reference rates of the European Central Bank (ECB). They're quoted as units of each currency per 1 EUR, for about 30 currencies, on every TARGET business day: weekdays except New Year's Day, Good Friday, Easter Monday, 1 May, and 25 and 26 December.

  • Rates are fetched every day at 16:30 Frankfurt time, shortly after the ECB publishes them. Missing days are filled in from the ECB's 90-day file.
  • Rates are stored exactly as published, as decimal strings such as 1.1650. If the ECB corrects a day's rates, the stored rates are replaced.
  • A conversion turns the rate into an exact fraction (1.1650 = 233/200) and rounds once, at the end. Converting between two currencies other than the euro goes through the euro as a single fraction (rate of the target ÷ rate of the source), so it's never rounded twice.

The rate of a date

The rate of a date is the ECB rate published that day. If there's none (a weekend, a holiday, or a weekday before about 16:00 CET), the rate of the last earlier publication day is used. If the last earlier rate is more than 7 days older than the date, Ownrecord refuses the conversion with fx_rate_unavailable instead of using a stale rate. You get the same error for a currency the ECB doesn't publish.

Where rates are used

  • Tax returns in another currency. EU One-Stop-Shop returns are in euros, converted at the ECB rate of the last day of the period. Each regime's filing rules say which rate applies.
  • Registration thresholds, which are counted in the threshold's own currency.
  • Display only, everywhere else. Prices, invoices and payments always stay in the currency they were set in.

Reference rates aren't market rates

The ECB publishes its reference rates for information. They're the rates tax authorities ask for, not the rates your bank or payment processor uses to convert your payouts.