Add your own alternative version
Stats
118.9K views 2.1K downloads 138 bookmarked
Posted
30 Jul 2008

Comments and Discussions



Isn't there logic error in this code? Code is the same for AwayFromZero and TowardZero
case MidpointRoundingRule.AwayFromZero:
{
var sign = Math.Sign(fraction);
fraction = Math.Abs(fraction);
fraction = Math.Floor(fraction + 0.5M);
fraction *= sign;
break;
}
case MidpointRoundingRule.TowardZero:
{
var sign = Math.Sign(fraction);
fraction = Math.Abs(fraction);
fraction = Math.Floor(fraction + 0.5M);
fraction *= sign;
break;
}





"System.Decimal is binary floating point." I understand it to be decimal floating point (likely as densely packed decimal, a.k.a. DPD), not as binary floating point. That's the point of it. Sometimes, people seem to automatically equate "floating point" with binary. It isn't always so.
see C# in Depth: Decimal floating point in .NET[^]
Unfortunately, System.Decimal appears to be different from the IEEE 754 definition for decimal floating point. IEEE floating point  Wikipedia, the free encyclopedia[^]
"There is really only one right answer here: fixed base 10." Decimal floating point is being added to C and to C++, though when it will be implemented.... who knows?
an excellent resource: General Decimal Arithmetic[^]
modified 31Mar16 16:16pm.





Hi codekaizen,
the * operator is defined as:
public static Money operator *(Money left, Decimal right)
{
return ((Decimal)left * right);
}
That seems to me misleading, if the currency of left is different from the CurrentCulture Currency. I would propose:
public static Money operator *(Money left, Decimal right)
{
return new Money((Decimal)left * right, left._currency);
}
Same with the / operator.
Felix
modified 10Feb16 9:36am.






I just started on C# and Visual Studio.
Forgive me for asking noob question, but how do I start using this in my solution?
EDIT: Added the Money project to my solution and created a reference to it. Is this the correct approach?
modified 9Jun15 21:09pm.





Nice project. It's a shame Microsoft hasn't added Decimal64 and/or Decimal32 to the BCL. Those would be really useful and efficient for many financial applications.
Then you could create Money64 and Money32.
The problem is that the full decimal type is a real memory hog. So for those of us who build trading applications, for example, we're stuck with using 128 bits to represent market prices like "2013.25".
Sure, one can convert back and forth with integers, assuming that the precision is fixed. But it makes for some unnecessarily noisy code.





Beautiful coding style  nicely commented and a wellthought out bit of coding. Well done!





Hello codekaizen,
If you look at the link to System.Decimal that is in your article, you will see that System.Decimal is not imprecise. From the System.Decimal page under Remarks (emphasis mine):
"The Decimal value type represents decimal numbers ranging from positive 79,228,162,514,264,337,593,543,950,335 to negative 79,228,162,514,264,337,593,543,950,335. The Decimal value type is appropriate for financial calculations requiring large numbers of significant integral and fractional digits and no roundoff errors. The Decimal type does not eliminate the need for rounding. Rather, it minimizes errors due to rounding. ..."
While your code has other interesting aspects, including on how System.Decimal itself could be implemented, saying that System.Decimal has "roundoff or computational error accumulation" problems is inaccurate. System.Single and System.Double, on the other hand are in deed binary floating point numbers and will have those issues.
I hope you get time to clarify this in your article.
Thanks, Jim








This is "the best" money implementation, thank you!





Looks very promising, but seems like there are some loose ends that have not been addressed since this was posted. Do you have an updated version of this? Any plans to mantain it?





I do have a number of updates which I haven't uploaded. I'll see what I can muster, but with a current deadline, I'd be happy for a reminder if nothing appears in a few days.





Reminder: Hi, it has been a week and still, nothing appeared.





Ah! You're right. Thanks for the reminder... I'll do it now!





very useful and should be part of .net framework





