【问题标题】:Is it safe to use float.NaN as Dictionary key?使用 float.NaN 作为 Dictionary 键是否安全?
【发布时间】:2019-12-12 02:54:21
【问题描述】:

代码(来自交互式外壳):

> var a = new Dictionary<float, string>();
> a.Add(float.NaN, "it is NaN");
> a[float.NaN]
"it is NaN"

所以有可能,但是安全吗?

【问题讨论】:

  • “安全”与什么相比?它可以编译...你期望什么结果(假设你阅读stackoverflow.com/questions/1145443/…)?
  • 是的,它在上下文中是“安全的”,即使有些出乎意料。 float.NaN == float.NaNfalse(根据标准 IEEE 754 规则),float.NaN.Equals(float.NaN) 是 true ……原因。 (.NET Standard 3.0 更改了一些 FP 处理规则,是否包括 Equal'ity?)

标签: c#


【解决方案1】:

转述自https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/Single.cs

public const float NaN = (float)0.0 / (float)0.0;
public static unsafe bool IsNaN(float f) => f != f;
public int CompareTo(object? value){
   ...
   if (m_value < f) return -1;
   if (m_value > f) return 1;
   if (m_value == f) return 0;
   if (IsNaN(m_value))
      return IsNaN(f) ? 0 : -1;
   else // f is NaN.
      return 1;
}
public bool Equals(float obj)
{
   if (obj == m_value)
   {
      return true;
   }
   return IsNaN(obj) && IsNaN(m_value);
}
public override int GetHashCode()
{
   int bits = Unsafe.As<float, int>(ref Unsafe.AsRef(in m_value));
   // Optimized check for IsNan() || IsZero()
   if (((bits - 1) & 0x7FFFFFFF) >= 0x7F800000)
   {
      // Ensure that all NaNs and both zeros have the same hash code
      bits &= 0x7F800000;
   }
   return bits;
}

您可以看到,在每种情况下,NaN 都需要特殊处理。标准 IEEE 表示未定义大多数位,并定义了比较的特殊情况,即使这些位值相同。

但是您也可以看到GetHashCode() && Equals() 将两个 NaN 视为等效。所以我相信使用 NaN 作为字典键应该没问题。

【讨论】:

  • 正如stackoverflow.com/a/59297194/477420 提醒的那样,浮点数是糟糕的开始键...所以实际上,无论使用浮点数进行 NaN 比较的任何行为都几乎可以保证永远找不到其他键(除非仅使用常量值。 ..) 由于几乎总是使用自定义comaprer(如stackoverflow.com/questions/48655003/…)作为浮点字典,因此很容易将您喜欢的任何相等添加到NaN,而不是依赖于查看源代码。
  • 对,除了 -0 和 Nan,默认实现需要具有相同表示的浮点数。我不会推荐它,但它确实有效。
【解决方案2】:

这取决于您所说的安全。

如果您希望人们能够使用字典并将其键与其他浮点数进行比较,则他们必须自己正确处理 NaN 的键值。由于float.NaN == float.NaN 恰好是False,这可能会导致问题。

但是,Dictionary 成功执行了查找,其他操作也正常工作。

这里的问题是你为什么首先需要它?

【讨论】:

    【解决方案3】:

    使用浮点数作为字典的键是个坏主意。

    理论上你可以做到。但是当您使用 float\double\decimal 时,您应该使用一些 Epsilon 来比较 2 个值。使用这样的公式:

    abs(a1 - a2) < Epsilon
    

    由于浮点运算的四舍五入和无理数的存在而需要它。例如,您将如何与 PI 或 sqrt(2) 进行比较?

    因此,在这种情况下,使用浮点数作为字典键是个坏主意。

    【讨论】:

    • 是的,作为键浮动是个坏主意……但这是一个不同的问题,所以……虽然“不要那样做”在某种程度上是一个答案……
    • 关闭原因。但是关于 NaN 和 Dictionary 和 .equals 方法有 2 个正确答案。但是没有人写:如果你愿意,你仍然可以在脚上开枪(C)
    猜你喜欢
    • 2017-12-10
    • 2015-04-18
    • 1970-01-01
    • 2016-04-07
    • 1970-01-01
    • 2013-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多