Does Math.Round(double, decimal) always return con

2020-07-18 04:08发布

问题:

Of course one should never compare floating point values that result from a calculation for equality, but always use a small tolerance, e.g.:

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

But if I use Math.Round can I always be sure that the resulting value will be consistent, i.e. will the following Assert always succeed, even when the rounded value is a value that can't be represented exactly by a double?

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

If not please provide a counterexample.

EDIT

Thanks to astander for a counterexample generated by brute force that proves the result is not "consistent" in the general case. This counterexample has 16 significant digits in the rounded result - it also fails in the same way when scaled thus:

        double value1 = 10546080000034341D;
        double value2 = 10546080000034257D;
        int decimals = 0;
        TestRound(value1, value2, decimals);

However I'd also be interested in a more mathematical explanation. Bonus upvotes for any of the more mathematical Stackoverflowers who can do any of the following:

  • Find a counterexample where the rounded result has fewer than 16 significant digits.

  • Identify a range of values for which the rounded result will always be "consistent" as defined here (e.g. all values where the number of significant digits in the rounded result is < N).

  • Provide an algorithmic method to generate counterexamples.

回答1:

OK, this seems like a very technical question, so I thought brute force might tell us.

I tried the following

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

I placed a breakpoint on the "string s = "";" to chcek when it enters this section,

and it entered with the following values

value1 = 1.0546080000034341
value2 = 1.0546080000034257
decimals = 15
roundedValue1 = 1.0546080000034339
roundedValue2 = 1.0546080000034259
roundedValue1.ToString(format) = 1.054608000003430
roundedValue2.ToString(format) = 1.054608000003430

I think this is the answer you were looking for?

If not, please let me know, so i can test more.



回答2:

While floating point calculations have a limited precision and are therefore inexact, they are deterministic. Therefore you should always get the same result if you use the same calculations on the same values in the same order. So using the same rounding method on the same value will result in the same result.

The problem with floating point calculations is that the results of two different calculations which mathematically give the same result (e.q. Sqrt(x)*Sqrt(x)==x) can and most likely will differ due to rounding errors inside the calculations