Firstly, really enjoyed the article and implementation. Very clean implementation and I dig the interface.
Found one issue around formatting
I did the following test:
Money uk = new Money(5, Currency.Gbp); Console.WriteLine(uk.ToString()); Console.WriteLine(uk.ToString("C", uk.Currency));
And the last line throws an exception. In Currency.GetFormat(...) you are creating a CultureInfo with the currency ID. And that fails with an invalid argument exception (as it should be using a culture id not a ISO currency ID).
I figured there were 2 work arounds:
1. Implement another lookup (similar to _cultureIdLookup) but reverse the key/value (key = Currency code, value = lcid). In the GetFormat() method change: new CultureInfo(_id).NumberFormat > new CultureInfo(_currencyIdToCultureIdLookup[IsoNumericCode]).NumberFormat That seemed to work for the most part. But it seems a bit error prone and nondeterminstic in the way it may format a currency that is used in multiple cultures.
2. Add a default culture id to CurrencyTableEntry and use that in formatting.
I'll probably stick with the first one, as it saves me the manual updating of all the CurrencyTableEntries.
Thanks again, Will





hi i am amit from india studying in the coleege of the engg ....... i am in final year aand i am doing project int c# please guide me ...





Hi Amit,
I'd suggest you might want to consider adding a little bit more detail on your specific needs or questions in your ongoing search. Otherwise it is like going into a library and saying that you need some help from a book.





India, the worlds toilet bowl. Let's flush it already; twice to get the floaters..





My friend Micheal Bolton wrote a virus that took advantage of my company's lack of a clear currency object. It truncated the fractions of a decimal used when doing money calculations into an account we owned when I worked at Initech. We almost got busted though, man that was great





I have heard of one such case... sometime. Did he get arrested or something?





Hi,
Nice article and code. I noticed that several of the MoneyDistributor's Distribute methods throw a NotImplementedException when called. Is the source code just out of date or are you still working on these methods?
Thanks Dan





Yep, it's an evolving work. Feel free to comment at the project's home at CodePlex[^].





Hi,
is there any intention to implement the distributor methods ..(params decimal[] counts)...? It would be very useful?!
Best regards Norbert






Yes, I considered this type. It is featured prominently in the article.





System.Decimal is a binary floatingpoint type System.Decimal is actually a decimal floatingpoint type. It can represent any value that can be written as a decimal number, including a fractional part, without any representation error.
A decimal floatingpoint type is a sum of powers of ten (including negative powers for the fractional part).
A binary floatingpoint number, such as a double, is a sum of powers of two. They can't represent many decimal numbers with absolute precision. For instance, $1.10 represented as a double might come out as 1.1000000000000001.





