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

JD.*_*JD. 9 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;
    }
Run Code Online (Sandbox Code Playgroud)

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

Eri*_*ert 24

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

可以以比操作的结果类型更高的精度执行浮点运算.例如,某些硬件体系结构支持"扩展"或"长双"浮点类型,其范围和精度比double类型更大,并使用此更高精度类型隐式执行所有浮点运算.只有在性能成本过高的情况下,才能使这种硬件架构以较低的精度执行浮点运算,而不是要求实现放弃性能和精度,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.
};
Run Code Online (Sandbox Code Playgroud)

现在你的实施是

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);
}
Run Code Online (Sandbox Code Playgroud)

请注意代码现在如何读取非常类似于您尝试执行的操作:从此常用比率列表中,选择给定比率与公共比率之间的差异最小化的那个.


Chr*_*isF 8

我不会使用浮点数,除非我真的不得不这样做.由于舍入错误,他们太容易发生这种事情.

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

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

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

这样的事情可能是原因.


Joh*_*ica 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");
}
Run Code Online (Sandbox Code Playgroud)