Вопрос по java – Что не так с использованием == для сравнения чисел в Java?

162

В соответствии сэта страница java.sun == является оператором сравнения равенства для чисел с плавающей точкой в Java.

Тем не менее, когда я набираю этот код:

if(sectionID == currentSectionID)

В моем редакторе и запустив статический анализ, я получаю: & quot; значения с плавающей запятой по сравнению с == & quot;

Что не так с использованием== сравнить значения с плавающей запятой? Как правильно это сделать? & # XA0;

Обязательная ссылка :-)What Every Computer Scientist Should Know About Floating-Point Arithmetic nos
Да, это был случайный пример, или вы фактически используете поплавки в качестве идентификаторов? Есть ли причина? Per Wiklander
Поскольку сравнение чисел с == проблематично, неразумно использовать их в качестве идентификаторов; имена в вашем примере кода показывают, что вы делаете; длинные целые числа (longs) предпочтительны, и де-факто стандарт для идентификаторов. Carl Manaster
"for float fields, use the Float.compare method; and for double fields, use Double.compare. The special treatment of float and double fields is made necessary by the existence of Float.NaN, -0.0f and the analogous double constants; see the Float.equals documentation for details." (Джошуа Блох: Эффективная Ява) lbalazscs

Ваш Ответ

21   ответ
3

Если вам * нужно * использовать поплавки, может пригодиться ключевое слово strictfp.

http://en.wikipedia.org/wiki/strictfp

Error: User Rate Limit Exceeded
3

Вы можете хотеть, чтобы это было ==, но 123.4444444444443! = 123.4444444444442

4

Прежде всего, они плавают или плавают? Если один из них является Float, вы должны использовать метод equals (). Также, вероятно, лучше для нас, например, статический метод Float.compare.

53

Значения с плавающей точкой могут быть немного отключены, поэтому они могут не отображаться как абсолютно равные. Например, установив число с плавающей запятой на «6.1» и затем, распечатав его снова, вы можете получить сообщенное значение, например, "6.099999904632568359375". Это имеет основополагающее значение для работы поплавков; следовательно, вы не хотите сравнивать их, используя равенство, а скорее сравнение в пределах диапазона, то есть, если разность числа с плавающей запятой и числа, с которым вы хотите сравнивать, меньше определенного абсолютного значения.

это статья о реестре дает хороший обзор того, почему это так; полезное и интересное чтение.

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededvery specific hardware.
Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededvaluesError: User Rate Limit ExceededcalculationError: User Rate Limit ExceededliteralError: User Rate Limit Exceededcalculation.
6

Вот очень долгое (но, надеюсь, полезное) обсуждение этой и многих других проблем с плавающей запятой, с которыми вы можете столкнуться:Что каждый ученый должен знать об арифметике с плавающей точкой

0

Правильный путь будет

java.lang.Float.compare(float1, float2)
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededFloat.compare(1.1 + 2.2, 3.3) != 0
2

Имеете ли вы дело с внешним кодом, который будет использовать числа с плавающей запятой для вещей с именами sectionID и currentSectionID? Просто любопытно.

@ Билл К: & quot; Бинарное представление с плавающей точкой раздражает. & Quot; Как так? Как бы вы сделали это лучше? Есть определенные числа, которые не могут быть представлены в любой базе должным образом, потому что они никогда не заканчиваются. Пи хороший пример. Вы можете только приблизить это. Если у вас есть лучшее решение, обратитесь в Intel.

11

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

  1. There is nothing inherently wrong in using floats as IDs in standard-compliant JVM [*]. If you simply set the float ID to x, do nothing with it (i.e. no arithmetics) and later test for y == x, you'll be fine. Also there is nothing wrong in using them as keys in a HashMap. What you cannot do is assume equalities like x == (x - y) + y, etc. This being said, people usually use integer types as IDs, and you can observe that most people here are put off by this code, so for practical reasons, it is better to adhere to conventions. Note that there are as many different double values as there are long values, so you gain nothing by using double. Also, generating "next available ID" can be tricky with doubles and requires some knowledge of the floating-point arithmetic. Not worth the trouble.

  2. On the other hand, relying on numerical equality of the results of two mathematically equivalent computations is risky. This is because of the rounding errors and loss of precision when converting from decimal to binary representation. This has been discussed to death on SO.

[*] Когда я сказал «совместимая со стандартом JVM» Я хотел исключить некоторые реализации JVM с поврежденным мозгом. Увидетьэтот.