I will keep using System.Decimal (in VB .net, so also in C#) because has a 96 bit's value and an exponent of 10^38, and as dgph say, it is... decimal. I like the currency handle of your solution...





I made the change you indicated, and it works!
I was afraid I just didn't know what I was doing (again).
Are you going to put Money into the CLR (gac)?
Thanks, Don
p.s. I'll have to wait for codekaizen's changes to be uploaded to Code Project to see what he did.





Don 
It might take a bit of time, since the article got vetted by an editor much faster than my last one, and I can't make changes on my own. I sent off the code.
If you want to have better access to it, check on the MoneyType project at CodePlex[^]. I post my updates there first.





I downloaded your code from CodePlex (Thanks,), created a strong key name, signed the assembly, and installed Money.dll into the GAC.
I then removed my reference to Money (in my Windows forms project), expecting the Money type to be retrieved from the System namespace. However, I am now getting the following error:
The Type or namespace name 'Money' could not be found (are you missing a using directive or an assembly reference?)
What am I doing wrong now?
Thanks again for your help.
Don





You still need to reference the assembly, even though it is in the GAC.





Yes, I tried to 'Add Reference' to my project and looked for 'Money', in the .NET tab, but I don't see anything.
I ran gacutil /l money, and it showed 'money' in the Global Assembly Cache version 1.0.0.0, Culture=neutral, PublicKeyToken=...,ProcessorArchitecture=MSIL. Number of Items = 1.
Thanks for your help.
Don






All 35 tests failed. Here is the xUnit.net Runner error msg from the 1st one:
FAILED: System.Tests.MoneyDistributorTests.DistributeUniformRatioToLastIsCorrect: System.TypeInitializationException : The type initializer for 'System.Currency' threw an exception.  System.ArgumentOutOfRangeException : The value isn't a valid ISO 4217 numeric currency code. Parameter name: isoCurrencyCode Actual value was 8. at System.Currency.FromCurrentCulture() at System.Money..ctor(Decimal value) in E:\Code Project Stuff\MoneyType\Money\Money.cs:line 176 at System.Money.op_Implicit(Decimal value) in E:\Code Project Stuff\MoneyType\Money\Money.cs:line 32 at System.Tests.MoneyDistributorTests.DistributeUniformRatioToLastIsCorrect() in E:\Code Project Stuff\MoneyType\Money.Tests\MoneyDistributorTests.cs:line 23  Inner Stack Trace  at System.Currency..ctor(Int32 isoCurrencyCode) in E:\Code Project Stuff\MoneyType\Money\Currency.cs:line 1178 at System.Currency..cctor() in E:\Code Project Stuff\MoneyType\Money\Currency.cs:line 53
When I tried to use the Money type in a Windows Form Application:
private Money m1; private Money m2;
m1 = 1.23;
It also failed at line 176 in Money.cs:
_currency = Currency.FromCurrentCulture();
with: Type initializer for 'System.Currency' threw an exception.
How do I fix this?
Thanks, Don





Oops...
I added the static fields at the last minute since I felt a bit more courageous with XSLT... and then forgot to run the tests. The fields were trying to get initilialized before the type initializer was run. I moved the initialization code into the static constructor and now all looks well.
I've updated the code.
Thanks for catching this and reporting it!





It fails on loading the public static currencies.
the problem being for some reason the static constructor is never called on the class. the static values are called before the static constructor, and the interal lookup lists are never loaded.
So to rectify this I took the guts of the static Currency() call and made a new private static void Init()
then threw this line
if (_currencies.Count == 0) Currency.Init();
into the public CurrencyIint32 isoCurrenctCode) constructor





Spotted a mistake though which may have influenced your design.
The Decimal type is not a binary floating point type, it is fixed point. Internally, it used 4 x ints; 3 of these store a 96bit mantissa? and the final one stores flags including sign and scale.
If you use Reflector, you can reverseengineer the Decimal class and see how it works (although some methods are external). Also there are some rounding methods you might want to crib.
Is a there SmallMoney class in the works?
Cheers Simon





Thanks Simon 
It's a common misconception that System.Decimal is a fixed type. The reason for this is probably due to the large precision it has. From the docs (http://msdn.microsoft.com/enus/library/system.decimal.aspx[^]):
A decimal number is a floatingpoint 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.
Using Reflector, you can see how the type is composed of 4 Int32 fields. The "flags" field is used to store the exponent, and the other 3 Int32 fields are the significand. The significand uses a set number of bits (96) which are then scaled using the value in the "flags" field. This is just how IEEE 754 Single and Double types work, except they have only 32 and 64 bits to Decimal's 128 bits of storage.
Good idea on the SmallMoney class  it would be a nice complement! (As would, perhaps, a BigMoney, using an arbitrary number of bytes to represent arbitrary amounts of money.)





It is floating point, yes, but it is NOT a "binary floatingpoint type".
Of course everything is represented by bits in the end but in a Decimal the bits represent *decimal* digits, not binary digits, which leads to a fixed number of *decimal* digits precision. After all, numbers do not require the same number of fractional digits to be represented in different bases; 0.125 in decimal is 0.1 in octal, and 0.1 in base 3 cannot be represented exactly as a decimal number.
This makes Decimal suitable for money, simply because we humans deal with amounts in base ten, and less so for scientific calculations, where "base twofriendly" numbers are just as good as "base tenfriendly" ones.
(Q: What is (base ten) 0,00000000023283064365386962890625 in base two?)





