为什么这个浮点计算在不同的机器上给出不同的结果?

时间:2010-02-26 14:50:42

标签: c#

我有一个简单的例程,它根据浮点值计算宽高比。因此,对于值1.77777779,例程返回字符串“16:9”。我已在我的机器上测试了它,它运行正常。

例程如下:

    public string AspectRatioAsString(float f)
    {
        bool carryon = true;
        int index = 0;
        double roundedUpValue = 0;
        while (carryon)
        {
            index++;
            float upper = index * f;

            roundedUpValue = Math.Ceiling(upper);

            if (roundedUpValue - upper <= (double)0.1 || index > 20)
            {
                carryon = false;
            }
        }

        return roundedUpValue + ":" + index;
    }

现在在另一台机器上,我得到了完全不同的结果。所以在我的机器上,1.77777779给出“16:9”但是在另一台机器上我得到“38:21”。

4 个答案:

答案 0 :(得分:24)

这是C#规范的一个有趣的部分,来自4.1.6节:

  

浮点运算可能是   执行精度高于   操作的结果类型。对于   例如,一些硬件架构   支持“扩展”或“长双”   具有更大范围的浮点型   和双精度相比,   并隐含地执行所有   使用它的浮点运算   精度更高的类型。只在   性能成本过高就可以了   硬件架构   用。执行浮点运算   精度较低,而不是   要求实施放弃   性能和精度,C#   允许更高精度的类型   用于所有浮点数   操作。除了提供更多   精确的结果,这很少有   可衡量的影响。

由于对Ceiling的调用,这可能是“可测量的效果”之一。正如其他人所指出的那样,取浮点数的上限,将差值0.000000002放大9个数量级,因为它将15.99999999变为16,将16.00000001变为17.在操作之前略有不同的两个数字之后大不相同;微不足道的差异可能是由于不同的机器在其浮点运算中可能具有或多或少的“额外精度”。

一些相关问题:

解决如何从浮点数计算宽高比的具体问题:我可能以完全不同的方式解决这个问题。我会做一个这样的桌子:

struct Ratio
{
    public int X { get; private set; }
    public int Y { get; private set; }
    public Ratio (int x, int y) : this()
    {
        this.X = x;
        this.Y = y;
    }
    public double AsDouble() { return (double)X / (double)Y; }
}

Ratio[] commonRatios = { 
   new Ratio(16, 9),
   new Ratio(4, 3), 
   // ... and so on, maybe the few hundred most common ratios here. 
   // since you are pinning results to be less than 20, there cannot possibly
   // be more than a few hundred.
};

现在你的实现是

public string AspectRatioAsString(double ratio)      
{ 
    var results = from commonRatio in commonRatios
                  select new {
                      Ratio = commonRatio, 
                      Diff = Math.Abs(ratio - commonRatio.AsDouble())};

    var smallestResult = results.Min(x=>x.Diff);

    return String.Format("{0}:{1}", smallestResult.Ratio.X, smallestResult.Ratio.Y);
}

注意代码现在的读取方式与您尝试执行的操作非常相似:从这个常用比率列表中,选择给定比率与公共比率之间的差异最小化的那个。

答案 1 :(得分:8)

我不会使用浮点数,除非我真的不得不这样做。由于舍入错误,他们太容易出现这种情况。

您可以更改代码以双精度工作吗? (十进制将是矫枉过正)。如果你这样做,它会给出更一致的结果吗?

至于为什么它在不同的机器上有所不同,两台机器之间有什么区别?

  • 32位vs 64位?
  • Windows 7 vs Vista vs XP?
  • 英特尔与AMD处理器? (感谢Oded)

这样的东西可能是的原因。

答案 2 :(得分:5)

尝试使用Math.Round代替Math.Ceiling。如果您以16.0000001结束并向上舍入,则会错误地丢弃该答案。

杂项其他建议:

  • 双打比漂浮物好。
  • (double) 0.1施放是不必要的。
  • 如果无法弄清楚宽高比是多少,可能会抛出异常。
  • 如果您在找到答案后立即返回,则可以放弃carryon变量。
  • 可能更精确的检查是计算每个猜测的纵横比,并将其与输入进行比较。

修订(未经测试):

public string AspectRatioAsString(double ratio)
{
    for (int height = 1; height <= 20; ++height)
    {
        int    width = (int) Math.Round(height * ratio);
        double guess = (double) width / height;

        if (Math.Abs(guess - ratio) <= 0.01)
        {
            return width + ":" + height;
        }
    }

    throw ArgumentException("Invalid aspect ratio", "ratio");
}

答案 3 :(得分:2)

当index为9时,您可能会得到类似上限= 16.0000001或上限= 15.9999999的内容。你得到哪一个将取决于舍入错误,这可能在不同的机器上有所不同。当它是15.999999时,roundedUpValue - upper <= 0.1为真,循环结束。当它是16.0000001时,roundedUpValue - upper <= 0.1为假,循环一直持续到index > 20

相反,也许您应该尝试将上部舍入到最接近的整数,并检查它与该整数的差值的绝对值是否很小。换句话说,请使用if (Math.Abs(Math.Round(upper) - upper) <= (double)0.0001 || index > 20)

之类的内容