5

In my application I want all my properties storing money amounts to be rounded to n decimal places.

For code clarity, I'd rather have a custom type MoneyAmount which all my corresponding fields would have, instead of having to put a `Math.Round(value, n)' in all the property getters/setters.

Is there a neat way to achieve this?

I saw this post about overloading assignment operators - is this the suggested approach?

EDIT: Given the multiple views, I post the full code I derived here:

public struct MoneyAmount {
const int N = 4;
private readonly double _value;

public MoneyAmount(double value) {
  _value = Math.Round(value, N);
}

#region mathematical operators
public static MoneyAmount operator +(MoneyAmount d1, MoneyAmount d2) {
  return new MoneyAmount(d1._value + d2._value);
}

public static MoneyAmount operator -(MoneyAmount d1, MoneyAmount d2) {
  return new MoneyAmount(d1._value - d2._value);
}

public static MoneyAmount operator *(MoneyAmount d1, MoneyAmount d2) {
  return new MoneyAmount(d1._value * d2._value);
}

public static MoneyAmount operator /(MoneyAmount d1, MoneyAmount d2) {
  return new MoneyAmount(d1._value / d2._value);
}
#endregion

#region logical operators
public static bool operator ==(MoneyAmount d1, MoneyAmount d2) {
  return d1._value == d2._value;
}
public static bool operator !=(MoneyAmount d1, MoneyAmount d2) {
  return d1._value != d2._value;
}
public static bool operator >(MoneyAmount d1, MoneyAmount d2) {
  return d1._value > d2._value;
}
public static bool operator >=(MoneyAmount d1, MoneyAmount d2) {
  return d1._value >= d2._value;
}
public static bool operator <(MoneyAmount d1, MoneyAmount d2) {
  return d1._value < d2._value;
}
public static bool operator <=(MoneyAmount d1, MoneyAmount d2) {
  return d1._value <= d2._value;
}
#endregion

#region Implicit conversions
/// <summary>
/// Implicit conversion from int to MoneyAmount. 
/// Implicit: No cast operator is required.
/// </summary>
public static implicit operator MoneyAmount(int value) {
  return new MoneyAmount(value);
}

/// <summary>
/// Implicit conversion from float to MoneyAmount. 
/// Implicit: No cast operator is required.
/// </summary>
public static implicit operator MoneyAmount(float value) {
  return new MoneyAmount(value);
}

/// <summary>
/// Implicit conversion from double to MoneyAmount. 
/// Implicit: No cast operator is required.
/// </summary>
public static implicit operator MoneyAmount(double value) {
  return new MoneyAmount(value);
}

/// <summary>
/// Implicit conversion from decimal to MoneyAmount. 
/// Implicit: No cast operator is required.
/// </summary>
public static implicit operator MoneyAmount(decimal value) {
  return new MoneyAmount(Convert.ToDouble(value));
}
#endregion

#region Explicit conversions
/// <summary>
/// Explicit conversion from MoneyAmount to int. 
/// Explicit: A cast operator is required.
/// </summary>
public static explicit operator int(MoneyAmount value) {
  return (int)value._value;
}

/// <summary>
/// Explicit conversion from MoneyAmount to float. 
/// Explicit: A cast operator is required.
/// </summary>
public static explicit operator float(MoneyAmount value) {
  return (float)value._value;
}

/// <summary>
/// Explicit conversion from MoneyAmount to double. 
/// Explicit: A cast operator is required.
/// </summary>
public static explicit operator double(MoneyAmount value) {
  return (double)value._value;
}

/// <summary>
/// Explicit conversion from MoneyAmount to decimal. 
/// Explicit: A cast operator is required.
/// </summary>
public static explicit operator decimal(MoneyAmount value) {
  return Convert.ToDecimal(value._value);
}
#endregion
}
Community
  • 1
  • 1
neggenbe
  • 1,697
  • 2
  • 24
  • 62
  • 5
    Side note: when working with *money*, `decimal` is a better type to wrap – Dmitry Bychenko Jul 05 '16 at 08:36
  • 4
    Rounding when storing seems very suspect. Normally you would only round when displaying. – Matthew Watson Jul 05 '16 at 08:36
  • 4
    @MatthewWatson no it isn't, in fact the number of decimal digits for each currency is strictly defined. The OP is actually asking about the [Money Pattern](http://martinfowler.com/eaaCatalog/money.html) – Panagiotis Kanavos Jul 05 '16 at 08:38
  • @PanagiotisKanavos If you're rounding to the nearest currency unit, it becomes more complicated than just storing N decimal places. For example, if you want to split an amount of money 30%/70% and the calculation doesn't result in two values that are exact to N decimal places, you have to ensure that after rounding, the two split values still add to exactly the original value. This requires additional work beyond simple rounding to N places. – Matthew Watson Jul 05 '16 at 08:48
  • 1
    @MatthewWatson I know, which is why `Money` is a pattern. In fact, it's even *more* complicated because there are certain rule that deal with minimizing rounding errors (the Banker's algorithm for rounding is just one techniquer) and *allocating* missing cents so that nothing gets lost. Other rules that govern currency conversions, others that govern how many decimals are *allowed* for each currency and how many for internal calculations – Panagiotis Kanavos Jul 05 '16 at 09:05
  • Are your realy realy sure you want to round DURING calculations. Normally your would only round after all calculations are done and your a writing the result to an output medium, like a report. I prefer to NEVER round and only round "on paper". – Martin Mulder Jul 05 '16 at 09:10
  • @ Martin Mulder: the nice thing is that I can have a `MoneyAmount` field (used for storage, etc...) use a `double` property, or the reverse way! – neggenbe Jul 05 '16 at 10:49

2 Answers2

9

