1

I'm currently working on a class RoundedDouble. The aim of this class is to provide an alternative to the .Net double struct. It should work similarly to a double but that is rounded to a number of decimal places. At first we just wanted to have formatted numbers at the user interface level, but we're stuck with the requirement that we need to ensure that the value shown in the user interface is the value used in the calculation (limited precision of a double is accepted). That means a value that round to 2 decimals and was entered as 1.23456789 should evaluate to 1.23.

(Using a custom TypeConverter is something we have considered as an alternative, but we opted for the RoundedDouble as that would allow us with arithmetic that preserves the lowest precision similar to calculating with number and taking their significance/accuracy into account.)

Now I have implemented IEquatable<Double> for the RoundedDouble class and I've hit a snag for the following situation:

object roundedDoubleBoxedAsObject = new RoundedDouble(2, 1.23456789); //1st param is the number of decimal places
double expectedValue = 1.23;
Debug.Assert(Equals(roundedDoubleBoxedAsObject, expectedValue)); // Passes, as RoundedDouble.Equals(double) is called
Debug.Assert(Equals(expectedValue, roundedDoubleBoxedAsObject),
             "Rounded value should be considered equal."); // <-- Fails, because Double.Equals(object) is called!

I've already implemented an implicit conversion from RoundedDouble to Double that already covers a lot of problems. But when RoundedDouble is boxed as an object, the implicit conversion is circumvented.

Is this problem even solvable in C#? Or have I hit a limit in the programming language?

Jakotheshadows
  • 1,485
  • 2
  • 13
  • 24
Xilconic
  • 3,785
  • 4
  • 26
  • 35
  • If your intent is to create a value type you should use a struct, but based on your code sample that is not your intent. You should probably reword the title. – Jakotheshadows Mar 09 '16 at 16:11
  • Yeah, we considerd using struct, but RoundedDouble would not be immutable in our usecases. We would like to specify the number of decimals spaces for some property, and then set new values when required while keeping the responsibility for the number of decimal spaces in the owner of the property. And we'd also like to prevent boilerplate code in the property setter :) – Xilconic Mar 09 '16 at 16:16
  • What is the Equals method that is called in the assertions? Is it this.Equals, i.e. System.Object.Equals? – nodots Mar 09 '16 at 16:17
  • It's not a limit on the language, you just need to understand the type system. `object.Equals(object,object)` exists and the compiler knows it has an object and double. It's not going to perform any conversion because it doesn't need to. Frankly, you shouldn't be declaring your variable as `object` anyway, but it won't matter, it won't change the situation. – Jeff Mercado Mar 09 '16 at 16:19
  • 1
    You'll almost certainly have to tone down your expectations. The built-in value types, like double, are rather special. The C# compiler, the jitter and the CLR have a lot of hard-coded knowledge about them, the kind that you can't reproduce easily with your own type. If you want a template to work from then look at, say, BigInteger. – Hans Passant Mar 09 '16 at 16:22
  • 2
    Maybe your underlying object type should be a a Decimal: http://stackoverflow.com/questions/1132765/adjusting-decimal-precision-net – Steve Wellens Mar 09 '16 at 16:25
  • Are you trying to create a Money class? That's the most common scenario where you want to store and enforce a desired precision. In almost all cases the value is stored as a `decimal`, not `double`, to avoid scaling errors – Panagiotis Kanavos Mar 09 '16 at 16:30
  • @nodots System.Object.Equals(Object) is indeed the one being used in the failing assertion, due to RoundedDouble being a boxed as an object. – Xilconic Mar 10 '16 at 07:12
  • @SteveWellens I've considered Decimal and might move to that. The main thing is that we want to define an object `A` with a property whose value is always rounded to a number of decimals defined at construction of `A`, is displayed as formatted number with that number of decimals and lastly do arithmetic with it taking the limited precision of the resulting rounding into account. – Xilconic Mar 10 '16 at 07:22

1 Answers1

0

It seems that the problem of boxed comparison is already a challenge present in C# v4.0 as using System.Object.Equals(Object) will not work as you expected in cases like these:

double oneAsDouble = 1.0;
int oneAsInteger = 1;
Debug.Assert(oneAsDouble.Equals(oneAsInteger)); // Pass
Debug.Assert(oneAsInteger.Equals(oneAsDouble)); // Pass
Debug.Assert(Equals((object)oneAsDouble, (object)oneAsInteger)); // <-- Fails!
Debug.Assert(oneAsDouble.Equals((object)OneAsInteger)); // <-- Fails!
Debug.Assert(oneAsInteger.Equals((object)oneAsDouble)); // <-- Fails!
Xilconic
  • 3,785
  • 4
  • 26
  • 35
  • IMHO, although this is perfectly true, the problem in your case is more that `double` only supports equality with another `double` instance (http://referencesource.microsoft.com/#mscorlib/system/double.cs,1840cabec71ddb69). If you were dealing with two classes you own, you could override both sides of the Equals. – Simon Mourier Mar 10 '16 at 07:56
  • You are absolutely correct when you would deal with two custom classes or structs. Sadly, this isn't our situation as we do work with the double primitive. – Xilconic Mar 10 '16 at 10:14