【问题标题】:Is "(float)integer == integer" guaranteed to be equal in C#?“(float)integer == integer”在C#中是否保证相等?
【发布时间】:2012-09-19 16:39:12
【问题描述】:

虽然“我们都知道”x == y 可能有问题,其中xy 是浮点值,但这个问题更具体一点:

int x = random.Next(SOME_UPPER_LIMIT);
float r = x;
// Is the following ALWAYS true?    
r == x

现在,由于float的范围远大于整数(但精度不足以在边缘唯一呈现整数),如果回答这个问题还解决了上述x的哪些值可以保证,如果可以保证的话。


目前我的代码 做出这个假设(对于相对较小的 x 值) - 我想确保我不会被咬 :)


这将失败并显示“不等于:16777217”(cast float -> int):

for (int i = 0; i < int.MaxValue; i++) {
   float f = i;
   if ((int)f != i) throw new Exception("not equal " + i);
}

这个类似的代码不会失败(只有 int -> float);然而,由于转换中的损失,有几个浮点数可以“等于”同一个整数,并且可能代表一个无声的错误:

for (int i = 0; i < int.MaxValue; i++) {
   float f = i;
   if (f != i) throw new Exception("not equal " + i);
}

【问题讨论】:

  • 循环通过Int32.MinValueInt32.MaxValue,每次比较演员的结果。收集比较错误并且您有答案的案例(至少对于您的架构而言)。
  • @pst:老实说,不要认为这个问题有 any 通用的正确答案。假设“总是”永远不会在不同的机器上工作,所以它永远不会总是。如果,自然,我们在这里谈论的是肯定的答案。

标签: c# floating-point equality


【解决方案1】:

是的,无论int 是什么值,比较总是正确的。

int 将转换为 float 进行转换,第一次转换为 float 将始终与第二次转换产生相同的结果。

考虑:

int x = [any integer value];
float y = x;
float z = x;

yz 的值将始终相同。如果转换丢失精度,则两种转换都会以完全相同的方式丢失精度。

如果您将float 转换回int 以进行比较,那就是另一回事了。


另外,请注意,即使特定的 int 值转换为 float 始终会产生相同的 float 值,这并不意味着 float 值对于该 int 必须是唯一的价值。有int 值,其中(float)x == (float)(x+1) 将是true

【讨论】:

  • 谢谢。这正是我脑海中缺少的步骤/逻辑:y = xz = x,因此y == z。现在更有意义了。
  • 请注意,这是特定于语言的。某些语言可能不需要将整数转换为浮点数的结果保持一致。 (例如,一个可能在具有额外精度的寄存器中完成,另一个可能在内存中完成。当获取内存结果并增加到寄存器长度时,丢失的精度可能导致它们无法比较。)
  • +1。详细的浮点格式可以看en.wikipedia.org/wiki/IEEE_754-2008。我的理解是 int-> float 转换它大致是 (value &amp; 0xFFFFFD00) 所以整数将被分组为约 512 个值的集群,这些值等于相同的浮点值。
  • @AlexeiLevenkov:对于非常大的整数值也是如此。最多七位数的整数可以精确地表示为浮点数,然后整数值越大,它们就被分组到更大的簇中。
【解决方案2】:

比较 int 和 float 时,int 被隐式转换为 float。这确保了同样的精度损失发生,因此比较总是会发生。只要您不干扰隐式转换或进行算术运算,等式就应该成立。例如,如果你这样写:

bool AlwaysTrue(int i) {
    return i == (float)i;
}

有一个隐式转换,所以它等价于这个应该总是返回 true 的函数:

bool AlwaysTrue(int i) {
    return (float)i == (float)i;
}

但如果你这样写:

bool SometimesTrue(int i) {
    return i == (int)(float)i;
}

那么就没有更多的隐式转换,精度损失只发生在右侧。结果可能是错误的。同样,如果你这样写:

bool SometimesTrue(int i) {
    return 1 + i == 1 + (float)i;
}

那么双方的精度损失可能不相等。结果可能是假的。

【讨论】:

  • 是的,感谢您指出逆向并不总是正确的。加法的范围情况也很有趣。
【解决方案3】:

下面的实验表明答案是你没有那种不相等的极端情况

    static void Main(string[] args)
    {
        Parallel.For(int.MinValue, int.MaxValue, (x) =>
        {
            float r = x;
            // Is the following ALWAYS true?    
            bool equal = r == x;
            if (!equal) Console.WriteLine("Unequal: " + x);                
        });

        Console.WriteLine("Done");
        Console.ReadKey();

        return;
}

转换似乎是合理的

float f = i;

if ((int)f != i)

应遵循相同的规则。这证明 int -> float 和 float -> int 转换是双射。

注意: 实验代码实际上并没有测试边缘情况 int.MaxValue,因为 Parallel.For 的 to 参数是独占的,但我单独测试了该值并且它也通过了测试。

【讨论】:

  • 啊,我明白了,我把它搞砸了——方向。
【解决方案4】:

我运行这段代码没有抛出异常:

for (int x = Int16.MinValue; x < Int16.MaxValue; x++)
{
 float r = x;
 if (r != x)
 {
  throw new Exception("Failed at: " + x);
 }
}

仍在等待(未完成此测试,因为它花费了太长时间,但在运行时从未抛出异常):

for (long x = Int64.MinValue; x < Int64.MaxValue; x++)
{
 float r = x;
 if (r != x)
 {
  throw new Exception("Failed at: " + x);
 }
}

返回并运行您的示例并提出警告,这是输出:

[Exception: not equal 16777217 ?= 1.677722E+07 ?= 16777216]

for (int i = 0; i < int.MaxValue; i++)
{
 float f = i;
 if ((int)f != i) throw new Exception("not equal " + i + " ?= " + f + " ?= " + (int)f);
}

【讨论】:

  • 嘿。祝你好运 :) Guffa 的回答应该让你放心。
  • @pst - 是的,我不再等了,哈哈,这台机器可能需要 非常 很长时间才能运行。请参阅我的编辑,了解从 float 转换回 int 的扩展输出。
【解决方案5】:

我对浮点算术计算的理解是它们是由 CPU 处理的,它完全决定了你的精度。因此,没有确定的值超过该值会导致浮点数失去精度。

例如,我曾认为 x86 架构可以保证最低限度,但事实证明我错了。

【讨论】:

  • 如果我错了,我很想知道为什么我错了。投反对票并逃跑!
  • C# 的行为由标准文档指定。无论目标处理器型号如何,C# 实现都必须符合标准。如果处理器不提供与 C# 要求相对应的指令,则 C# 实现必须以其他方式提供所需的结果,或者通过使用本机指令来完成大部分工作,但通过额外的测试和指令“修复”结果,或者通过在位级别上模拟浮点运算——无论什么是必要的。此外,与 C 不同的是,C# 为 int 和 float 指定了特定范围。
  • @EricPostpischil 也浮动,嗯?感谢知识滴!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-09
  • 2011-05-30
  • 1970-01-01
相关资源
最近更新 更多