2009-09-09 4 views
41

Я использую C#.Сравнение двойных значений в C#

У меня есть двойная переменная, называемая x. В коде х получает присваивается значение 0,1 и проверить его в «если» заявление сравнения х и 0,1

if(x==0.1) 
{ 
---- 
} 

К сожалению, это не входит, если заявление

1) Должен ли я использовать Двойной или двойной ???

2) В чем причина этого? Можете ли вы предложить решение для этого?

+2

Можете вы добавить свое объявление о двойнике, пожалуйста? –

+0

http://stackoverflow.com/questions/753948/why-is-floating-point-arithmetic-in-c-imprecise –

+0

Всестороннее чтение по теме: [Что должен знать каждый компьютерный ученый о арифметике с плавающей запятой] (http://docs.sun.com/source/806-3568/ncg_goldberg.html) –

ответ

66

Это стандартная проблема из-за того, как компьютер хранит значения с плавающей точкой. Найдите здесь «проблему с плавающей точкой», и вы найдете массу информации.

Вкратце - поплавок/двойной не может точно хранить 0,1. Это будет всегда немного.

Вы можете попробовать использовать тип decimal, который хранит числа в десятичной системе. Таким образом, 0,1 будет представиться точно.


Вы хотели бы знать причину:

Float/двойной хранятся в виде двоичных дробей, а не десятичные дроби. В качестве иллюстрации:

12.34 в десятичной системе счисления (то, что мы используем) означает 1 * 10 + 2 * 10 + 3 * 10 -1 + 4 * 10 -2 . Компьютер хранит числа с плавающей точкой таким же образом, за исключением того, что использует базу 2: 10.01 означает 1 * 2 + 0 * 2 + 0 * 2 -1 + 1 * 2 -2

Теперь, вы, наверное, знаете, что есть некоторые цифры, которые не могут быть представлены в полном объеме с нашей десятичной нотации. Например, 1/3 в десятичной нотации составляет 0,33333333 ... То же самое происходит в двоичной нотации, за исключением того, что цифры, которые не могут быть представлены точно, различны. Среди них номер 1/10. В двоичной записи, которая составляет 0,000110011001100 ...

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

+10

Я полностью понимаю, что вы говорите выше. НО, почему, если он написал 'x = 0.01;', чтобы присваивать 'x', а затем сравнивался с литералом' 0.01', не было бы фактическое базовое двоичное значение 'x' и' 0.01' одинаковым (опять же, предполагая, что для присвоения 'x' не использовались никакие вычисления. Поэтому '(x == 0,01)' должен работать, не так ли? Однако, если вы выполните некоторую арифметику с 'x', все ставки будут выключены. Кроме того, я был бы более явным: 'if (x == 0.01d)' явно указывать, что '0.01' является двойным (и не конвертирует' 0.01' из 'float' в' double'), что может были проблемой. – fourpastmidnight

+0

@fourpastmidnight: я не могу воспроизвести то, что вы говорите (.NET 4). Я получаю 'true', если я сравниваю' 0.01' с '0.01'. –

+1

Да, если вы делаете 'x = 0.01; if (x == 0,01) ", то сравнение должно быть истинным. Ну, если предположить, что нет никаких неявных преобразований типа данных с плавающей точкой в ​​двойное (хотя, возможно, даже тогда). Но ОП не уточнил, как он получил первый «0.01». Весьма вероятно, что он исходит из расчета. –

10

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

(x == .1) 

компьютер действительно сравнивает

(x - .1) vs 0 

Результат sybtraction не всегда может быть represeted именно из-за того, как число с плавающей запятой представлены на машине. Поэтому вы получаете некоторое ненулевое значение и условие оценивается до false.

Чтобы преодолеть это сравнить

Math.Abs(x- .1) vs some very small threshold (like 1E-9) 
+0

Не могли бы вы ясно проиллюстрировать пример ??? Каково решение, так что мне нужно изменить свое утверждение ?? –

3

Использование decimal. У этого нет этой «проблемы».

+3

Операции с десятичными значениями намного медленнее. Мы не должны всегда использовать тип decimal только потому, что он «не имеет этой проблемы». Оба типа имеют собственные виды использования. –

0

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

т.е.

if(x - 0.1 < tol) 
+8

И выбросьте Math.Abs, в случае, если x немного меньше 0,1. Ваш код примет x == -10. –

3

Точное сравнение значений с плавающей запятой известно, что не всегда работают из-за проблемы округления и внутреннего представления.

Попробуйте неточное сравнение:

if (x >= 0.099 && x <= 0.101) 
{ 
} 

Другой альтернативой является использование типа десятичного данных.

26

double и Double те же самые (double - это псевдоним для Double) и могут использоваться взаимозаменяемо.

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

Вместо проверки на равенство вы должны убедиться, что разница меньше определенной минимальной разницы (допуска). Что-то вроде:

if (Math.Abs(x - 0.1) < 0.0000001) 
{ 
    ... 
} 
+2

Это должно быть 'if (Math.Abs ​​(x - 0.1) <0.0001) ' – trev

+0

Хорошая точка! Обновит мой ответ –

1

1) Должен ли я использовать двойные или двойной ???

Double и double такое же. double - это просто ключевое слово C#, работающее как псевдоним для класса System.Double Наиболее распространенная вещь - использовать псевдонимы! То же самое для string (System.String) int (System.Int32)

Также см Built-In Types Table (C# Reference)

0

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

Тип данных десятичных чисел делает то, что вы хотите.

Для ссылочных десятичных и десятичных чисел одинаковы в .NET C#, как и двойные и двойные типы, оба они относятся к одному и тому же типу (десятичные и двойные очень разные, хотя, как вы видели).

Учтите, что тип данных Decimal имеет некоторые расходы, связанные с ним, так что используйте его с осторожностью, если вы смотрите на петлях и т.д.

4

От documentation:

точности в Сравнения Метод Equals следует использовать с осторожностью, поскольку два явно эквивалентных значения могут быть неравными из-за различной точности этих двух значений. В следующем примере показано, что двойное значение .3333 и Double возвращаются путем деления 1 на 3, являются неравными.

...

Вместо того, чтобы сравнивать на равенство, один рекомендуется методика включает определение приемлемого запаса разности между двумя значениями (например, .01% одного из значений). Если абсолютное значение разницы между этими двумя значениями меньше или равно этому маржу, разница, вероятно, будет обусловлена ​​различиями в точности, и поэтому значения, вероятно, будут равны. В следующем примере этот метод используется для сравнения .33333 и 1/3, двух значений Double, которые, как было показано в предыдущем примере кода, были неравными.

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

0

По общему правилу:

Двойного представление достаточно хорошо в большинстве случаев, но может плачевно потерпеть неудачу в некоторых ситуациях. Используйте десятичные значения, если вам нужна полная точность (как в финансовых приложениях).

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

Проверьте свою логику, если код:

x = 0.1 

if (x == 0.1) 

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

if (x.ToString() == "0.1") 
9

Вам необходимо сочетание 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; 
     } 

     ... 

Поскольку вы редко вызывают методы в два раза, за исключением ToString Я считаю его довольно безопасное расширение.

Тогда вы можете сравнить x и y как

if(x.Equals4DigitPrecision(y))

+0

Это может быть приятное расширение для Double struct! –

-7

Большинство выше способов или следующие глупой метод расширения!

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; 
} 
+0

Это проверяет, имеет ли два удвоения одно и то же двоичное представление, которое вообще не отвечает на вопрос. –

+0

Все данные в компьютерном мире сохраняются в двоичной форме! –

+1

Ваш код не пытается ответить на вопрос «почему эти значения не сравниваются одинаково, даже если они кажутся, и как я могу это решить?» Несмотря на титул, вопрос заключается не в том, чтобы «дать мне все возможное, чтобы сравнить двойники». –

0

Холодные обходной я нашел заключается в использовании .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

в то время как: (0.4d + 0.3d + 0.2d + 0.1d) == 1d //returns false

Я попробовал это на нескольких случаях, и, похоже, он работает хорошо.

0

Принимая подсказку с базы кода Java, попробуйте использовать .CompareTo и проверьте нулевое сравнение. Это предполагает, что функция .CompareTo принимает во внимание справедливость равенства с плавающей точкой. Например,

System.Math.PI.CompareTo(System.Math.PI) == 0 

Этот предикат должен возвращать true.

Смежные вопросы