Error: User Rate Limit Exceeded==Error: User Rate Limit ExceededequalsError: User Rate Limit Exceeded
floatError: User Rate Limit ExceededequalsError: User Rate Limit Exceeded
Error: User Rate Limit ExceededFloatError: User Rate Limit Exceededfloat.
Error: User Rate Limit ExceededFloatError: User Rate Limit ExceededfloatError: User Rate Limit Exceeded==Error: User Rate Limit ExceededNaNError: User Rate Limit Exceeded
Error: User Rate Limit ExceededequalsError: User Rate Limit ExceededFloatError: User Rate Limit Exceededfloat.
22

Просто чтобы объяснить причину того, что говорят все остальные.

Бинарное представление с плавающей точкой раздражает.

В двоичном коде большинство программистов знают соотношение между 1b = 1d, 10b = 2d, 100b = 4d, 1000b = 8d

Ну, это работает и по-другому.

.1b = .5d, .01b = .25d, .001b = .125, ...

Проблема в том, что нет точного способа представления большинства десятичных чисел, таких как .1, .2, .3 и т. Д. Все, что вы можете сделать, это приблизительное в двоичном виде. Система делает небольшое округление, когда числа печатаются так, чтобы отображать .1 вместо .10000000000001 или .999999999999 (которые, вероятно, так же близки к сохраненному представлению, как и .1)

Редактировать из комментария: причина этого является нашими ожиданиями. Мы полностью ожидаем, что 2/3 будет размытым в некоторый момент, когда мы преобразуем его в десятичное число, или .7, или .67, или .666667. Но мы не ожидаем автоматического округления .1 таким же образом, как 2/3 - и это именно то, что происходит.

Кстати, если вам любопытно, то число, которое оно хранит внутри, является чисто двоичным представлением с использованием двоичного кода «Scientific Notation». Таким образом, если вы скажете ему, чтобы хранить десятичное число 10,75d, он будет хранить 1010b для 10 и .11b для десятичного. Таким образом, он будет хранить .101011, а затем в конце сохранит несколько битов, чтобы сказать: переместите десятичную точку на четыре позиции вправо.

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

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
7

Это проблема не только для Java. Использование == для сравнения двух чисел с плавающей запятой / двойных / любого десятичного числа может потенциально вызвать проблемы из-за способа их хранения. Число с плавающей запятой одинарной точности (согласно стандарту IEEE 754) имеет 32 бита, распределенных следующим образом:

1 бит - знак (0 = положительный, 1 = отрицательный)
8 битов - экспонента (специальное (смещение-127) представление x в 2 ^ x)
23 бита - Мантиса. Фактический номер, который сохраняется.

Богомол - то, что вызывает проблему. Это похоже на научную запись, только число в базе 2 (двоичное) выглядит как 1.110011 x 2 ^ 5 или что-то подобное. Но в двоичном коде первый 1 всегда равен 1 (кроме представления 0)

Поэтому, чтобы сэкономить немного места в памяти (как каламбур), IEEE решил, что следует принять значение 1. Например, богомол 1011 действительно равен 1.1011.

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

У Java есть уникальная проблема в том, что язык универсален для множества различных платформ, каждая из которых может иметь свой собственный уникальный формат float. Это делает еще более важным избегать ==.

Правильный способ сравнить два числа с плавающей запятой (не относящихся к конкретному языку) на равенство заключается в следующем:

if(ABS(float1 - float2) < ACCEPTABLE_ERROR)
    //they are approximately equal

где ACCEPTABLE_ERROR равен #defined или некоторой другой константе, равной 0,000000001, или любой другой требуемой точности, как уже упоминал Виктор.

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

Error: User Rate Limit Exceeded
1

Как упоминалось в других ответах, двойники могут иметь небольшие отклонения. И вы можете написать свой собственный метод, чтобы сравнить их, используя & quot; приемлемый & quot; отклонение. Тем не мение ...

There is an apache class for comparing doubles: org.apache.commons.math3.util.Precision

Он содержит несколько интересных констант:SAFE_MIN а такжеEPSILON, которые являются максимально возможными отклонениями простых арифметических операций.

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

1

В одной строке ответа я могу сказать, вы должны использовать:

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)

Чтобы вы узнали больше о правильном использовании связанных операторов, я рассмотрю несколько случаев здесь: Как правило, есть три способа проверить строки в Java. Вы можете использовать ==, .equals () или Objects.equals ().

Насколько они разные? == проверяет эталонное качество в строках, что означает, что оба объекта одинаковы. С другой стороны, .equals () проверяет, имеют ли две строки одинаковое значение логически. Наконец, Objects.equals () проверяет наличие любых нулей в двух строках, а затем определяет, вызывать ли .equals ().

