Всегда ли Math.Round (double, decimal) возвращает согласованные результаты - PullRequest
4 голосов
/ 02 декабря 2009

Конечно, никогда не следует сравнивать значения с плавающей запятой, которые получаются в результате вычисления на равенство, но всегда используйте небольшой допуск, например ::

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

Но если я использую Math.Round, могу ли я всегда быть уверен, что результирующее значение будет согласованным, т. Е. Всегда ли следующий 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);
    }
}

Если нет, предоставьте контрпример.

EDIT

Спасибо astander за контрпример, сгенерированный грубой силой, который доказывает, что результат не является "последовательным" в общем случае. Этот контрпример имеет 16 значащих цифр в округленном результате - он также дает сбой таким же образом при таком масштабировании:

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

Однако меня также интересовало бы более математическое объяснение. Дополнительные бонусы за любой из более математических Stackoverflowers, которые могут сделать любое из следующего:

  • Найдите контрпример, в котором округленный результат содержит менее 16 значащих цифр.

  • Определите диапазон значений, для которых округленный результат будет всегда быть «последовательным», как определено здесь (например, все значения, где число значащих цифр в округленном результате

  • Предоставить алгоритмический метод для генерации контрпримеров.

Ответы [ 2 ]

2 голосов
/ 02 декабря 2009

Хотя вычисления с плавающей запятой имеют ограниченную точность и, следовательно, неточны, они являются детерминированными. Поэтому вы всегда должны получать один и тот же результат, если вы используете одни и те же вычисления для одинаковых значений в одном и том же порядке. Таким образом, использование одного и того же метода округления для одного и того же значения приведет к тому же результату.

Проблема с вычислениями с плавающей запятой состоит в том, что результаты двух разных вычислений, которые математически дают один и тот же результат (например, Sqrt(x)*Sqrt(x)==x), могут и, скорее всего, будут отличаться из-за ошибок округления внутри вычислений

2 голосов
/ 02 декабря 2009

Хорошо, это кажется очень техническим вопросом, поэтому я подумал, что грубая сила может сказать нам.

Я попробовал следующее

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

Я думаю, это ответ, который вы искали?

Если нет, пожалуйста, дайте мне знать, чтобы я мог проверить больше.

...