Сравнение двойных значений в С#
У меня есть double
переменная, называемая x
. В коде x
получает значение 0.1
и я проверяю его в выражении "if", сравнивая x
и 0.1
if (x==0.1)
{
----
}
К сожалению, он не вводит оператор if
-
Должен ли я использовать Double
или double
?
-
В чем причина этого? Можете ли вы предложить решение для этого?
Ответы
Ответ 1
Это стандартная проблема из-за того, как компьютер сохраняет значения с плавающей запятой. Найдите здесь "проблему с плавающей точкой", и вы найдете массу информации.
Короче говоря, float/double не может точно хранить 0.1
. Это будет всегда немного.
Вы можете попробовать использовать decimal
тип, который хранит числа в десятичной нотации. Таким образом, 0.1
будет представиться точно.
Вы хотели знать причину:
Float/double хранятся как двоичные дроби, а не десятичные дроби. Проиллюстрировать:
12.34
в десятичной нотации (то, что мы используем) означает
1 * 101 + 2 * 100 + 3 * 10-1 + 4 * 10-2
Компьютер хранит числа с плавающей точкой таким же образом, за исключением того, что использует базу 2
: 10.01
означает
1 * 21 + 0 * 20 + 0 * 2-1 + 1 * 2-2
Теперь вы, вероятно, знаете, что есть некоторые числа, которые не могут быть полностью представлены нашей десятичной нотацией. Например, 1/3
в десятичной нотации составляет 0.3333333…
То же самое происходит в двоичной нотации, за исключением того, что числа, которые не могут быть представлены точно, различны. Среди них номер 1/10
. В двоичной записи, которая составляет 0.000110011001100…
Поскольку двоичная нотация не может хранить ее точно, она хранится округленным образом. Отсюда и ваша проблема.
Ответ 2
double
и double
являются одинаковыми (double
является псевдонимом для double
) и может использоваться взаимозаменяемо.
Проблема сравнения двойника с другим значением состоит в том, что двойные числа являются приблизительными значениями, а не точными значениями. Поэтому, когда вы устанавливаете x
в 0.1
, он может быть сохранен в действительности как 0.100000001
или что-то в этом роде.
Вместо проверки на равенство вы должны убедиться, что разница меньше определенной минимальной разницы (допуска). Что-то вроде:
if (Math.Abs(x - 0.1) < 0.0000001)
{
...
}
Ответ 3
Вам нужна комбинация Math.Abs
на X-Y
и value
для сравнения.
Вы можете использовать следующий подход метода расширения
public static class DoubleExtensions
{
const double _3 = 0.001;
const double _4 = 0.0001;
const double _5 = 0.00001;
const double _6 = 0.000001;
const double _7 = 0.0000001;
public static bool Equals3DigitPrecision(this double left, double right)
{
return Math.Abs(left - right) < _3;
}
public static bool Equals4DigitPrecision(this double left, double right)
{
return Math.Abs(left - right) < _4;
}
...
Поскольку вы редко вызываете методы в double, кроме ToString
, я считаю, что это довольно безопасное расширение.
Затем вы можете сравнить x
и y
как
if(x.Equals4DigitPrecision(y))
Ответ 4
Сравнение числа с плавающей запятой не всегда может быть выполнено именно из-за округления. Чтобы сравнить
(x == .1)
компьютер действительно сравнивает
(x - .1) vs 0
Результат sybtraction не всегда может быть репрезентален именно из-за того, что количество чисел с плавающей запятой представлено на машине. Поэтому вы получаете некоторое ненулевое значение, а условие оценивается как false
.
Чтобы преодолеть это сравнение
Math.Abs(x- .1) vs some very small threshold ( like 1E-9)
Ответ 5
Из документации:
Точность в сравнениях. Метод Equals следует использовать с осторожностью, поскольку два явно эквивалентных значения могут быть неодинаковыми из-за различной точности двух значений. В следующем примере показано, что значение Double.3333 и значение Double, возвращаемые делением 1 на 3, являются неравными.
...
Вместо того, чтобы сравнивать на равенство, один рекомендуемый метод включает определение приемлемого запаса разницы между двумя значениями (например, 0,01% от одного из значений). Если абсолютное значение разности между этими двумя значениями меньше или равно этому пределу, разница, вероятно, будет связана с различиями в точности и, следовательно, значения, вероятно, будут равны. В следующем примере этот метод используется для сравнения .33333 и 1/3, двух значений Double, которые в предыдущем примере кода оказались неравными.
Так что если вам действительно нужен дубль, вы должны использовать технику, описанную в документации. Если можете, измените его на десятичное. Это будет медленнее, но у вас не будет проблем такого типа.
Ответ 6
Двойные и двойные идентичны.
По этой причине см. http://www.yoda.arachsys.com/csharp/floatingpoint.html.
Короче говоря, двойной тип не является точным, а разница в минутах между "x" и "0.1" отбрасывает его.
Ответ 7
Точное сравнение значений с плавающей запятой известно, что не всегда работает из-за проблемы округления и внутреннего представления.
Попробуйте неточное сравнение:
if (x >= 0.099 && x <= 0.101)
{
}
Другой альтернативой является использование десятичного типа данных.
Ответ 8
Используйте decimal
. У этого нет этой "проблемы".
Ответ 9
1) Должен ли я использовать Double или double???
Double
и Double
- одно и то же. Double
- это просто ключевое слово С#, работающее как псевдоним для класса System.Double
Самое распространенное - использовать псевдонимы! То же самое для string
(System.String
), int
(System.Int32
)
Также см. Таблица встроенных типов (ссылка на С#)
Ответ 10
Двойной (называемый float на некоторых языках) является проблемой с проблемами из-за проблем округления, это хорошо, только если вам нужны приблизительные значения.
Тип данных Decimal делает то, что вы хотите.
Для ссылочных десятичных и десятичных одинаковых в .NET С#, как и двойные и двойные типы, оба они относятся к одному типу (десятичные и двойные очень разные, хотя, как вы видели).
Остерегайтесь того, что тип данных Decimal имеет некоторые связанные с этим расходы, поэтому используйте его с осторожностью, если вы смотрите на циклы и т.д.
Ответ 11
Взяв подсказку из базы Java-кода, попробуйте использовать .CompareTo
и проверьте нулевое сравнение. Это предполагает, что функция .CompareTo
принимает точный учет равенства с плавающей запятой. Например,
System.Math.PI.CompareTo(System.Math.PI) == 0
Этот предикат должен возвращать true
.
Ответ 12
Представления чисел с плавающей запятой общеизвестно неточны (из-за способа хранения чисел с плавающей запятой), например, x может фактически быть 0.0999999999 или 0.100000001, и ваше условие не будет выполнено. Если вы хотите определить, равны ли значения с плавающей точкой, вам нужно указать, равны ли они в пределах определенного допуска.
т.е.
if(Math.Abs(x) - 0.1 < tol) {
// Do something
}
Ответ 13
Как правило:
Двойное представление достаточно хорошо в большинстве случаев, но в некоторых ситуациях может с трудом потерпеть неудачу. Используйте десятичные значения, если вам нужна полная точность (как в финансовых приложениях).
Большинство проблем с двойниками исходит не из прямого сравнения, а из-за накопления нескольких математических операций, которые экспоненциально нарушают значение из-за округления и дробных ошибок (особенно с умножениями и делениями).
Проверьте свою логику, если код:
x = 0.1
if (x == 0.1)
он не должен терпеть неудачу, он просто проваливается, если значение X вычисляется более сложными средствами или операциями, вполне возможно, что метод ToString, используемый отладчиком, использует интеллектуальное округление, возможно, вы можете сделать то же самое (если это слишком рискованно вернуться к использованию десятичной дроби):
if (x.ToString() == "0.1")
Ответ 14
Классный hack, который я нашел, - это использовать метод .GetHashCode()
, который возвращает int, представляющий двойную, т.е.
(0.4d + 0.3d + 0.2d + 0.1d).GetHashCode() //returns -1072693248
1d.GetHashCode() //returns 1072693248
так как вы заметили, что мы можем использовать что-то вроде этого
public static bool AccurateEquality(double first,double second)
{
return Math.Abs(first.GetHashCode()) == Math.Abs(second.GetHashCode());
}
использование:
AccurateEquality((0.4d + 0.3d + 0.2d + 0.1d),1) //returns true
while: (0.4d + 0.3d + 0.2d + 0.1d) == 1d //returns false
Я попробовал его в нескольких случаях и, похоже, хорошо работает.
Ответ 15
// number of digits to be compared
public int n = 12
// n+1 because b/a tends to 1 with n leading digits
public double Epsilon { get; } = Math.Pow(10, -(n+1));
public bool IsEqual(double a, double b)
{
if (Math.Abs(a)<= double.Epsilon || Math.Abs(b) <= double.Epsilon)
return Math.Abs(a - b) <= double.Epsilon;
return Math.Abs(1.0 - a / b) <= Epsilon;
}
Ответ 16
Большинство способов или способов глупого расширения!
public static bool EqualsTo(this double value, double value2)
{
var bytes1 = BitConverter.GetBytes(value);
var bytes2 = BitConverter.GetBytes(value2);
var long1 = BitConverter.ToInt64(bytes1, 0);
var long2 = BitConverter.ToInt64(bytes2, 0);
return long1 == long2;
}