|
|
Comments and Discussions
|
|
 |

|
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 round-off 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 "round-off 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 non-determinstic 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 floating-point type
System.Decimal is actually a decimal floating-point type. It can represent any value that can be written as a decimal number, including a fractional part, without any representation error.
A decimal floating-point type is a sum of powers of ten (including negative powers for the fractional part).
A binary floating-point 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 96-bit mantissa? and the final one stores flags including sign and scale.
If you use Reflector, you can reverse-engineer 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/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.
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 floating-point 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 two-friendly" numbers are just as good as "base ten-friendly" ones.
(Q: What is (base ten) 0,00000000023283064365386962890625 in base two?)
|
|
|
|

|
Ah, you are correct - Decimal is a base-10 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 base-2 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 fixed-scale 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 base-10 floating point type, and provide a bit more clarity on the difference between a base-2 floating, base-2 fixed, base-10 floating and base-10 fixed number. Thanks for the heads-up!
|
|
|
|

|
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 mid-17th 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.
|
|
|
|

|
Please show and explain the code.
And it can't parse from strings? Can't it at least serialize/deserialize?
While I realize that if given "$1.23" you can't just assume that it's USD, when given "1.23 USD" (or whatever the actual abbreviation is) you should be able to parse it.
P.S. And you can use a Decimal but ensure that it always contains an integer. I did so for my Rational[^] struct.
P.P.S. Yeah, that article doesn't show and explain the code, just as I'm complaining about here. But it's one of the first I wrote, and no one pointed out the error of my ways.
modified on Wednesday, July 30, 2008 11:10 AM
|
|
|
|

|
PIEBALDconsult wrote: I did so for my Rational[^] struct.
I used BigInteger instead, unlimited 'precision'
|
|
|
|

|
Which BigInteger did you use? The CLR's "native" one was pulled from RTM at the last minute.
I'd considered using a big number type, but it's hard to see the reason. An Int64 is more than enough for every usage scenario I've ever encountered or has been related to me. Int64 is easier to deal with and easier to store and retrieve from databases. It's got better performance. It allows true value semantics (a big number would need to have a reference type somewhere), which means that the GC is relieved of tracking it. I'm sure if you really need to have that much money being computed, you can find a way around these issues, but for the rest of us, I think the constrained scope brings useful benefits.
|
|
|
|

|
codekaizen wrote: Which BigInteger did you use? The CLR's "native" one was pulled from RTM at the last minute.
Pretty much that one, that comes with the DLR. There is a nasty right shift bug though Present in .NET 3.5 internal version too.
I needed to support unlimited numbers, performance on big numbers is not my concern
|
|
|
|

|
Eh... how exactly did someone manage to create infinite precision in 64 bits? I hope you're adherent of at least one religion too, lest your belief in the supernatural should go wasted on data types.
|
|
|
|

|
dojohansen wrote: Eh... how exactly did someone manage to create infinite precision in 64 bits?
It's not possible. I said I used a BigInteger class, and rationalizing that gives you infinite precision.
|
|
|
|

|
The impossibility was kind of my point. My bad though; I thought you said BigInt, not BigInteger. Though I am not familiar with BigInteger I suppose it is a class to represent any integer, with storage dynamically allocated.
However, my bad is limited to thinking it was 64 bits. I'll rephrase the question to this instead: How do you get infinite precision (or "keyspace" if you prefer) using only finite storage? The answer is still "it's not possible".
I assume that "rationalizing them" means to represent numbers as ratios of BigIntegers. If that is what you mean, this certainly does NOT create "infinite precision" either. If you disagree, please show me how to represent Pi, or e, or the square root of two, or indeed ANY irrational number accurately.
BigInteger may of course be very useful and ratios of them maybe even more so. I'm not saying they're no good. But it isn't strictly speaking correct that this gives you "infinite precision".
|
|
|
|
 |
|
|
General News Suggestion Question Bug Answer Joke Rant Admin
|
A convenient, high-performance money structure for the CLR which handles arithmetic operations, currency types, formatting, and careful distribution and rounding without loss.
| Type | Article |
| Licence | Ms-PL |
| First Posted | 30 Jul 2008 |
| Views | 69,307 |
| Downloads | 1,058 |
| Bookmarked | 117 times |
|
|