We ran into a magic decimal number that broke our hashtable. I boiled it down to the following minimal case:
decimal d0 = 295.50000000000000000000000000m;
decimal d1 = 295.5m;
Console.WriteLine("{0} == {1} : {2}", d0, d1, (d0 == d1));
Console.WriteLine("0x{0:X8} == 0x{1:X8} : {2}", d0.GetHashCode(), d1.GetHashCode()
, (d0.GetHashCode() == d1.GetHashCode()));
Giving the following output:
295.50000000000000000000000000 == 295.5 : True
0xBF8D880F == 0x40727800 : False
What is really peculiar: change, add or remove any of the digits in d0 and the problem goes away. Even adding or removing one of the trailing zeros! The sign doesn’t seem to matter though.
Our fix is to divide the value to get rid of the trailing zeroes, like so:
decimal d0 = 295.50000000000000000000000000m / 1.000000000000000000000000000000000m;
But my question is, how is C# doing this wrong?
edit: Just noticed this has been fixed in .NET Core 3.0 (possibly earlier, I didn’t check) : https://dotnetfiddle.net/4jqYos
To start with, C# isn’t doing anything wrong at all. This is a framework bug.
It does indeed look like a bug though – basically whatever normalization is involved in comparing for equality ought to be used in the same way for hash code computation. I’ve checked and can reproduce it too (using .NET 4) including checking the
Equals(decimal)andEquals(object)methods as well as the==operator.It definitely looks like it’s the
d0value which is the problem, as adding trailing 0s tod1doesn’t change the results (until it’s the same asd0of course). I suspect there’s some corner case tripped by the exact bit representation there.I’m surprised it isn’t (and as you say, it works most of the time), but you should report the bug on Connect.