views:

720

answers:

7

I have read a couple of articles about the perils of certain data types being used to store monetary amounts. Unfortunately, some of the concepts are not in my comfort zone.

Having read these articles, what are the best practises and recommendations for working with money in C#? Should I use a certain data type for a small amount and a different one for a larger amount? Also, I am based in the UK which means we use , (e.g. £4,000, whereas other cultures represent the same amount differently).

Thanks

+2  A: 

You should not use floating point numbers because of rounding errors. The decimal type should suit you.

Ed Swangren
+2  A: 

I use a value object to hold both the amount (as a decimal) and the currency (I use a string for it). This allows to work with different currencies simultaneously.

Avoid using floating point operations. Use integral data types (preferably large ones) to store the amounts in cents and only show it as a (pseudo) floating point value on screen.

EDIT: It seems like decimal is not fixed point after all. Anyways, it is the recommend data type for money in .NET.

Martinho Fernandes
+12  A: 

Decimal is the most sensible type for monetary amounts.

Decimal is a floating point base 10 numeric type with 28+ decimal digits of precision. Using Decimal, you will have fewer surprises than you will using the base 2 Double type.

Double uses half as much memory as Decimal and Double will be much faster because of the CPU hardware for many common floating point operations, but it cannot represent most base 10 fractions (such as 1.05) accurately and has a less accurate 15+ decimal digits of precision. Double does have the advantage of more range (it can represent larger and smaller numbers) which can come in handy for some computations, particularly some statistics computations.

One answer to your question states that Decimal is fixed point with 4 decimal digits. This is not the case. If you doubt this, notice that the following line of code yields 0.0000000001:

Console.WriteLine("number={0}", 1m / 10000000000m);

Having said all of that, it is interesting to note that the most widely used software in the world for working with monetary amounts, Microsoft Excel, uses doubles. Of course, they have to jump through a lot of hoops to make it work well, and it still leaves something to be desired. Try these two formulas in Excel:

  • =1-0.9-0.1
  • =(1-0.9-0.1)

The first yields 0, the second yields ~-2.77e-17. Excel actually massages the numbers when adding and subtracting numbers in some cases, but not in all cases.

Joe Erickson
Neat Excel example!
Jess
Decimal for money is good. But ratios of money should still be double (e.g. rates of interest).
Richard
+4  A: 

Martin Fowler recommends using a Money class. See the link for the rationale. There are a number of implementations of his idea out there, or you could write your own. Fowler's own implementation is in Java, so he uses a class. The C# versions I have seen use a struct, which seems sensible.

dangph
A: 

My recommendation would be to use Decimal, as is recommended by others if division is required. For simple tally application, I would recommend an Integer type. For both types, I would always work on the lowest monetary denomination. (ie. cents in Canada / US)

I do like the theory of Fowler's Money call added by @dangph.

tylermac
A: 

Whatever you do, make sure that you understand how currency amounts are handled in each tier of your application.

I once spent week tracking down a 1¢ error because SQLServer and .Net use different ways of rounding currencies, and the application wasn't consistent in how it handled certain types of calculations - sometimes they were done in SQL, sometimes in .net. Look into "bankers' rounding" if you're interested.

There are also issues related to formatting currencies - not sure if you have to deal with non-UK amounts, other languages/cultures, etc but that'll add another level of complexity.

chris
Another issue to watch out for is that SQL Server stores DATETIME with a precision of 3.33 milliseconds (0. 00333 seconds). I once got bit by this as it was not sufficient for my purposes.
ahsteele
A: 

As you indicated in your question aside from using an appropriate data type how well your program will handle currency conversions is important. This issue of course if not exclusive to currency. Jeff Atwood did a great post summarizing the virtues of performing The Turkey Test.

ahsteele