0

I am composing some core types for an application which will deal with money. A simple money class may look like this:

public class Money 
{
    private decimal RawAmount { get; set; }
    public Currency Currency { get; set; }
    public decimal Amount
    {
        get
        {
            return Math.Round(RawAmount, 2);
        }
        set
        {
            RawAmount = value;
        }
    }
}

Initially I thought this seemed like a good idea, until I have to compose more complex values based on several Money.Amounts.

The issue is obviously that the amount of each Money instance will be rounded and could therefore trim the odd value off.

So my question is this, what is the recommended way to work with money where you expect a rounded output?

Specifically I'm wondering:

  1. Is there a way to avoid needing to round all my values in the UI they are displayed it, could I do it better via some formatter class perhaps?

  2. If I'm working with complex calculations, what's the best way to generate test values. E.g. If I use a decimal for my Amount property and want to test the output of 10/3, how would you recommend I check the result without just re-phrasing the calculation?

  3. Is rounding each composite Amount is the way to go? and just accept the inaccuracy? does anyone have experience of apps dealing with financials (note this is not a serious banking app or anything so perhaps fractions of pence may not be too much of a problem.)

Any input greatly appreciated.

dougajmcdonald
  • 19,231
  • 12
  • 56
  • 89
  • Be aware that decimal uses bankers rounding. – garryp May 15 '15 at 15:09
  • Expose the rounded and unrounded value. When to round and when not can't be an implementation detail of this class. Its in the responsability of the classes user. – Ralf May 15 '15 at 15:10
  • 3
    You can't round money. Money must always go somewhere. I assume you've seen the opening scenes of Superman II? – Bathsheba May 15 '15 at 15:10
  • 2
    Does `RawAmount` even make sense? In GBP, for instance, there would is no such monetary value as £10.001, it is £10.00. Your calculations would have to take care of this - e.g. for your 10/3, someone gets £3.34 and the other two £3.33. How this is allocated is your decision. – Charles Mager May 15 '15 at 15:11
  • Rounding at the very last minute will keep accuracy. If possible **keep** the un-rounded values but **display** the rounded values. Add tolerances to deal with rounding issue as you will likely have some at some point i.e. doing complex calculations...... or simply rounding 1 by 3 for that matter! – Liam May 15 '15 at 15:12
  • @Liam I'm not sure it helps accuracy, you'll lose money. Take the 10/3 example, if you had 3 Money with £3.3333333 then they all round to £3.33. You've lost a penny somewhere as that adds up to £9.99. I think you need to round as you go if you want to start and end with the same total amounts. – Charles Mager May 15 '15 at 15:14
  • If you have to show the 3.33 somewhere (an invoice for example) the sum of 3 times the value should be 9.99 even if you loose a penny. The cost of the trouble you get if the sum of individual values doesn't fit the shown sum will certainly be more costly. And if you think about an eqivalent 11/3 example you will win a penny. – Ralf May 15 '15 at 15:20
  • You will always loose pennies, most companies/financial packages allow for this. You cannot divide £1 by 3..that's just a fact, you have to deal with that. It's impossible to bank £0.3333 so therefore it doesn't exist. So you make a decision, round up (typically) or down. Or you could try and allocate the left over penny soemwhere else, but this isn't always possible. When I worked for a financial company we had tolerances of xp. Anything out by that amount was fine – Liam May 15 '15 at 15:21
  • TBH you could write books on this subject. It can get very complex and subjective so I don't believe this question is answerable. Talk to your finance department and ask them how they want you to account for it. – Liam May 15 '15 at 15:23
  • 1
    This is question for accountants, not programmers. This has to be in the specification of the project. – Agent_L May 15 '15 at 15:34
  • Related: http://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency –  May 15 '15 at 15:37

2 Answers2

2

We are making app working with money (even foreign currencies - but not banking app) right now... (btw sorry for my engrish - and sorry for lack of "public" code, i am not sure if i can just copy & paste anything from my work)

The best approach is simply "ask the client". Its not your business to know, what the internal regulations of every clients are.

I know you dont ask about foreign currencies, so you can skip to 4) and 5), but for context... :)

1) Internally, we use only one currency.

