Подтвердить что ты не робот

Правило для проверки равенства двух удвоений в С#?

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

Как определить, что означает "разумный"?

Может ли double.Epsilon использоваться каким-то образом?


Обновление

Пара вещей. Как отмечалось в @ho1, в документации для double.Epsilon указано, что, когда дело доходит до сравнения двух удвоений для равенства, вы, вероятно, захотите получить значение, намного большее, чем epsilon. Вот соответствующий абзац из документации:

Два явно эквивалентных числа с плавающей запятой могут не сравниться одинаковыми из-за различий в наименее значимых цифрах. Например, выражение С# (double) 1/3 == (double) 0.33333 не сравнивается с равным, поскольку операция деления на левой стороне имеет максимальную точность, а константа справа - точная только с указанными цифрами. Если вы создадите собственный алгоритм, определяющий, можно ли считать, что два числа с плавающей запятой считаются равными, вы должны использовать значение, большее, чем константа Epsilon, чтобы установить приемлемый абсолютный запас разницы для того, чтобы два значения считались равными. (Как правило, этот разницы во много раз больше, чем Epsilon.) - http://msdn.microsoft.com/en-us/library/system.double.epsilon.aspx

... но вопрос в том, сколько раз больше?

В случае, если это повлияет на ваш ответ, моя конкретная ситуация связана с геометрическими вычислениями (такими как точечные продукты и кросс-продукты с использованием точек и векторов). В некоторых случаях вы делаете разные выводы на основе A == B, A > B или A < B, поэтому я ищу хорошее правило для определения размера окна эквивалентности.

4b9b3361

Ответ 1

Использование double.Epsilon НЕ обязательно работает. double.Epsilon дает наименьшее представимое значение, большее нуля. Однако из-за того, как реализованы числа с плавающей запятой, они имеют меньшую точность, чем дальше от нуля, поэтому проверка разницы double.Epsilon может завершиться неудачно для двух больших чисел, которые очень близки друг к другу.

Подробности: номер с плавающей запятой base-2 представлен как значение - число от 1 до 2 - умноженное на два, поднятое до некоторого показателя. Двойник имеет 52 бита для дробной части значащего плюс 11 бит точности для экспоненты. Если показатель экспоненты является очень большим отрицательным значением, а значение - 0, то вы получаете значения, близкие к double.Epsilon, но если ваш показатель достаточно велик, то даже очень небольшая разница в двух значениях значении приведет к значительному значению больше double.Epsilon.

Для полного обсуждения того, как проверить два числа с плавающей запятой для равенства, см. "Сравнение чисел с плавающей запятой, издание 2012 года" , по Брюс Доусон. Подводя итог, есть три основных метода сравнения:

Используйте абсолютную разницу

Как и в примере Joel Coehoorn, но будьте very осторожны, чтобы выбрать значение, соответствующее соответствующей величине, в отличие от примера Джоэля.

Используйте относительную разницу

Что-то вроде следующего:

if (Math.Abs(a - b) / b <= maxRelativeError)
{
    return true;
}

Однако есть осложнения; вы должны разделить на большее из двух значений, и эта функция плохо работает для значений, близких к нулю, если вы также не добавите проверку на максимальную абсолютную разницу. Подробнее см. В документе.

Использование единиц последнего места

Сравнение с использованием единиц последнего места (ULP) означает проверку последней части значения. (Статья ссылается на это как "Сравнение с использованием целых чисел" ). Это более сложный подход, но очень надежный. В документе представлен исходный код на языке C; для С# вы, вероятно, можете использовать BitConverter.DoubleToInt64Bits.

В ответ на ваше редактирование

"Сколько раз больше?" Это действительно вопрос вашего домена приложения, поэтому, вероятно, почему .NET Framework не предоставляет метод по умолчанию, но мне удалась использовать сравнение ULP с максимальной разницей ULP 4.

Ответ 2

double d1 = GetRandomDouble();
double d2 = GetRandomDouble();

if (Math.Abs(d1 - d2) < double.Epsilon)
{
   // the doubles are equal
}

Обратите внимание, что на практике этот код эквивалентен просто d1 == d2, потому что epsilon определяется как наименьшее возможное положительное значение > 0. Таким образом, у вас никогда не будет значения от 0 до epsilon, и если у вас есть вид ошибка округления/точности, которая вызовет проблемы с оператором ==, вы тоже увидите ее здесь.

