Making Money with C#

By plind | C#
24 Jul 2008

Introduction

So my next .NET project started and sure enough, there was the requirement again to support international customers. Now, most of the systems I work on have quite a bit of financial functionality in them and therefore the handling of money is an important aspect. I have found that most developers like to take the simple approach to handling money, supporting only a single currency by solely using decimal type to store an amount and not explicitly naming the currency. Instead the currency is first made explicit when it is presented on a user interface, adding a currency symbol as it is stored in the “active culture info” (which generally means: the server’s culture settings).

I don’t know about you, but if some system administered some amount of my money, I would be very worried if it simply stored: 1,000,000. Especially if my money were in Euro and the system would be a web system running in the US. Chances are, the system’s implicit currency is USD and the current difference between USD 1,000,000 and EUR 1,000,000 is currently about USD 476,500, which is enough to give me quite big headache! ;)

I think we can all agree that when there is a hint that our system might be confronted with cross-border business, it is a very good architectural practice to store money not simply as a number, but also with the appropriate unit. Martin Fowler has already suggested this long ago (1996) in his book Analysis Patterns, where he discussed the Money analysis pattern to be a specialized version of the Quantity analysis pattern. In his Patterns of Enterprise Application Architecture he revisits the Money pattern with several code samples. So the most ground breaking work has been done long ago already.

What is there now?

There are different angles to look at money representations:

  1. From an administrative point of view (as discussed above), recording and computing monetary amounts along with the correct currency;
  2. From a presentation point of view, describing the way a monetary value is displayed;
  3. From a geographical point of view, listing what currencies are used in what countries.

In .NET, Microsoft has supplied us with facilities for the last two angles with the CultureInfo and NumberInfo classes. Amazingly, the first angle is completely ignored, even though that is what’s needed for solid financial business functionality.

Despite of the work of people like Martin Fowler, it seems there are not too many people who care about the administration aspect of money in the .NET space. At least, that’s the feeling I get when I set out to search for C# Money implementations. That yields one fairly decent implementation by Jason Hunt. Unfortunately, Jason relies on the presentation-related CultureInfo class to store currency information. I think that it is unnecessarily complicated, since it forces me to instantiate “1,000 of that currency they use in the US” instead of instantiating “1,000 USD”.

What do we need?

As far as I can see now, we need to be able to do the following with a money representation:

  • Store monetary amounts as an amount together its currency code.
  • Get a string representation of the monetary amount, dependent on how the user is accustomed to see currencies and numbers. Note that this means that a even though USD is an American currency, a Dutch user would not expect to see the American formatting rules applied to a USD amount, but instead the Dutch formatting rules.

The design

Basic properties

The basics of the Money class are straightforward: we need a property to hold the monetary amount and one to hold the currency of that amount.

