Проблемы с проверкой равенства двух двойников в .NET - что не так с этим методом? - PullRequest
6 голосов
/ 05 августа 2010

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

Цель функции, которую я собираюсь вставить, состоит в том, чтобы сравнить два двойных значения с 4 цифрами точности и вернуть результаты сравнения.Для иллюстрации мои значения:

Dim double1 As Double = 0.14625000000000002 ' The result of a calculation
Dim double2 As Double = 0.14625 ' A value that was looked up in a DB

Если я передам их в эту функцию:

Public Shared Function AreEqual(ByVal double1 As Double, ByVal double2 As Double) As Boolean

    Return (CType(double1 * 10000, Long) = CType(double2 * 10000, Long))

End Function

сравнение не удастся.После умножения и приведения к Long, сравнение заканчивается следующим образом:

Return 1463 = 1462

Я вроде отвечаю на свой вопрос здесь, но я вижу, что double1 находится в пределах точности двойного (17 цифр) и актерский состав работает правильно.

Мой первый настоящий вопрос: если я поменяю строку выше на следующую, почему она работает правильно (возвращает True)?

Return (CType(CType(double1, Decimal) * 10000, Long) = _
    CType(CType(double2, Decimal) * 10000, Long))

Не Decimalеще больше точности, поэтому приведение к Long должно быть равно 1463, а возвращаемое сравнение False?Я думаю, у меня есть мозги пердеть на этом материале ...

Во-вторых, если бы кто-то изменил эту функцию, чтобы сделать сравнение я ищу более точным или менее подверженным ошибкам, вы бы порекомендовали изменитьэто к чему-то намного проще?Например:

Return (Math.Abs(double1 - double2) < 0.0001)

Буду ли я сумасшедшим, чтобы попробовать что-то вроде:

Return (double1.ToString("N5").Equals(double2.ToString("N5")))

(Я бы никогда не сделал выше, мне просто любопытно, как вы реагируете. Это было быужасно неэффективно в моем приложении.)

В любом случае, если бы кто-то мог пролить свет на разницу, которую я вижу между приведением Double с и Decimal с к Long, это было бы здорово.

Спасибо!

Ответы [ 3 ]

4 голосов
3 голосов
/ 05 августа 2010

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

Я бы настоятельно рекомендовал вам написать код сравнения без приведения. Ваша линия Math.Abs ​​в порядке.

По поводу вашего первого вопроса:

Мой первый настоящий вопрос: если я изменюсь строка выше к следующему, почему он работает правильно (возвращает True)?

Причина в том, что приведение от двойного к десятичному теряет точность, что приводит к сравнению от 0,1425 до 0,1425.

2 голосов
/ 05 августа 2010

Когда вы используете CType, вы говорите своей программе: «Мне все равно, как вы округляете числа; просто убедитесь, что результат этого другого типа». Это не совсем то, что вы хотите сказать своей программе при сравнении чисел.

Сравнение чисел с плавающей запятой - это боль, и я бы никогда не стал доверять функции Round на любом языке, если бы вы не знали точно , как она ведет себя (например, иногда она округляет .5 вверх, а иногда вниз , в зависимости от предыдущего номера ... это беспорядок).

В .NET я мог бы использовать Math.Truncate() после умножения моего двойного значения. Итак, Math.Truncate(.14625 * 10000) (то есть Math.Truncate(1462.5)) будет равно 1462, потому что оно избавляется от всех десятичных значений. Используя Truncate() с данными из вашего примера, оба значения в конечном итоге будут равны, потому что 1) они остаются двойными и 2) вы убедились, что десятичное число было удалено из каждого.

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

...