TL;DR: There are various rounding-related issues in ledger.  This
email (a) shows the problems we've encountered, (b) proposes some
ideas for new directives that might be useful, and c) ends with some
open questions for discussion on this list.

We are planning on addressing some of these issues as part of GSoC
work through Conservancy's NPO Accounting project and will be offering
patches upstream, and therefore would be delighted for more input.

The problem
-----------

Bradley Kuhn and I ran into various problems related to rounding with
ledger.  This is a proposal based on conversations with Bradley, his
GSoC Tripun Goel and me.  This email shows some problems we've run
into and attempts to provide some ideas for a possible solution which
we can use as a base for discussion.

At the moment, ledger keeps all precision internally and only rounds
when presenting the data to you (based on the precision you use to
input data or on the D or commodity format directives).  ledger
keeping around all that precision can lead to problems.

Let's look at some examples:

D 10.00 EUR

2014-01-01 Opening balance
 Assets:Investments     10 AAA @ 10.1234 EUR
 Assets:Investments     10 BBB @ 10.5692 EUR
 Equity:Opening balance

I have 10 AAA worth 10.1234 each and 10 BBB worth 10.5692 each.
ledger will just add everything up: 10*10.1234 + 10*10.5692 = 206.926.
Since the display precision is 2 digits, it will round to 206.93.  The
calculation is obviously correct.  But it doesn't represent what
happens in reality: in real life, your broker will round to cents:
your 10 AAA are worth 101.23 and not 101.234, and 10 BBB is 105.69 and
not 105.692.  Now if you add it up, you get 206.92 (101.23+105.69).
So ledger is off by a cent compared to what you'd get in real life.

Now you can argue: why use @ instead of @@?  I could solve this
particular problem like this:

2014-01-01 Opening balance - workaround
 Assets:Investments2     10 CCC @@ 101.23 EUR
 Assets:Investments2     10 DDD @@ 105.69 EUR
 Equity:Opening balance

However, there is often value in specifying a detailed exchange rate
with @.

Let's take a look at another example:

D 1000.00 EUR

2012-01-01 * Test
   Assets:Investments       1 AAA @@ 10.00 EUR
   Assets:Investments       1 BBB @@ 20.00 EUR
   Equity:Opening balance

P 2012-07-01 AAA 10.123 EUR
P 2012-07-01 BBB 20.123 EUR

I have 1 AAA which I bought for 10.00 and 1 BBB I bought for 20.00.
Later, the value of the shares go up to 10.123 and 20.123,
respectively.  How much do I have now?  ledger will do: 10.123 +
20.123 = 30.246 => 30.25, but in reality, it's 30.24.  I cannot have
0.003, so it's really worth: 10.12 and 20.12. (Imagine what would
happen if you sold 1 AAA: how much would you get on your account?
10.12 or 10.123?)

However, it makes sense to have a lot of precision in the exchange
rate / value of the shares.  If I had 10 AAA instead, the precision of
the pricedb would matter: 10*10.123 = 101.23.  You cannot just drop
digits from the pricedb (10.123=>10.12) because this would change the
value if you have a lot.

Another example for this: a currency exchange rate might be something
like 1.6415 (GBP->USD).  Of course, if you only exchange 1 GBP to USD,
you wouldn't get 1.6415 -- you would get 1.64 (assuming no fees).  But
if you exchanged a 1000, the precision of the pricedb matters: you get
1641.50.

However, for the actual amounts, it doesn't make sense to keep all the
precision around.  Many of us think like programmers and want to keep
things as precise and "correct" as possible.  But with accounting, you
have to represent reality: and in real life, you don't have 1.6415 USD
when you exchange one GBP to one USD: you have 1.64 USD.

Here's another example showing that the current behaviour of ledger
will lead to strange results:

D $1000.00

2014-06-15 Test 1
    Liabilities:Payable           €-19.86 @ $1.3869
    Expenses

2014-06-16 Test 2
    Liabilities:Payable           €-19.86 @ $1.3869
    Expenses

In this example, you owe 19.86€ to someone, so you charge it as an
expense and accrue it.  The books are in USD.  You make the same
expense twice.  So let's look at it individually: how much is the
first expense?  It's $27.54.  And you do this twice.  So how much did
you spent in total?  Well, you'd probably say 2*$27.54 = $55.08.  But
if you ask ledger, it will tell you that the total expenses were
$55.09.  Why?  Because 19.86*1.3869 is actually $27.543834 and not
$27.54.  And if you do 2*$27.543834, you get $55.09.

Just imagine a conversation with your accounting.  "So you spent
$27.54 and then you spent $27.54, but your total expenses are $55.09?
Why is that?" "Oh, you know, the expenses weren't really $27.54...
they were actually $27.543834".  No, they weren't.  In practice, each
expense was $27.54.

