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

前端 未结 5 1626
执念已碎
执念已碎 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:38

    I'm not sure why you're shrugging off J Trana's answer as irrelevant. Why don't you try it yourself? The same example works with your struct too. You just need to add a couple extra iterations because you're using a double instead of a float, which gives you a bit more precision. Just delays the problem, doesn't get rid of it.

    Proof:

    class Program
    {
        static void Main(string[] args)
        {
            Currency currencyAccumulator = new Currency(0.00);
            double doubleAccumulator = 0.00f;
            float floatAccumulator = 0.01f;
            Currency currencyIncrement = new Currency(0.01);
            double doubleIncrement = 0.01;
            float floatIncrement = 0.01f;
    
            for(int i=0; i<100000000; ++i)
            {
                currencyAccumulator += currencyIncrement;
                doubleAccumulator += doubleIncrement;
                floatAccumulator += floatIncrement;
            }
            Console.WriteLine("Currency: {0}", currencyAccumulator);
            Console.WriteLine("Double: {0}", doubleAccumulator);
            Console.WriteLine("Float: {0}", floatAccumulator);
            Console.ReadLine();
        }
    }
    
    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"); }
    }
    

    Result:

    Currency: $1,000,000.0008
    Double: 1000000.00077928
    Float: 262144
    

    We're only up to .08 cents, but eventually that'll add up.


    Your edit:

        static void Main(string[] args)
        {
            Currency c = 1.00;
            c /= 100000;
            c *= 100000;
            Console.WriteLine(c);
            Console.ReadLine();
        }
    }
    
    struct Currency
    {
        private const double EPS = 0.00005;
        private double val;
        public Currency(double val) { this.val = Math.Round(val, 4); }
        public static Currency operator +(Currency a, Currency b) { return new Currency(a.val + b.val); }
        public static Currency operator -(Currency a, Currency b) { return new Currency(a.val - b.val); }
        public static Currency operator *(Currency a, double factor) { return new Currency(a.val * factor); }
        public static Currency operator *(double factor, Currency a) { return new Currency(a.val * factor); }
        public static Currency operator /(Currency a, double factor) { return new Currency(a.val / factor); }
        public static Currency operator /(double factor, Currency a) { return new Currency(a.val / factor); }
        public static explicit operator double(Currency c) { return Math.Round(c.val, 4); }
        public static implicit operator Currency(double d) { return new Currency(d); }
        public static bool operator <(Currency a, Currency b) { return (a.val - b.val) < -EPS; }
        public static bool operator >(Currency a, Currency b) { return (a.val - b.val) > +EPS; }
        public static bool operator <=(Currency a, Currency b) { return (a.val - b.val) <= +EPS; }
        public static bool operator >=(Currency a, Currency b) { return (a.val - b.val) >= -EPS; }
        public static bool operator !=(Currency a, Currency b) { return Math.Abs(a.val - b.val) < EPS; }
        public static bool operator ==(Currency a, Currency b) { return Math.Abs(a.val - b.val) > EPS; }
        public bool Equals(Currency other) { return this == other; }
        public override int GetHashCode() { return ((double)this).GetHashCode(); }
        public override bool Equals(object o) { return o is Currency && this.Equals((Currency)o); }
        public override string ToString() { return this.val.ToString("C4"); }
    }
    

    Prints $0.

提交回复
热议问题