2) Every job is under contract with fixed rate (may be 1:1 if it is our internal currency -- Becouse no trader want to speculate with exchange rate, so they have fixed rate under signed contract & job, for next job, they can make new contract with different rate)

3) Any money can be traced back to job & contract & rate.

4) Rounding can be set for whole app (one, two ... n decimal places) Somebody want two decimal places but one client want to see 5 decimals)

5) We are rounding ONLY for gui&reports, even then, if you click on decimal box, you will see unrounded values. No one care about 0.1 CZK, but may care about 0.01 € (which is 0.2 CZK... Strange, but people are strange :) )

Jan 'splite' K.
  • 1,667
  • 2
  • 27
  • 34
  • Exactly. Programmer needs to ask accountants on this. – Agent_L May 15 '15 at 15:32
  • I think this is a sensible answer, I agree that the clients requirements are key. I am in this case my own client so you could argue I'm making a mountain out of a molehill, but I'm trying to get feel for the thought processes others use when approaching this type of issue. – dougajmcdonald May 15 '15 at 23:00
2

Is there a way to avoid needing to round all my values in the UI they are displayed it, could I do it better via some formatter class perhaps?

Yes:

public string ToString(string format, IFormatProvider provider)
{
    return Currency + " " + RawAmount.ToString(format, provider);
}
public string ToString(string format)
{
    return ToString(format, NumberFormatInfo.CurrentInfo);
}
public string ToString(IFormatProvider provider)
{
    return ToString("0.00", provider);
}
public override string ToString()
{
    return ToString(provider);
}

You can go much beyond this, but now just how such an object is presented is up to the code presenting it (but with a two-decimal place default).

If I'm working with complex calculations, what's the best way to generate test values. E.g. If I use a decimal for my Amount property and want to test the output of 10/3, how would you recommend I check the result without just re-phrasing the calculation?

Rephrasing the calculation outside of the class. Assert.Equal(10/3, (new Amount(10) / 3).Value);

Is rounding each composite Amount is the way to go? and just accept the inaccuracy? does anyone have experience of apps dealing with financials (note this is not a serious banking app or anything so perhaps fractions of pence may not be too much of a problem.)

This depends on the reason it's being used. If you are tallying a bunch of things presented to a user at a given price, then you are going to have to add the rounded values up, because you said you were charging them USD 12.03 and USD 3.12 and so they expect a total of USD 15.15 as that's the sum of what they were told about, not USD 15.16 because that's the sum of 12.034 and 3.1246 when rounded from USD 15.1586.

If you are tallying a bunch of small items including perhaps currency exchange etc. you are going to have to keep all of those small fractions until nearer the end.

You are also going to have to have a decision about whether to apply Bankers Rounding (the default, and the one to go for most of the time) or school-child rounding (the sort expected by some customers, should the details prior to the rounding be visible to them), or always round down (has been legally insisted upon during some currency-change transition periods).

Really, for any given financial process, which sort of rounding, to what degree, and when it is applied is one of the things that need to be recorded in the rules for that process. It is not something that you can generalise about in a general-purpose class.

If you were going to have such a class, then it would make more sense to have this as an operation on the objects themselves:

public Money Round(int decimals, MidpointRounding mode)
{
    return new Money(Currency, Amount.Round(decimals, mode));
}
public Money Round(MidpointRounding mode)
{
    return Round(2, mode);
}
public Money Round(int decimals)
{
    return Round(decimals, MidpointRounding.ToEven);
}
public Money Round()
{
    return Round(2);
}

There; the user code can decide when to round, and also how to round but with sensible defaults.

I'd also recommend making such a class a struct and definitely to make it immutable.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • Excellent, my one counter point would be to the response about test values. Say I provide a calculator,I should be validating function(input) == output. Rather than function(input) == function(input). A simple example it might not seem like much of a difference, but take an example where I have a method called 'doToughMaths(1,1200,897)' and my test method has to assert that Math.Round((1/1200),2) ^ 897 * 1 - 1200 + 897/ (897*(1200/1)) == doToughMaths(1,1200,897).Result, (contrived I know) it seems like I'm need to replicate complex logic in my tests which could be amazingly brittle. – dougajmcdonald May 15 '15 at 22:57