Идеальный оператор для использования

Что ж, это было предметом многочисленных дискуссий, потому что каждый из трех операторов имеет свой уникальный набор сильных и слабых сторон. Например, == часто является предпочтительным вариантом при сравнении ссылки на объект, но в некоторых случаях может показаться, что он также сравнивает строковые значения.

Однако, то, что вы получаете, является падением, потому что Java создает иллюзию того, что вы сравниваете значения, но в действительности это не так. Рассмотрим два случая ниже:

Case 1:

String a="Test";
String b="Test";
if(a==b) ===> true

Case 2:

String nullString1 = null;
String nullString2 = null;
//evaluates to true
nullString1 == nullString2;
//throws an exception
nullString1.equals(nullString2);

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

Здесь вы можете получить более подробную информацию:http://fluentthemes.com/use-compare-strings-java/

8

На сегодняшний день быстрый & amp; Самый простой способ сделать это:

if (Float.compare(sectionID, currentSectionID) == 0) {...}

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

Тем не менее, если требуется более высокая или индивидуальная точность, то

float epsilon = Float.MIN_NORMAL;  
if(Math.abs(sectionID - currentSectionID) < epsilon){...}

это еще один вариант решения.

19

What is wrong with using == to compare floating point values?

Потому что это не правда, что0.1 + 0.2 == 0.3

Error: User Rate Limit ExceededFloat.compare(0.1f+0.2f, 0.3f) == 0 ?
Error: User Rate Limit ExceededWhat is the correct way to do it??
Error: User Rate Limit Exceededh-schmidt.net/FloatApplet/IEEE754.html
8

Значения точки плавления не надежны из-за ошибки округления.

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

Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededdoubleError: User Rate Limit ExceededfloatError: User Rate Limit Exceededdouble.
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
7

В дополнение к предыдущим ответам вы должны знать, что с-0.0f а также+0.0f (они есть== но нетequals) а такжеFloat.NaN (этоequals но нет==) (надеюсь, я правильно понял - не надо!).

Изменить: Давайте проверим!

import static java.lang.Float.NaN;
public class Fl {
    public static void main(String[] args) {
        System.err.println(          -0.0f   ==              0.0f);   // true
        System.err.println(new Float(-0.0f).equals(new Float(0.0f))); // false
        System.err.println(            NaN   ==               NaN);   // false
        System.err.println(new Float(  NaN).equals(new Float( NaN))); // true
    }
} 

Добро пожаловать в IEEE / 754.

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded==Error: User Rate Limit Exceeded-0.0fError: User Rate Limit Exceeded0.0fError: User Rate Limit Exceeded==Error: User Rate Limit ExceededequalsError: User Rate Limit Exceeded==Error: User Rate Limit Exceeded
-1

Один из способов уменьшить ошибку округления - использовать double, а не float. Это не устранит проблему, но уменьшит количество ошибок в вашей программе, и float почти никогда не будет лучшим выбором. ПО МОЕМУ МНЕНИЮ.

200

правильный способ проверки поплавков на «равенство» является:

if(Math.abs(sectionID - currentSectionID) < epsilon)

где эпсилон - это очень небольшое число, например 0,00000001, в зависимости от желаемой точности.

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededcygnus-software.com/papers/comparingfloats/comparingfloats.htmError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededif(Math.abs(sectionID - currentSectionID) < epsilon*sectionIDError: User Rate Limit Exceeded
Error: User Rate Limit ExceededMath.ulp()Error: User Rate Limit Exceeded
2

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

4

Следующее автоматически использует лучшую точность:

/**
 * Compare to floats for (almost) equality. Will check whether they are
 * at most 5 ULP apart.
 */
public static boolean isFloatingEqual(float v1, float v2) {
    if (v1 == v2)
        return true;
    float absoluteDifference = Math.abs(v1 - v2);
    float maxUlp = Math.max(Math.ulp(v1), Math.ulp(v2));
    return absoluteDifference < 5 * maxUlp;
}

Конечно, вы можете выбрать более или менее 5 ULP (& # x2018; единица на последнем месте & # x2019;).

Если вы попали в библиотеку Apache Commons,Precision класс имеетcompareTo() а такжеequals() как с эпсилоном, так и с ULP.

Error: User Rate Limit ExceededdoubleError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
4

Вы можете использовать Float.floatToIntBits ().

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)
Error: User Rate Limit Exceededstackoverflow.com/a/3668105/2066079Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded

Похожие вопросы