Ah, you are correct  Decimal is a base10 floating point type. This does cancel out a major concern of using it to represent currency amounts.
I do treat the fractional representation issue in the article  the problem using a base2 type for money is due to the fact that there are many fractions which can be represented in base 10 exactly which can't be represented in base 2 exactly. I use 0.33 and 0.34, since both of those are exact decimal fractions, where in base 2, they don't have an exact representation in a finite number of digits. Your example is 1/3, which binary as well as decimal cannot represent.
However, I still think that a floating point type, whether binary or decimal, is inappropriate. The reason is due to the problems of scale. When you have 2 floating point numbers of vastly different scale, the results of a computation become suspect due to truncation. If you have two very close numbers, you can run into catastrophic cancellation. Numerical errors are introduced which are much more deterministic if you use a fixedscale type. Almost all the world's financial transactions deal with amounts within a very small scale of numbers (the Money type here has 24 orders of magnitude represented), so having the large range of magnitude really isn't that important. The System.Decimal type is too generic for the domain of handling money without introducing errors in the computation unless extreme care is take  care which is often beyond the purview of the developer working with the type, and beyond the framework to give any assistance.
I'll rewrite the article to include the understanding that Decimal is a base10 floating point type, and provide a bit more clarity on the difference between a base2 floating, base2 fixed, base10 floating and base10 fixed number. Thanks for the headsup!





I'm sure you are correct; I'm no expert on this and I think you are!
That said, as a curious person I wish I understood "the problems of scale" that you mention. This isn't the ideal place to go into any deep discussion about it, but I'd like to express my wish that you show exactly what causes the problems and how the Money type solves this if you do rewrite the article.
Keep up the excellent work!





An article update with examples is in progress as I write this! Thanks for the encouragement, and pointing out how to improve the article.





A small suggestion...
The right term to be used in your article should be Currency, and not Money, it doesn't sound professional.
Funny Money... ))
Professional System Library on www.prosyslib.com, making it simple for software developers.





I'm not aware of a connotation of the term "money" which gives it an unprofessional sound. If I'd used "cash" on the other hand, I'd agree with you.
The reason I called it "Money" however, is primarily due to Martin Fowler and Matt Foemmel calling it "Money" in their description of the pattern. Since they used it in a widely read reference source (Patterns of Enterprise Application Architecture), I can rely on other developers having a good idea of the general solution to the problem when they read the pattern  in effect creating the language that we use to communicate about it.
Further, I like having two terms, so I can use "currency" to mean the measuring system, the one the standard covers, and "money" to mean instances of that system. Sort of like I can use "the metric system" when talking about the units of measuring linear distance, and "meters" when referring to instances of it.
It used to be, according to my dictionary here, that "money" was the only thing to call it in English, from the Latin word for "mint". Then "currency" came around in the mid17th century and apparently may have caused "money" to look a bit plebeian. Perhaps, if we work together, we can turn this situation around!





According to Investopedia:
Fiat money was introduced because gold is a scarce resource and economies growing quickly couldn't always mine enough gold to back their money requirement.
(Fiat money is money that derives it value purely from the faith people have in it being valuable. This sounds insane, but if you think about it, gold derived it's value in no small part from the same source  people's belief that it is valuable, and will continue to be so.)





I can't think of a better example of creating a new type. This was an excellent idea. Five from me





Thanks. I couldn't think of one either, so I trundled down this obscured, yet already blazoned trail. My hope is that I at least cleared away some of the thickets and pointed out the hazards to avoid.







General News Suggestion Question Bug Answer Joke Praise Rant Admin Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

