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

我有一个简单的例程,它从浮点值计算宽高比。 因此,对于值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  20) { carryon = false; } } return roundedUpValue + ":" + index; } 

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

从4.1.6节开始,这是一个有趣的C#特性:

可以以比操作的结果类型更高的精度执行浮点运算。 例如,某些硬件体系结构支持“扩展”或“长双”浮点类型,其范围和精度高于double类型,并使用此更高精度类型隐式执行所有浮点运算。 只有在性能成本过高的情况下,才能使这种硬件架构以较低的精度执行浮点运算,而不是要求实现失去性能和精度,C#允许更高精度的类型用于所有浮点运算。 除了提供更精确的结果外,这很少有任何可衡量的影响。

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

一些相关问题:

  • C#XNA Visual Studio:“发布”和“调试”模式之间的区别?

  • CLR JIT优化是否违反了因果关系?

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

 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); } 

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

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

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

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

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

这样的事情可能是原因。

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

杂项其他建议:

  • 双打比漂浮更好。
  • (double) 0.1投射是不必要的。
  • 如果你无法弄清楚宽高比是什么,可能想要抛出exception。
  • 如果您在找到答案后立即返回,您可以抛弃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"); } 

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

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