tl;dr: What’s wrong with my Cur (currency) structure?
tl;dr 2: Read the rest of the question please, before giving an example with float or double. 🙂
I’m aware that this question has come up numerous times before all around the internet, but I have not yet seen a convincing answer, so I thought I’d ask again.
I fail to understand why using a non-decimal data type is bad for handling money. (That refers to data types that store binary digits instead of decimal digits.)
True, it’s not wise to compare two doubles with a == b. But you can easily say a - b <= EPSILON or something like that.
What is wrong with this approach?
For instance, I just made a struct in C# that I believe handles money correctly, without using any decimal-based data formats:
struct Cur
{
private const double EPS = 0.00005;
private double val;
Cur(double val) { this.val = Math.Round(val, 4); }
static Cur operator +(Cur a, Cur b) { return new Cur(a.val + b.val); }
static Cur operator -(Cur a, Cur b) { return new Cur(a.val - b.val); }
static Cur operator *(Cur a, double factor) { return new Cur(a.val * factor); }
static Cur operator *(double factor, Cur a) { return new Cur(a.val * factor); }
static Cur operator /(Cur a, double factor) { return new Cur(a.val / factor); }
static explicit operator double(Cur c) { return Math.Round(c.val, 4); }
static implicit operator Cur(double d) { return new Cur(d); }
static bool operator <(Cur a, Cur b) { return (a.val - b.val) < -EPS; }
static bool operator >(Cur a, Cur b) { return (a.val - b.val) > +EPS; }
static bool operator <=(Cur a, Cur b) { return (a.val - b.val) <= +EPS; }
static bool operator >=(Cur a, Cur b) { return (a.val - b.val) >= -EPS; }
static bool operator !=(Cur a, Cur b) { return Math.Abs(a.val - b.val) < EPS; }
static bool operator ==(Cur a, Cur b) { return Math.Abs(a.val - b.val) > EPS; }
bool Equals(Cur other) { return this == other; }
override int GetHashCode() { return ((double)this).GetHashCode(); }
override bool Equals(object o) { return o is Cur && this.Equals((Cur)o); }
override string ToString() { return this.val.ToString("C4"); }
}
(Sorry for changing the name Currency to Cur, for the poor variable names, for omitting the public, and for the bad layout; I tried to fit it all onto the screen so that you could read it without scrolling.) 🙂
You can use it like:
Currency a = 2.50;
Console.WriteLine(a * 2);
Of course, C# has the decimal data type, but that’s beside the point here — the question is about why the above is dangerous, not why we shouldn’t use decimal.
So would someone mind providing me with a real-world counterexample of a dangerous statement that would fail for this in C#? I can’t think of any.
Thanks!
Note: I am not debating whether decimal is a good choice. I’m asking why a binary-based system is said to be inappropriate.
Floats aren’t stable for accumulating and decrementing funds. Here’s your actual example:
On my box this gives $4,000,002,000.0203 accumulated vs. 4000002000 expected in C#. It’s a bad deal if this gets lost over many transactions in a bank – it doesn’t have to be large ones, just many. Does that help?