Но то, что вы можете сделать, это использовать технику, чтобы определить свой собственный уровень точности — у вас собственный эпсилон. Я ожидал бы, что double.Equals() перегрузится для этой техники, но документа

Ответ 3

Это зависит от того, с какими ценностями вы работаете. Если вы работаете с числами, в которых вам всего 2 десятичной точки, может быть просто использовать 0,001. Иногда вы можете использовать Epsilon, но обычно я не думаю.

Изменить: Удалена ссылка на валюту, так как она отвлекается от точки.

Цитата из MSDN:

Если вы создадите собственный алгоритм, который определяет, можно ли считать, что два числа с плавающей запятой считаются равными, вы должны использовать значение, большее, чем константа Epsilon, для установления приемлемого абсолютного предела разницы для двух значений, которые считаются равными, (Как правило, этот запас разницы во много раз больше, чем Epsilon.)

Ответ 4

поэтому я ищу хорошее правило для определения размера окна эквивалентности.

К сожалению, здесь нет хорошего эмпирического правила. Это полностью определяется потребностями вашей программы. Моделирование физики игрушек может предпочесть очень высокий эпсилон, так что столкновения не будут пропущены. Между тем пакет статистики хотел бы, чтобы низкий эпсилон был более точным. Вам просто нужно настроить его на нужды вашего приложения.

Ответ 5

The question is, but many times greater??

Насколько велика зависимость от входов и от того, сколько операций вы выполняете. В дополнение к соображениям величины, каждая операция увеличивает вашу ошибку округления. Если вы делаете много расчетов по номерам перед их сравнением, это скорее вопрос значительных цифр, а не точности машины.

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

Ответ 6

Вот реализация (сравнение последних бит представления IEEE), упомянутая в "Сравнение чисел с плавающей запятой" , Брюс Доусон, портирована на С#,

Можно утверждать, что Float.NaN!= Float.NaN. Этот код будет обрабатывать все специальные значения поплавка IEEE (NaN, Inf и т.д.) Как равные. Для Unit Testing это, вероятно, то, что вы хотите. Для реального производственного кода вы, вероятно, не должны сравнивать NaN или Inf ни с чем - вы должны выбросить исключение или что-то умное.

Это тот же метод, который используется Google Test (gtest) для С++, а также.

Gtest использует значение по умолчанию 4 для maxUlps.

    public static bool AlmostEqual2sComplement(float A, float B, int maxUlps)
    {
        // Make sure maxUlps is non-negative and small enough that the
        // default NAN won't compare as equal to anything.
        if (!(maxUlps > 0 && maxUlps < 4 * 1024 * 1024)) throw new Exception("maxUlps is invalid");

        Int32 aInt = BitConverter.ToInt32(BitConverter.GetBytes(A),0);
        // Make aInt lexicographically ordered as a twos-complement int
        if (aInt < 0)
            aInt = Int32.MinValue + (-aInt);
        // Make bInt lexicographically ordered as a twos-complement int
        Int32 bInt = BitConverter.ToInt32(BitConverter.GetBytes(B), 0);
        if (bInt < 0)
            bInt = Int32.MinValue + (-bInt);
        Int64 intDiff = Math.Abs(aInt - bInt);
        if (intDiff <= maxUlps)
            return true;
        return false;
    }
    public static bool AlmostEqual2sComplement(double A, double B, int maxUlps)
    {
        // Make sure maxUlps is non-negative and small enough that the
        // default NAN won't compare as equal to anything.
        if (!(maxUlps > 0 && maxUlps < 4 * 1024 * 1024)) throw new Exception("maxUlps is invalid");

        Int64 aInt = BitConverter.ToInt64(BitConverter.GetBytes(A), 0);
        // Make aInt lexicographically ordered as a twos-complement int
        if (aInt < 0)
            aInt = Int64.MinValue + (- aInt);
        // Make bInt lexicographically ordered as a twos-complement int
        Int64 bInt = BitConverter.ToInt64(BitConverter.GetBytes(B), 0);
        if (bInt < 0)
            bInt = Int64.MinValue + (- bInt);
        Int64 intDiff = Math.Abs(aInt - bInt);
        if (intDiff <= maxUlps)
            return true;
        return false;
    }