Math.Round(double, decimal)是否总是返回一致的结果

4

当然,我们不能将计算结果得到的浮点数进行直接比较判断是否相等,而应该使用一个小的容差值,例如:

double value1 = ... 
double value2 = ...
if (Math.Abs(value1 - value2) < tolerance * Math.Abs(value1))
{
    ... values are close enough
}

但是如果我使用Math.Round,我能否始终确信结果值是一致的,即使四舍五入后的值是一个不能被double精确表示的值,以下Assert语句是否总是成功?

public static void TestRound(double value1, double value2, int decimals)
{
    double roundedValue1 = Math.Round(value1, decimals);
    double roundedValue2 = Math.Round(value2, decimals);

    string format = "N" + decimals.ToString();
    if (roundedValue1.ToString(format) == roundedValue2.ToString(format))
    {
        // They rounded to the same value, was the rounding exact?
        Debug.Assert(roundedValue1 == roundedValue2);
    }
}

如果不是这样,请提供一个反例。
编辑
感谢astander提供了一个通过暴力生成的反例,证明了该结果在一般情况下不是“一致”的。这个反例在四舍五入后有16个有效数字 - 当按比例缩放时,它也以同样的方式失败。
        double value1 = 10546080000034341D;
        double value2 = 10546080000034257D;
        int decimals = 0;
        TestRound(value1, value2, decimals);

然而,我也对更数学的解释感兴趣。任何能够完成以下任务的数学Stackoverflower都可以获得额外的赞:

  • 找到一个反例,其中舍入结果的有效数字少于16位。

  • 确定一组值范围,在这个范围内,舍入结果将始终“一致”,如此处定义的(例如,所有舍入结果中有效数字的数量< N的所有值)。

  • 提供一种算法方法来生成反例。


Eric Lippert 写了很多关于浮点数的文章,它们可能会帮助你理解一些问题.. http://blogs.msdn.com/ericlippert/archive/tags/Floating+Point+Arithmetic/default.aspx - flesh
2个回答

2

好的,这似乎是一个非常技术性的问题,我认为粗暴的方法可能会告诉我们答案。

我尝试了以下方法:

public static void TestRound(double value1, double value2, int decimals)
{
    double roundedValue1 = Math.Round(value1, decimals);
    double roundedValue2 = Math.Round(value2, decimals);

    string format = "N" + decimals.ToString();
    if (roundedValue1.ToString(format) == roundedValue2.ToString(format))
    {
        // They rounded to the same value, was the rounding exact?
        if (roundedValue1 != roundedValue2)
        {
            string s = "";
        }
    }
}
private void button1_Click(object sender, EventArgs e)
{
    for (double d = 0, inc = .000001; d < 1000; d += inc)
        for (int p = 0; p <= 15; p++)
            TestRound(Math.Pow(Math.Pow(d, inc), 1 / inc), d, p);
}

我在“string s =“”;”上设置了一个断点,以检查它何时进入此部分,并且它使用以下值进入了。
value1 = 1.0546080000034341
value2 = 1.0546080000034257
decimals = 15
roundedValue1 = 1.0546080000034339
roundedValue2 = 1.0546080000034259
roundedValue1.ToString(format) = 1.054608000003430
roundedValue2.ToString(format) = 1.054608000003430

我认为这就是你正在寻找的答案?

如果不是,请告诉我,这样我可以进行更多测试。


这是我的问题还是ToString(“N15”)的缺陷比Round更大?这些双精度数在小数点后15位四舍五入为不同值,但ToString(“N15”)将它们舍入为小数点后14位加零。 - Rawling

2

尽管浮点数计算具有有限的精度并因此不太准确,但它们是确定性的。因此,如果您在相同值上以相同的顺序使用相同的计算,则应始终获得相同的结果。因此,在相同的值上使用相同的舍入方法将产生相同的结果。

浮点数计算的问题在于,两个不同计算的结果在数学上给出相同的结果(例如Sqrt(x)*Sqrt(x)==x),由于计算中的舍入误差而可能会有所不同。


当然可以,但我说的是在两个不同的值上使用相同的舍入方法,这将得到“相同”的结果。请参考astander的回答。 - Joe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接