Why is using a NON-decimal data type bad for money?

前端 未结 5 1637
执念已碎
执念已碎 2021-02-08 23:13

tl;dr: What\'s wrong with my Cur (currency) structure?

tl;dr 2: Read the rest of the question please, before giving an

5条回答
  •  眼角桃花
    2021-02-08 23:47

    Floats aren't stable for accumulating and decrementing funds. Here's your actual example:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace BadFloat
    {
        class Program
        {
            static void Main(string[] args)
            {
                Currency yourMoneyAccumulator = 0.0d;
                int count = 200000;
                double increment = 20000.01d; //1 cent
                for (int i = 0; i < count; i++)
                    yourMoneyAccumulator += increment;
                Console.WriteLine(yourMoneyAccumulator + " accumulated vs. " + increment * count + " expected");
            }
        }
    
        struct Currency
        {
            private const double EPSILON = 0.00005;
            public Currency(double value) { this.value = value; }
            private double value;
            public static Currency operator +(Currency a, Currency b) { return new Currency(a.value + b.value); }
            public static Currency operator -(Currency a, Currency b) { return new Currency(a.value - b.value); }
            public static Currency operator *(Currency a, double factor) { return new Currency(a.value * factor); }
            public static Currency operator *(double factor, Currency a) { return new Currency(a.value * factor); }
            public static Currency operator /(Currency a, double factor) { return new Currency(a.value / factor); }
            public static Currency operator /(double factor, Currency a) { return new Currency(a.value / factor); }
            public static explicit operator double(Currency c) { return System.Math.Round(c.value, 4); }
            public static implicit operator Currency(double d) { return new Currency(d); }
            public static bool operator <(Currency a, Currency b) { return (a.value - b.value) < -EPSILON; }
            public static bool operator >(Currency a, Currency b) { return (a.value - b.value) > +EPSILON; }
            public static bool operator <=(Currency a, Currency b) { return (a.value - b.value) <= +EPSILON; }
            public static bool operator >=(Currency a, Currency b) { return (a.value - b.value) >= -EPSILON; }
            public static bool operator !=(Currency a, Currency b) { return Math.Abs(a.value - b.value) <= EPSILON; }
            public static bool operator ==(Currency a, Currency b) { return Math.Abs(a.value - b.value) > EPSILON; }
            public bool Equals(Currency other) { return this == other; }
            public override int GetHashCode() { return ((double)this).GetHashCode(); }
            public override bool Equals(object other) { return other is Currency && this.Equals((Currency)other); }
            public override string ToString() { return this.value.ToString("C4"); }
        }
    
    }
    

    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?

提交回复
热议问题