public sealed class Money : IEquatable<Money>, IComparable, IComparable<Money>
{
    private CurrencyCodeKind _currencyCode;
	private decimal _amount;

The CurencyCodeKind type shown in the listing above is an enumeration. The names of the enumerated values are the ISO 4217 currency codes. This allows us to store any monetary amount, independent of culture or presentation standards. For the amount itself, we use a decimal type, generally regarded as best practice for storing monetary amounts. This can hold values from -10 Sextillion to 79 Sextillion (Sextillion = 10^15 Billion) (http://www.statman.info/conversions/number_scales.html), assuming we use a maximum of 3 significant decimal digits. In short: this is more than enough… even for Zimbabwe, that currently has bills up 100 Billion Zimbabwe Dollars!

Here’s a snippet of the CurrencyCodeKind enumeration:

public enum CurrencyCodeKind
{
    AED = 784,
    AFN = 971,
    ALL = 8,
    AMD = 51,
    ANG = 532,
    ...
}

Note that for the indexing values, we use the related ISO 4217 numeric codes. The reason not to use the default incremental numbering scheme for this enumeration, is rather practical: when storing money objects to a relational database, the numeric value of the currency code will get stored. If we inspect a table with Money objects later, it is very convenient to have these ISO 4217 numeric codes show up instead of application-specific numbers.

The same argument holds for the use of the decimal type to store the actual amount: Fowler has shown implementation examples just using (big) integers, which means that in order to know what the right amount is, you’ll always need the application to position the decimal separator correctly. I find that a little too brittle for information that’s that important, hence I choose to go with a decimal datatype that also has an exactly matching representation on database level.

The inner workings

When working with a Money object, we’ll eventually need to know more about its currency than just its ISO code. More specifically we’ll need to know:

  1. The number of significant decimal digits, needed for correct calculations and rounding
  2. The currency symbol for display purposes
  3. The rounding type, needed for rounding of calculation results (discussed later)
  4. The name of the currency (maybe not necessary, but still convenient

I’ve created a Currency class to hold this information for a currency. Here are its properties:

internal class Currency
{
    public CurrencyCodeKind CurrencyCode { get; private set; }
    public string EnglishName { get; private set; }
    public string Symbol { get; private set; }
    public int SignificantDecimalDigits { get; private set; }
    public CurrencyRoundingKind RoundingType { get; private set; }

These Currency class properties are vital for the logic in the Money class. Therefore it is convenient to have a Money object hold a reference to the Currency object that is represented by the Money object’s currency code. So let’s have some additional private properties on the Money class to achieve this:

public sealed class Money : IEquatable<Money>, IComparable, IComparable<Money>
{
    #region Properties
    public CurrencyCodeKind CurrencyCode { get; set; }
    public decimal Amount { get; set; }
    private Currency _currency;
    private CurrencyRepository _currencyRepository = new CurrencyRepository();
    private static int[] _cents = new int[] { 1, 10, 100, 1000 };
    #endregion

In order to keep the CurrencyCode and _currency in sync, the currency object reference is set in all places where the currency code can be set, which is in de constructors:

// Initialize a new money with an explicit currency code
public Money(CurrencyCodeKind currencyCode, decimal amount)
{
    if (_currencyRepository.Exists(currencyCode))
    {
        CurrencyCode = currencyCode;
        Amount = amount;
        _currency = _currencyRepository.Get(CurrencyCode);
    }
    else
    {
        throw new InvalidOperationException("Currency code unknown.");
    }
}

// Initialize a new money with the currency code
// held in the passed in CultureInfo.
public Money(CultureInfo cultureInfo, decimal amount)
{
    CurrencyCodeKind currencyCode;
    try
    {
        currencyCode = (CurrencyCodeKind)Enum.Parse(typeof(CurrencyCodeKind), (new RegionInfo(cultureInfo.LCID)).ISOCurrencySymbol);
    }
    catch (Exception)
    {
        throw new InvalidOperationException("Currency code unknown.");
    }
    if (_currencyRepository.Exists(currencyCode))
    {
        CurrencyCode = currencyCode;
        Amount = amount;
        _currency = _currencyRepository.Get(CurrencyCode);
    }
    else
    {
        throw new InvalidOperationException("Currency code unknown.");
    }
}

	// Initialize a new money with the currency code of the 
// runtime enviroment's current regional settings.
public Money(decimal amount)
{
    CurrencyCodeKind currencyCode;
    try
    {
        currencyCode = (CurrencyCodeKind)Enum.Parse(typeof(CurrencyCodeKind), RegionInfo.CurrentRegion.ISOCurrencySymbol);
    }
    catch (Exception)
    {
        throw new InvalidOperationException("Currency code unknown.");
    }
    if (_currencyRepository.Exists(currencyCode))
    {
        CurrencyCode = currencyCode;
        Amount = amount;
        _currency = _currencyRepository.Get(CurrencyCode);
    }
    else
    {
        throw new InvalidOperationException("Currency code unknown.");
    }
}

As you can see, the constuctors use the _currencyRepository to find the currency object matching the current currency code. The currency repository is a singleton, being nothing more than a static in memory store of all possible currency objects. Here’s a snippet:

internal class CurrencyRepository
{
    // List of all currencies with their properties.
    static private Dictionary<CurrencyCodeKind, Currency> _currencyDictionary =
        new Dictionary<CurrencyCodeKind, Currency>()
        {
            {CurrencyCodeKind.AED, new Currency(CurrencyCodeKind.AED, "United Arab Emirates dirham", "¤", 2)},
            {CurrencyCodeKind.AFN, new Currency(CurrencyCodeKind.AFN, "Afghani", "¤", 2)},
            {CurrencyCodeKind.ALL, new Currency(CurrencyCodeKind.ALL, "Lek", "¤", 2)},
            {CurrencyCodeKind.AMD, new Currency(CurrencyCodeKind.AMD, "Armenian dram", "¤", 2)},
            {CurrencyCodeKind.ANG, new Currency(CurrencyCodeKind.ANG, "Netherlands Antillean guilder", "ƒ", 2)},
            ...

The reasons all currency objects are hard coded are:

  1. the currency list hardly ever changes;
  2. when a new currency is introduced, it needs to be added to the CurrencyCodeKind enumeration anyway, thus requiring a new system build; it is only natural to then also update the hard coded currency object list;
  3. you don’t really want this core configuration of the way you handle money to be stored in an easy to corrupt/misuse configuration file.

One additional thing to note is that I quickly found out that the default character set does not hold all currency symbols. Therefore a lot of currency symbols are represented by the generic currency sign. A future enhancement will be to add the Unicode of the symbol to the properties of the Currency class.

Operators

The implementation of most arithmetic operators are straightforward; here’s the >, >=, ==, <=, <, !=, +, and – operators:

public static bool operator >(Money first, Money second)
{
    AssertSameCurrency(first, second);
    return first.Amount > second.Amount;
}

public static bool operator >=(Money first, Money second)
{
    AssertSameCurrency(first, second);
    return first.Amount >= second.Amount;
}

public static bool operator <=(Money first, Money second)
{
    AssertSameCurrency(first, second);
    return first.Amount <= second.Amount;
}

public static bool operator <(Money first, Money second)
{
    AssertSameCurrency(first, second);
    return first.Amount < second.Amount;
}

public static Money operator +(Money first, Money second)
{
    AssertSameCurrency(first, second);
    return new Money(first.CurrencyCode, first.Amount + second.Amount);
}

public static Money operator -(Money first, Money second)
{
    AssertSameCurrency(first, second);
    return new Money(first.CurrencyCode, first.Amount - second.Amount);
}

public static bool operator ==(Money first, Money second)
{
    if (object.ReferenceEquals(first, second)) return true;
    if (object.ReferenceEquals(first, null) || object.ReferenceEquals(second, null)) return false;
    return (first.CurrencyCode == second.CurrencyCode && first.Amount == second.Amount);
}

public static bool operator !=(Money first, Money second)
{
    return !first.Equals(second);
}

Where it starts getting tricky is with multiplication and division. This is because of the rounding that needs to take place when the result of the operation exceeds the number of significant decimal digits allowed for that currency. To tackle this, I’ve implemented the WithRoundedAmount() operation for both the * and / operators.

public static Money operator *(Money money, decimal value)
{
    if (money == null) throw new ArgumentNullException("money");
    return new Money(money.CurrencyCode, money.Amount * value).WithRoundedAmount();
}

public static Money operator /(Money money, decimal value)
{
    if (money == null) throw new ArgumentNullException("money");
    return new Money(money.CurrencyCode, money.Amount / value).WithRoundedAmount();
}

internal Money WithRoundedAmount()
{
    switch (_currency.RoundingType)
    {
        case CurrencyRoundingKind.Argentinian:
            decimal insignificantDecimals1 = Amount * CentFactor() - (long)Math.Truncate(Amount * CentFactor());
            if (insignificantDecimals1 < 0.3m)
            {
                return this.WithTruncatedAmount();
            }
            else
            {
                if (insignificantDecimals1 > 0.7m)
                {
                    Amount = decimal.Round(Amount, _currency.SignificantDecimalDigits, MidpointRounding.AwayFromZero);
                    return this;
                }
                else
                {
                    Amount = ((long)Math.Truncate(Amount * CentFactor()) + 0.5m) / CentFactor();
                    return this;
                }
            }
        case CurrencyRoundingKind.Swiss:
            decimal insignificantDecimals2 = Amount * CentFactor() - (long)Math.Truncate(Amount * CentFactor());
            if (insignificantDecimals2 < 0.26m)
            {
                return this.WithTruncatedAmount();
            }
            else
            {
                if (insignificantDecimals2 > 0.75m)
                {
                    Amount = decimal.Round(Amount, _currency.SignificantDecimalDigits, MidpointRounding.AwayFromZero);
                    return this;
                }
                else
                {
                    Amount = ((long)Math.Truncate(Amount * CentFactor()) + 0.5m) / CentFactor();
                    return this;
                }
            }
        default:
            Amount = decimal.Round(Amount, _currency.SignificantDecimalDigits, MidpointRounding.AwayFromZero);
            return this;
    }
}

As you can see, the WithRoundedAmount() is not as straightforward as expected, as is described in this source. The default (and much expected) behaviour for rounding is:

  • If the last digit is less than 5, drop it, otherwise,
  • If the last digit is equal to or above 5, add one to the previous digit and drop the last digit.

Apparently, Argentina and Switzerland have alternate rounding schemes, as described in the above mentioned source. To accommodate for these, I have added a CurrencyRoundingKind enumeration to indicate when to use what rounding scheme.

internal enum CurrencyRoundingKind
{
    AwayFromZero,
    Swiss,
    Argentinian
}

For now, I’ve associated that scheme to the Argentinian and Swiss currencies, but I’m not sure if that’s entirely correct. In other words: do Argentina and Switzerland only apply these rounding schemes to their own currencies, or also to all foreign currencies they perform calculations on? For now, I choose to leave this alone, since I currently don’t need to accommodate for this in my projects. But if someone can enlighten me… by all means!

Allocation

With rounding comes loss of information. If you use the division operator to calculate how to divide $0.05 amongst 3 people, you’ll have $0.01 to give to each person. $0.02 is lost due to rounding. Everyone knows about those exploit stories of programmers in the early days who took advantage of this loss due to rounding. We’ve wizened up since then, and Fowler introduced an Allocate algorithm to take care of situations like this. It’s a simple algorithm that actually plays the “rings on a sticks” game:

Say you have 5 rings and 3 sticks to divide them eenly over, you’ll get the following:

MoneyAllocate

With the Allocate algorithm, you’ll ask to Allocate $0.05 over 3 parts: (new Money(CurrencyCodeKind.USD, 0.05m)).Allocate(3). The result is an array with 3 elements, two of which contain $0.02 and one that contains $0.01.

public Money[] Allocate(int n)
{
    Money lowResult = new Money(CurrencyCode, Amount / n).WithTruncatedAmount();
    Money highResult = lowResult.Amount + new Money(this.CurrencyCode, 1.0m/CentFactor());
    Money[] results = new Money[n];
    int remainder = (int)((Amount * CentFactor()) % n);
    for (int i = 0; i < remainder; i++) results[i] = highResult;
    for (int i = remainder; i < n; i++) results[i] = lowResult;
    return results;
}

I’ve also included Fowler’s enhanced version of the Allocate algorithm, which allows to user to specify an array of allocation ratios, e.g. allocating $1 with a ratio of {2, 3, 3}, can result in an array with $0.25, $0.37 and $0.38. Note that I said can, because the algorithm does not assert how the division takes place exactly. It only asserts that:

  1. the remainder is evenly distributed over the parts
  2. the sum of the resulting amounts is equal to the divided amount.
public Money[] Allocate(int[] ratios)
{
    decimal total = 0m;
    for (int i = 0; i < ratios.Length; i++) total += ratios[i];
    Money remainder = this.Copy();
    Money[] results = new Money[ratios.Length];
    // First distribute the truncated amounts over all parts
    for (int i = 0; i < results.Length; i++) {
        results[i] = new Money(CurrencyCode, (this.Amount * ratios[i]) / total).WithTruncatedAmount();
        remainder.Subtract(results[i]);
    }
    // Now determine how many cents are left
    long centsLeftToDivide = (long)(remainder.Amount / (1.0m / CentFactor()));
    // Distribute those remaining cents over the different parts
    for (int i = 0; i < centsLeftToDivide; i++) {
        results[i].Add(new Money(this.CurrencyCode, 1.0m / CentFactor()));
    }
    return results;
}

So in order to apply “rounding safe divisions”, be sure to use the Allocate operation.

Formatting for displaying

Lastly, we’ll need some formatting on a Money object for it to present itself neatly to a user. This formatting is included in the 3 variants of the ToString() operation:

  1. with no parameters (generally not recommended); the number formatting rules are taken from the OS’ current culture info and the three letter currency code is used as the currency indicator;
  2. with a CultureInfo object as parameter (recommended); the number formatting rules are derived from the passed in CultureInfo object and the three letter currency code is used as the currency indicator;
  3. with a CultureInfo object as parameter and an indicator whether or not to use the currency symbol instead of the three letter currency code as currency indicator (equally recommended); the number formatting rules are derived from the passed in CultureInfo object.
public string ToString()
{
    return Amount.ToString("C", GetCurrencyFormatter(CultureInfo.CurrentCulture, false));
}

public string ToString(CultureInfo cultureInfo)
{
    return Amount.ToString("C", GetCurrencyFormatter(cultureInfo, false));
}

public string ToString(CultureInfo cultureInfo, bool useSymbol)
{
    return Amount.ToString("C", GetCurrencyFormatter(cultureInfo, useSymbol));
}

A central role is played by the GetCurrencyFormatter operation, which returns a NumberFormatInfo object that contains the desired currency indicator as well as an adapter currency formatting pattern that always makes sure there is a space between the currency indicator and the amount, if the currency indicator is a three letter currency code.

private NumberFormatInfo GetCurrencyFormatter(CultureInfo cultureInfo, bool useSymbol)
{
    // Get all the basics from the passed in culture info (not before it is 
    // guaranteed to use global settings)
    NumberFormatInfo LocalNumberFormatter = (NumberFormatInfo)this.GetGlobalCultureInfo(cultureInfo).NumberFormat.Clone();
    // Overwrite the culture settings with the specifics of the current currency
    Currency currency = _currencyRepository.Get(CurrencyCode);
    LocalNumberFormatter.CurrencyDecimalDigits = currency.SignificantDecimalDigits;
    if (useSymbol)
    {
        LocalNumberFormatter.CurrencySymbol = currency.Symbol;
    }
    else
    {
        LocalNumberFormatter.CurrencySymbol = Enum.GetName(typeof(CurrencyCodeKind), CurrencyCode);
        // If we use the ISO code, then we need to make sure the patterns always include 
        // a space between the ISO code and the amount.
        if (LocalNumberFormatter.CurrencyPositivePattern <= 1)
        {
            LocalNumberFormatter.CurrencyPositivePattern = LocalNumberFormatter.CurrencyPositivePattern + 2;
        }
        switch (LocalNumberFormatter.CurrencyNegativePattern)
        {
            case 0: // ($n)
                LocalNumberFormatter.CurrencyNegativePattern = 14;
                break;
            case 1: // -$n
                LocalNumberFormatter.CurrencyNegativePattern = 9;
                break;
            case 2: // $-n
                LocalNumberFormatter.CurrencyNegativePattern = 12;
                break;
            case 3: // $n-
                LocalNumberFormatter.CurrencyNegativePattern = 11;
                break;
            case 4: // (n$)
                LocalNumberFormatter.CurrencyNegativePattern = 15;
                break;
            case 5: // -n$
                LocalNumberFormatter.CurrencyNegativePattern = 8;
                break;
            case 6: // n-$
                LocalNumberFormatter.CurrencyNegativePattern = 13;
                break;
            case 7: // n$-
                LocalNumberFormatter.CurrencyNegativePattern = 10;
                break;
            default:
                break;
        }
    }
    return LocalNumberFormatter;
}

private CultureInfo GetGlobalCultureInfo(CultureInfo cultureInfo)
{
    if (!cultureInfo.UseUserOverride)
    {
        //The passed in culture info is already in "global mode" 
        return cultureInfo;
    }
    else
    {
        // The passed in culture info is set to use OS specific 
        // culture settings; this should be changed.
        return new CultureInfo(cultureInfo.Name, false);
    }
}

Wrapping it up

Jee wizz… that’s a lot of explainin’, Batman! Yeah, well… there’s still a bit more in the actual code that I have not covered in detail, but this story should have you covered for regular use. If -after this whole epistle- you’re still interested in the actual code, you can get your copy here:

Download the C# Money implementation

Oh yeah… do note that this all is built in Visual Studio 2008 Professional, so you might run into some problems if you try to use it directly in an older version of Visual Studio. Sorry for that!

Gimme feedback!

This is only my first go at a decent Money implementation. The unit test set can use some extension and cleaning up; I’m aware of that. Furthermore, the implementation will certainly get updated based on requirements within projects I use it in. But also, I hope to get feedback from you with suggestions, new insights, updates of rules (e.g. the rounding schemes), updates of code, etc. Especially I’d like feedback from foreign readers who find that this implementation needs adaptation to work correctly in their country, with their currency. Use this implementation freely and let me know it can be improved!

5 Comments

  1. codekaizen says:

    I feel like one of those scientists in the 19th century who simultaneously discovered something that another scientist discovered. I just authored a money type using the same basic pattern. I published it on the same day, here: http://www.codeplex.com/MoneyType. There are a few differences which are notable – I’ve devolved the responsibility for money allocation to another class, made Currency a bit more functional to take advantage of built-in string formatting, and used scaled-integer storage (as did Fowler and Foemmel) to avoid the problems with using floating-point types such as System.Decimal.

  2. Pascal Lindelauf says:

    Haha! What an insane coincidence this is! :D

    I’m going to check out your solution very soon and get back to you! One remark: de System.Decimal is not a floating point type. The latter obviously suffers from imprecisions, but the Decimal type is precise and therefore is suited to be used for currency amounts.

  3. codekaizen says:

    I know, it is an astounding coincidence! I was thinking all along, “Wow, I can’t believe no one has tackled this problem comprehensively yet.” I’ll bet you had just the same thoughts!

    System.Decimal is a floating-point. It’s a common misconception that it isn’t due to the fact that it has so many bits of precision. From the MSDN docs (http://msdn.microsoft.com/en-us/library/system.decimal.aspx):

    A decimal number is a floating-point value that consists of a sign, a numeric value where each digit in the value ranges from 0 to 9, and a scaling factor that indicates the position of a floating decimal point that separates the integral and fractional parts of the numeric value.

    So, Decimal is not immune to base-10 fractional numeric representation issues. Also from the docs:

    The Decimal type does not eliminate the need for rounding. Rather, it minimizes errors due to rounding. For example, the following code produces a result of 0.9999999999999999999999999999 rather than 1.

  4. Pascal Lindelauf says:

    I indeed had the exact same thought. I have tackled it partially before, but it never materialized into a comprehensive solution, until recently. And yesterday the stars aligned when I finally wrote the last couple of lines on that whole story and managed to publish it onto my blog at the same time as you. Amazing!

    Darn, I know I should have checked the docs on the floating-point thing! ;) Thanks for pointing that out. I’m going to look into that more closely. I still like the idea of having matching types on data storage level and code level. On the other hand, integer calculations are sure to be faster than the decimal calculations (as they are 10-based). But first and foremost the solution should be “safe” (whatever that means *exactly*). Interestingly enough, judging by the amount of articles on the theory on this, there still seems to be a bit of grey area around this.

    Anyway, I downloaded your MoneyType and I hope to get around reviewing it this weekend. I’ll post my comments on your site.

  5. JH says:

    Thanks for the referral. Just a note, the reason I left the CultureInfo reference in my implementation was that I didn’t want to have to maintain a CurrencyCodeKind enumeration as countries were added/removed. I also wanted to leave the method of how to properly display the currency (“$100.00 CAD” english canadian, “100,00 $ CAD” french canadian – reference: http://www.sterlingdata.com/canada.htm) to the OS.