I'd suggest the following:

  1. Create a new struct, called MoneyAmount.
  2. It contains one field: A double.
  3. The constructor with one double parameter, this constructor rounds the value and assigns it to the internal field.
  4. Add the members/operators you might need to your struct so it has all the same operations as the double, like +, -, etc. But also casts/conversions from/to other types. Every operation produces a new instance of MoneyAmount with a rounded value.
  5. Also consider implementing the interfaces IFormattable, IComparable and IConvertible.

Short example:

public struct MoneyAmount
{
    const int N = 4;
    private readonly double _value;

    public MoneyAmount(double value)
    {
        _value = Math.Round(value, N);
    }

    // Example of one member of double:
    public static MoneyAmount operator *(MoneyAmount d1, MoneyAmount d2) 
    {
        return new MoneyAmount(d1._value * d2._value);
    }

    /// <summary>
    /// Implicit conversion from double to MoneyAmount. 
    /// Implicit: No cast operator is required.
    /// </summary>
    public static implicit operator MoneyAmount(double value)
    {
        return new MoneyAmount(value);
    }

    /// <summary>
    /// Explicit conversion from MoneyAmount to double. 
    /// Explicit: A cast operator is required.
    /// </summary>
    public static explicit operator double(MoneyAmount value)
    {
        return value._value;
    }

    /// <summary>
    /// Explicit conversion from MoneyAmount to int. 
    /// Explicit: A cast operator is required.
    /// </summary>
    public static explicit operator MoneyAmount(int value)
    {
        return new MoneyAmount(value);
    }

    /// <summary>
    /// Explicit conversion from MoneyAmount to int. 
    /// Explicit: A cast operator is required.
    /// </summary>
    public static explicit operator int(MoneyAmount value)
    {
        return (int)value._value;
    }

    // All other members here...
}

I realize: The double has a lot of members...

With these operators, the following code is possible:

MoneyAmount m = 1.50; // Assignment from a double.
MoneyAmount n = 10; // Assignment from an integer.
m += n; // Mathematical operation with another MoneyAmount .
m *= 10; // Mathematical operation with an integer.
m -= 12.50; // Mathematical operation with a double.

EDIT

All conversion methods you may want to implement:

  • Explicit MoneyAmount --> int
  • Explicit MoneyAmount --> float
  • Explicit MoneyAmount --> double
  • Explicit MoneyAmount --> decimal

  • Implicit int--> MoneyAmount

  • Implicit float --> MoneyAmount
  • Implicit double--> MoneyAmount
  • Implicit decimal --> MoneyAmount

All mathematical operations you may want to implement:

  • MoneyAmount + MoneyAmount
  • MoneyAmount - MoneyAmount
  • MoneyAmount * MoneyAmount
  • MoneyAmount / MoneyAmount

All relational operations you may want to implement:

  • MoneyAmount == MoneyAmount
  • MoneyAmount != MoneyAmount
  • MoneyAmount > MoneyAmount
  • MoneyAmount >= MoneyAmount
  • MoneyAmount < MoneyAmount
  • MoneyAmount <= MoneyAmount

With all these operations your have all basics covered.

Martin Mulder
  • 12,642
  • 3
  • 25
  • 54
4

This gets big very quickly. Writing a struct is easy, as demonstrated in @MartinMulder's answer, but consider that you will want to overload a number of combinations of operators, as well as including a few implicit/explicit casts as well.

Mathematical & Logical Operation

Consider that you may want to do mathematical operations on MoneyAmount

  • MoneyAmount + MoneyAmount
  • MoneyAmount + double
  • MoneyAmount + int
  • MoneyAmount + decimal

That is 4 overloads of the + operator. Rinse and repeat for -,/,* (and possibly %). You'll also want to overload <,<=, == and >, >=. Thats something like 30 operator overloads. Phew! Thats a lot of static methods.

public static MoneyAmount operator +(MoneyAmount d1, double d2) 
{
    return new MoneyAmount((decimal)(d1._value + d2));
}

Explicit/Implicit casts

Now consider that instead of this code

MoneyAmount m = new MoneyAmount(1.234);

You wanted to do this:

MoneyAmount m = 1.234;

That can be achieved with an implicit cast operator.

public static implicit operator MoneyAmount(double d)
{
    return new MoneyAmount((decimal)d);
}

(You'll need one for every type you want to allow implicit casts)

Another one:

int i = 4;
MoneyAmount m = (MoneyAmount)i;

This is done with an explicit cast operator overload.

public static explicit operator MoneyAmount(double d)
{
    return new MoneyAmount((decimal)d);
}

(Again, 1 for every type you want to allow explicit casts)

Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • 1
    Operator `=` cannot be overlaoded. I think you mean operator `==` Do not forget operators `!=`, `<=` and `>=` ;) – Martin Mulder Jul 05 '16 at 09:03
  • Each operator needs to be implemented once. You do not need an `+` operator for each type if you have an implicit operator from that type to MoneyAmount. C# will convert the other type to MoneyAmount before calling the operator-method. – Martin Mulder Jul 05 '16 at 09:06
  • Did you read my last comment? "Thats something like 30 operator overloads" is based on an incorrect assumption. – Martin Mulder Jul 05 '16 at 09:11
  • @MartinMulder Ive been pondering it... :) `int->double` has no *implicit* cast. So if you have `int n=1` and you wanted to add to `MoneyAmount` but only have an overload for `double` you need to do `MoneyAmount m = 1.23; m+= (double)n;` which I find weird. Hence the overload of `double` *and* `int` - have I missed something? – Jamiec Jul 05 '16 at 09:14
  • I edited my answer to reflect your question a bit. There is an implicit conversion from `int` to `double`. The code 'double d = 2;' will compile. – Martin Mulder Jul 05 '16 at 09:26