【问题标题】:Decimal.GetHashCode Depends On Trailing Zeros [duplicate]Decimal.GetHashCode 取决于尾随零 [重复]
【发布时间】:2012-07-02 16:44:17
【问题描述】:

可能重复:
C# Why can equal decimals produce unequal hash values?

我在 .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

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

虽然我可以通过删除尾随零来解决此问题,但我始终假设 GetHashCode 应该为 x 和 y 返回相同的值,如果 x == y。这个假设是错误的,还是 Decimal.GetHashCode 的问题?

编辑:要清楚我使用 Visual Studio 2008 SP1、.NET 3.5 的版本。

【问题讨论】:

  • 这是您的实际代码吗?这将为我返回1085009408, 1085009408, True True。 - 编辑:那是 .NET 4,.NET 3.5 上的不同结果得到确认。
  • 我得到了与 .NET 3.5 上的 OP 相同的输出。 @CodeCaster,你在哪个版本上运行它?
  • 所以我去查看了小数位,它们对于 x 和 y 是不同的(使用 .NET pre 3.5)。显然,Equals 方法解释了这种差异,但 GetHashCode 没有。
  • @CodeCaster 是的,从外观上看,它是一个副本。我在发布之前确实搜索了类似的问题,但错过了,很好的发现。

标签: c# .net


【解决方案1】:

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

Per MSDN

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

复制

我已经针对不同版本的 .NET Framework 尝试了您的确切代码,结果是:

╔══════════════════╤══════════════════╗
║Framework version │ Hashcode equal ? ║
╟──────────────────┼──────────────────╢
║      2.0         │  No.             ║
║      3.0         │  No.             ║
║      3.5         │  No.             ║
║      4.0         │  Yes.            ║
║      4.5         │  Yes.            ║
╚══════════════════╧══════════════════╝

换句话说,您似乎偶然发现了 .NET 框架中的一个错误,该错误已在 .NET Framework 4 中得到修复。

以上结果是使用Visual Studio 2012 RC,使用属性页切换框架达到的。

微软acknowledges the bug here

【讨论】:

  • 我也看到了那些文档,所以我认为这一定是一个错误,但我想我会先在这里获得一些其他意见;毕竟,.NET 中的错误相当罕见!
  • 实际上看起来是个bug,目前版本已修复;查看答案的更新。
  • 感谢您在这里的工作,非常感谢。升级到 .NET 4 的另一个原因...
  • @MrKWatkins 虽然这些特定数字的问题已解决,但如果您检查顶部链接的“重复”问题,您会看到问题在 .NET 中仍然存在(有一些特定数字) 4.0 和 .NET 4.5。
  • @Jeppe Stig Nielsen 感谢您指出这一点,很高兴知道如果我们升级到 .NET 4.x,我将无法删除我的修复(删除尾随零)。
【解决方案2】:

在 .NET 4 之前的 .NET 版本中,这是一个相当大的 infamous bug。Decimal.GetHashCode() 实现依赖于十进制值中的位值。它们是不同的,因为十进制跟踪分数中已知数字的数量。您可以通过对值使用 Decimal.GetBits() 来查看。这是否是一个错误实际上是值得商榷的,小数确实有不同的值,这取决于你戴什么样的眼镜。

尽管如此,Microsoft 同意这是不直观的行为,并在 .NET 4 中对其进行了修复,相关反馈文章 is here

【讨论】:

  • 我注意到这个问题是因为玩GetBits;从这个角度来看,它们肯定是非常不同的,这让我有些头疼......我有一段时间没有注意到 GetHashCode 问题,尽管它们确实产生了相同的哈希码。 .. 例如,带有 0 -> 17 或 19 -> 22 个尾随零的 3575 都给出相同的哈希码,但 GetBits() 结果却大不相同。很想看到实际的 GetHashCode 实现,所以我可以找出原因......
  • 下载SSCLI20,clr/src/vm/comdecimal.cpp源代码文件,COMDecimal::GetHashCode()函数。
  • Decimal.Equals() 表示数字相等但GetHashCode 表示它们不相等这一事实表明DecimalObject.EqualsGetHashCode 的覆盖是错误的。就个人而言,我认为错误在于Object.Equals 的覆盖,因为1.0m 和1.00m 并不比“CAT”和“cat”更相似。在某些情况下,人们希望使用自定义比较器将它们映射为相等(同样使用“CAT”和“cat”),但恕我直言 Object.Equals 应该暗示强语义等价,例如两个不可变对象......跨度>
  • ...其所有成员都报告Equal 可以被视为可互换的(因此,如果没有关于引用相等性的比较,代码可以放弃一个并在每个使用前者的地方使用另一个)。因为在指向同一对象的两个引用上调用Equals 比比较具有相同值的不同对象所花费的时间要少得多,因此在使用此类类型时,这种替换可以显着提高性能。 Object.Equals 似乎是最通用的实习比较方法;太糟糕了,某些类型(例如Decimal)将它用于除等价之外的其他用途。
猜你喜欢
  • 2014-09-13
  • 2020-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-10
  • 1970-01-01
  • 2017-12-29
  • 1970-01-01
相关资源
最近更新 更多