One final example: you buy 123 shares for 1.23456 each and later sell
them for 2.345 each.  So first of all, how much money do you get?
123*2.345 = 288.435.  But do you really get that amount?  No, you get
288.44 if you're lucky (or 288.43 if you're not).

And how much capital gain was there?  Again, the maths is simple:
123*(2.345-1.23456) = 136.58412

But are you really going to tell your tax authority that you made a
gain of 136.58412?  No, you're going to tell them 136.58 (or 136.59).

Here's this example in ledger:

D 1000.00 EUR

2014-06-01 * Opening balance
    Assets:Investments            123 XXX {1.23456 EUR} @ 1.23456 EUR
    Equity:Opening balances          -151.85 EUR

2014-06-22 * Sell some shares
    Assets:Investments            -123 XXX {1.23456 EUR} @ 2.345 EUR
    Assets:Cash                        288.44 EUR
    Income:Capital gains              -136.59 EUR

This balances and works fine.  But if you change "D 1000.00 EUR" to
"D 1000.000 EUR", suddenly it won't balance:

Unbalanced remainder is:
          -0.001 EUR
Amount to balance against:
         288.440 EUR

This shouldn't happen.  The display precision shouldn't affect the
calculation: as you can see from these examples, the extra ledger
keeps internally is causing lots of problems in real life.

Now it's great that ledger can keep precision if there's a need, but
in most real-life scenarios, we want to round to 2 digits of
precision.

Possible solution
-----------------

We could have a "commodity" directive called "precision" (or something
similar):

commodity EUR
    precision 2

This would tell ledger to round to 2 digits of precision.  This
rounding is done internally, i.e. ledger wouldn't keep more detail
than that precision.

But is this enough?  I think we also need the ability to specify *how*
rounding is done?  In most cases, it's probably just rounded properly,
i.e.  0.005+ is rounded up, everything below is rounded down.

But I can imagine that some companies just truncate: so even if you
should get 1.126, they would give you 1.12 instead of 1.23.  (I don't
actually know if this is true, but it might be.)

And maybe there should be one option to make rounding "flexible" or
"permissive": in the example above, ledger would accept both 1.12 and
1.13 for the balancing check, so both of these examples would work:

2014-06-23 * Test 1
    A         1 AAA @ $1.126
    B         $1.13

2014-06-23 * Test 2
    A         1 AAA @ $1.126
    B         $1.12

I guess these are actually two separate questions: how should ledger do
rounding if nothing is specified for B:
2014-06-23 * Test 2
    A         1 AAA @ $1.126
    B
Is B $1.13 or $1.12?

And the second: what should ledger accept for the balancing check if
the user specifies a value as B (i.e. so either of the example above
is accepted).

Anyway, we could have something like (and possibly another directive
to say how "permissive" balancing should be):

commodity EUR
    precision 2
    rounding truncate

While precision is probably the same for one currency regardless of
the account, you should be able to specify the type of rounding for
each account (one bank may round properly while another may always
truncate):

account Foo
   rounding truncate

account Bar
   rounding round

In addition to these commodity/account directives, it might also be useful
to specify rounding on the command line to force it once.  I'm not sure if
there's a use case for this.

Open questions
--------------

* Do we actually need to specify the precision?  After all, ledger
keeps track of the precision you use in your input data (and by
default uses that for the display precision).  Maybe we should just
use that precision as the precision for rounding if rounding is
enabled.  In other words, we wouldn't need the "precision" directive
-- "rounding" would be enough.  I cannot think of any use cases where
this wouldn't work, but maybe I'm missing something. (See below for
rounding to 1000s)

* If we do implement a "precision" directive and someone says
"precision 2", what would happen if someone then wrote a value like
10.123.  Should this be rejected or should it override precision?

* What about currencies like CAN and AUD which have phased out the
cent coin?  The smallest coin is 5c.  I *assume*, bank and investment
accounts still show single cents, but I'm not sure (Martin Blais, can
you comment?).

* Does rounding only happen after the decimal point or are there
currencies where 600 would be rounded to 1000?  (I'm thinking of
currencies where one million or billion is worth $1 or so).  The
proposal assumes that rounding only happens after the decimal point
but maybe this assumption is wrong.

* Does anyone know how banks/currency exchanges/investment companies
actually do rounding in real life?  Do they round correctly or do some
truncate?

* How does Bitcoin work?  I know Bitcoin is divisible.  So would they
want no rounding?
  commodity BTC
      rounding off  (or precision 0)
Are there use cases where Bitcoin would be rounded?

* How do other accounting packages handle rounding?

* In the proposal above, I've assumed that users can specify rounding
(e.g. "truncate") as a directive.  Another way to implement it would
be to allow users to specify a function that does the rounding: this
would be more powerful, but otoh, it makes things more difficult to
use (and probably to implement).  And with a function, how do we know
what should balance?

-- 
Martin Michlmayr
http://www.cyrius.com/

-- 

--- 
You received this message because you are subscribed to the Google Groups 
"Ledger" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to