十进制数的哈希码取决于末尾的零

17

可能重复:
C#为什么相等的十进制数会产生不同的哈希值?

我在我的.NET 3.5应用程序中(x86或x64,我尝试了两者)遇到了一个问题,即具有不同数量尾随零的十进制数具有不同的哈希值。例如:

decimal x = 3575.000000000000000000M;
decimal y = 3575.0000000000000000000M;

Console.WriteLine(x.GetHashCode());
Console.WriteLine(y.GetHashCode());
Console.WriteLine(x == y);
Console.WriteLine(x.GetHashCode() == y.GetHashCode());

在我的电脑上输出如下内容:

1085009409
1085009408
True
False

我认为哈希码的差异是由于不同比例因素引起的两个数字的不同内部表示所致。

虽然我可以通过去掉尾随零来解决这个问题,但我一直认为如果x == y,则GetHashCode应该返回相同的值。这种假设是错误的吗,或者这是Decimal.GetHashCode的问题?

编辑:明确版本,我正在使用Visual Studio 2008 SP1,.NET 3.5。


1
这是你的实际代码吗?对我来说,它返回 1085009408, 1085009408, True True。- 编辑:那是.NET 4,在.NET 3.5上有不同的结果得到确认。 - CodeCaster
我在.NET 3.5上得到了与OP相同的输出。@CodeCaster,你在哪个版本上运行它? - Servy
1
所以我查看了十进制数字的位数,它们在x和y之间是不同的(使用.NET 3.5之前的版本)。显然,“Equals”方法考虑到了这种差异,但“GetHashCode”没有。 - Servy
@CodeCaster 看起来是那个问题的重复。在发布之前,我确实搜索了类似的问题,但错过了那一个,很好的发现。 - MrKWatkins
2个回答

13
这是关于Decimal.GetHashCode的问题,适用于.NET Framework版本3.5及更低版本。按照指南,当两个值被认为相等时,它们必须返回相同的哈希码;在这种情况下,decimal显然没有达到这个标准。您应该始终期望两个相等的对象具有相同的哈希码。 根据MSDN文档
  

如果两个对象比较相等,则每个对象的GetHashCode方法必须返回相同的值。

复现

我已经尝试在不同版本的.NET Framework中使用您的确切代码,并得出以下结果:
╔══════════════════╤══════════════════╗
║Framework version │ Hashcode equal ? ║
╟──────────────────┼──────────────────╢
║      2.0         │  No.             ║
║      3.0         │  No.             ║
║      3.5         │  No.             ║
║      4.0         │  Yes.            ║
║      4.5         │  Yes.            ║
╚══════════════════╧══════════════════╝

换句话说,看起来你发现了一个在.NET Framework中已经被修复的错误。

上述结果是使用Visual Studio 2012 RC和属性页面切换框架得出的。

Microsoft在此处承认了这个错误


我也看到了那些文档,所以我认为这一定是个bug,但我想先在这里听听其他人的意见;毕竟,在.NET中出现bug是相当罕见的! - MrKWatkins
实际上看起来这是一个错误,但已经在当前版本中修复了;请参见答案的更新。 - driis
感谢您在这里的工作,非常感激。升级到.NET 4的另一个原因... - MrKWatkins
1
@MrKWatkins 尽管这些特定数字的问题已经解决,但如果您查看顶部链接的“重复”问题,您会发现在.NET 4.0和.NET 4.5中仍然存在一些特定数字的问题。 - Jeppe Stig Nielsen
@Jeppe Stig Nielsen 感谢您指出这一点,很高兴知道如果我们升级到 .NET 4.x,我就不能删除我的修复(删除尾随零)。 - MrKWatkins

9
这是在.NET 4之前版本中相当臭名昭著的错误。Decimal.GetHashCode()实现对十进制值的位值有依赖关系。由于十进制跟踪小数部分中已知数字的数量,它们是不同的。您可以通过在值上使用Decimal.GetBits()来查看。实际上,这是否是一个错误是有争议的,取决于您戴什么样的眼镜,因为十进制确实具有不同的值。

尽管如此,微软认为这是不直观的行为,并在.NET 4中进行了修复,相关反馈文章在这里


我注意到了由于尝试使用GetBits而导致的问题;从这个角度来看,它们肯定非常不同,这让我头疼了一段时间...然而,我没注意到GetHashCode的问题,因为对于很多末尾零,它们确实会产生相同的哈希码...例如3575和0->17或19->22个末尾零都给出相同的哈希码,但是它们的GetBits()结果却有很大不同。我很想看看实际的GetHashCode实现,以便我能找出原因… - MrKWatkins
1
下载 SSCLI20,clr/src/vm/comdecimal.cpp 源代码文件,找到 COMDecimal::GetHashCode() 函数。 - Hans Passant
1
Decimal.Equals() 表示这些数字相等,但是 GetHashCode() 却表示它们不相等的事实表明 DecimalObject.EqualsGetHashCode 中有一个存在问题。就我个人而言,我认为这个问题在于 Object.Equals 的覆盖,因为 1.0m 和 1.00m 其实和 "CAT" 和 "cat" 没有什么区别。虽然有时候可能需要使用自定义比较器将它们映射为相等的情况 (同样适用于 "CAT" 和 "cat"),但在我看来 Object.Equals 应该意味着强语义等价,以使得两个不可变对象... - supercat
所有成员都报告“相等”的类型可以被视为可互换的(因此,如果没有比较引用相等的内容,代码可以放弃一个并在原来使用前者的每个地方使用另一个)。因为调用指向同一对象的两个引用上的Equals要比比较持有相同值的不同对象花费更少的时间,所以当使用这些类型时,这种替换可以带来重大的性能提升。Object.Equals似乎是最通用的内部化比较方法;太糟糕了,一些类型(例如Decimal)将其用于其他目的而不是等价性。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接