Как найти, если две переменные приблизительно равны?
Я пишу модульные тесты, которые проверяют вычисления в базе данных, и есть много округления и усечения, и это означает, что иногда цифры немного отключаются.
Когда вы проверяете, я нахожу много раз, когда все пройдет, но скажу, что они терпят неудачу - например, цифра будет 1, и я получаю 0.999999
Я имею в виду, я мог бы обойти все в целое число, но поскольку я использую много рандомизированных образцов, в конце концов я собираюсь получить что-то вроде этого
10,5
+10,4999999999
один округляется до 10, другой округляется до 11.
Как мне решить эту проблему, когда мне нужно что-то примерно правильное?
Ответы
Ответ 1
Определите значение допуска (например, "эпсилон" ), например, 0,00001, а затем используйте для сравнения разности так:
if (Math.Abs(a - b) < epsilon)
{
// Values are within specified tolerance of each other....
}
[Вы можете использовать Double.Epsilon
, но вам придется использовать множитель.]
Еще лучше, напишите метод расширения, чтобы сделать то же самое. У нас есть что-то вроде Assert.AreSimiliar(a,b)
в наших модульных тестах.
Ответ 2
Вы можете предоставить функцию, которая включает параметр для приемлемой разницы между двумя значениями. Например
// close is good for horseshoes, hand grenades, nuclear weapons, and doubles
static bool CloseEnoughForMe(double value1, double value2, double acceptableDifference)
{
return Math.Abs(value1 - value2) <= acceptableDifference;
}
И затем назовите его
double value1 = 24.5;
double value2 = 24.4999;
bool equalValues = CloseEnoughForMe(value1, value2, 0.001);
Если вы хотите быть немного профессиональным, вы можете вызвать функцию ApproximatelyEquals
или что-то в этом направлении.
static bool ApproximatelyEquals(this double value1, double value2, double acceptableDifference)
Ответ 3
Я не проверял, в какую версию MS Test были добавлены, но в v10.0.0.0. Методы Assert.AreEqual имеют перегрузки, которые принимают параметр дельта и делают приблизительное сравнение.
т.е.
//
// Summary:
// Verifies that two specified doubles are equal, or within the specified accuracy
// of each other. The assertion fails if they are not within the specified accuracy
// of each other.
//
// Parameters:
// expected:
// The first double to compare. This is the double the unit test expects.
//
// actual:
// The second double to compare. This is the double the unit test produced.
//
// delta:
// The required accuracy. The assertion will fail only if expected is different
// from actual by more than delta.
//
// Exceptions:
// Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException:
// expected is different from actual by more than delta.
public static void AreEqual(double expected, double actual, double delta);
Ответ 4
Один из способов сравнения чисел с плавающей запятой - сравнить количество отображаемых чисел с плавающей запятой. Это решение равнодушно к размеру чисел, и поэтому вам не нужно беспокоиться о размере "эпсилон", упомянутом в других ответах.
Описание алгоритма можно найти здесь (в конце - функция AlmostEqual2sComplement), и вот моя версия С#.
UPDATE:
Предоставленная ссылка устарела. Новая версия, которая содержит некоторые улучшения и исправления, здесь
public static class DoubleComparerExtensions
{
public static bool AlmostEquals(this double left, double right, long representationTolerance)
{
long leftAsBits = left.ToBits2Complement();
long rightAsBits = right.ToBits2Complement();
long floatingPointRepresentationsDiff = Math.Abs(leftAsBits - rightAsBits);
return (floatingPointRepresentationsDiff <= representationTolerance);
}
private static unsafe long ToBits2Complement(this double value)
{
double* valueAsDoublePtr = &value;
long* valueAsLongPtr = (long*)valueAsDoublePtr;
long valueAsLong = *valueAsLongPtr;
return valueAsLong < 0
? (long)(0x8000000000000000 - (ulong)valueAsLong)
: valueAsLong;
}
}
Если вы хотите сравнить поплавки, измените все double
на float
, все long
на int
и 0x8000000000000000
на 0x80000000
.
С помощью параметра representationTolerance
вы можете указать, насколько велика ошибка. Более высокое значение означает, что принимается большая ошибка. Обычно я использую значение 10.
Ответ 5
Вопрос заключался в том, как утверждать, что в модульном тестировании что-то было почти равным. Вы утверждаете, что что-то почти равное, используя встроенную функцию Assert.AreEqual
. Например:
Assert.AreEqual(expected: 3.5, actual : 3.4999999, delta:0.1);
Этот тест пройдет. Проблема решена и без необходимости писать свою собственную функцию!