Согласно этой странице java.sun ==
является оператором сравнения равенства для чисел с плавающей точкой в Java.
Тем не менее, когда я набираю этот код:
if(sectionID == currentSectionID)
в мой редактор и запустив статический анализ, я получаю: «JAVA0078 значения с плавающей точкой по сравнению с ==»
Что плохого в использовании ==
для сравнения значений с плавающей запятой? Как правильно это сделать?
java
equality
floating-accuracy
user128807
источник
источник
Ответы:
правильный способ проверить поплавки на «равенство»:
где эпсилон - очень небольшое число, например 0,00000001, в зависимости от желаемой точности.
источник
if(Math.abs(sectionID - currentSectionID) < epsilon*sectionID
чтобы решить эту проблему?Math.ulp()
в своем ответе на этот вопрос.Значения с плавающей точкой могут быть немного отключены, поэтому они могут не отображаться как абсолютно равные. Например, если задать значение с плавающей запятой равным «6,1», а затем снова распечатать его, вы можете получить сообщаемое значение, например «6,099999904632568359375». Это очень важно для работы поплавков; следовательно, вы не хотите сравнивать их, используя равенство, а скорее сравнение в пределах диапазона, то есть, если разность числа с плавающей запятой и числа, с которым вы хотите сравнить, меньше определенного абсолютного значения.
Эта статья о реестре дает хороший обзор того, почему это так; полезное и интересное чтение.
источник
Просто чтобы объяснить причину того, что говорят все остальные.
Бинарное представление с плавающей точкой раздражает.
В двоичном коде большинство программистов знают соотношение между 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 - и это именно то, что происходит.
Кстати, если вам интересно, число, которое оно хранит внутри, является чисто двоичным представлением, использующим двоичную «научную нотацию». Таким образом, если вы скажете ему, чтобы хранить десятичное число 10,75d, он будет хранить 1010b для 10 и .11b для десятичного. Таким образом, он будет хранить .101011, а затем в конце сохранит несколько битов, чтобы сказать: переместите десятичную точку на четыре позиции вправо.
(Хотя технически это уже не десятичная точка, теперь это двоичная точка, но эта терминология не сделала бы вещи более понятными для большинства людей, которые нашли бы этот ответ для любого использования.)
источник
Потому что это не правда, что
0.1 + 0.2 == 0.3
источник
Float.compare(0.1f+0.2f, 0.3f) == 0
?Я думаю, что есть много путаницы вокруг поплавков (и удваивается), это хорошо, чтобы прояснить это.
Нет ничего плохого в использовании поплавков в качестве идентификаторов в JVM, совместимой со стандартами [*]. Если вы просто установите для плавающего идентификатора значение x, ничего с этим не сделаете (то есть без арифметики) и позже проверите для y == x, все будет хорошо. Также нет ничего плохого в использовании их в качестве ключей в HashMap. Что вы не можете сделать, так это предположить равенство и
x == (x - y) + y
т. Д. При этом люди обычно используют целочисленные типы в качестве идентификаторов, и вы можете заметить, что большинство людей здесь откладывают этот код, поэтому по практическим соображениям лучше придерживаться соглашений , Обратите внимание, что существует столько же различныхdouble
значений, сколько и longvalues
, поэтому вы ничего не получите, используяdouble
. Кроме того, создание «следующего доступного идентификатора» может быть сложным с двойными числами и требует некоторых знаний арифметики с плавающей точкой. Не стоит хлопот.С другой стороны, полагаться на численное равенство результатов двух математически эквивалентных вычислений рискованно. Это связано с ошибками округления и потерей точности при преобразовании из десятичного в двоичное представление. Это было обсуждено до смерти на SO.
[*] Когда я сказал «стандартная JVM», я хотел исключить некоторые реализации JVM с поврежденным мозгом. Смотрите это .
источник
==
а неequals
, или гарантировать, что никакие числа с плавающей запятой, которые сравнивают неравные с самим собой, не будут храниться в таблице. В противном случае программа, которая пытается подсчитать, например, сколько уникальных результатов может быть получено из выражения при подаче различных входных данных, может рассматривать каждое значение NaN как уникальное.Float
, а не кfloat
.Float
? Если попытаться построить таблицу уникальныхfloat
значений и сравнить их с==
ужасными правилами сравнения IEEE-754, таблица будет заполненаNaN
значениями.float
Тип не имеетequals
метода.equals
метод экземпляра, а статический метод (я думаю, внутриFloat
класса), который сравнивает два значения типаfloat
.Это проблема не только для Java. Использование == для сравнения двух чисел с плавающей запятой / двойных / любого десятичного числа может потенциально вызвать проблемы из-за способа их хранения. Число с плавающей запятой одинарной точности (согласно стандарту IEEE 754) имеет 32 бита, распределенных следующим образом:
1 бит - знак (0 = положительный, 1 = отрицательный)
8 бит - экспонента (специальное (смещение-127) представление x в 2 ^ x)
23 бита - Mantisa. Фактический номер, который сохраняется.
Богомол - то, что вызывает проблему. Это похоже на научную запись, только число в базе 2 (двоичное) выглядит как 1.110011 x 2 ^ 5 или что-то подобное. Но в двоичном коде первая 1 всегда является 1 (кроме представления 0)
Поэтому, чтобы сэкономить немного места в памяти (как каламбур), IEEE решил, что следует принять значение 1. Например, богомол 1011 действительно равен 1.1011.
Это может вызвать некоторые проблемы со сравнением, особенно с 0, так как 0 не может быть представлен точно в плавающей запятой. Это главная причина, по которой == не рекомендуется, в дополнение к математическим задачам с плавающей запятой, описанным в других ответах.
У Java есть уникальная проблема в том, что язык универсален для множества различных платформ, каждая из которых может иметь свой собственный уникальный формат float. Это делает еще более важным избегать ==.
Правильный способ сравнить два числа с плавающей запятой (не относящихся к конкретному языку) на равенство заключается в следующем:
где ACCEPTABLE_ERROR равен #defined или некоторой другой константе, равной 0,000000001, или любой другой требуемой точности, как уже упоминал Виктор.
В некоторых языках есть эта функциональность или эта встроенная константа, но обычно это хорошая привычка.
источник
На сегодняшний день быстрый и простой способ сделать это:
Однако в документах не указано четко значение разницы полей ( эпсилон из ответа @Victor), которое всегда присутствует в вычислениях с плавающей запятой, но это должно быть чем-то разумным, поскольку оно является частью стандартной библиотеки языков.
Тем не менее, если требуется более высокая или индивидуальная точность, то
это еще один вариант решения.
источник
(sectionId == currentSectionId)
в случае с плавающей запятой. метод epsilon - лучший подход, который находится в этом ответе: stackoverflow.com/a/1088271/4212710Значения точки плавления не надежны из-за ошибки округления.
Как таковые они, вероятно, не должны использоваться в качестве ключевых значений, таких как sectionID. Вместо этого используйте целые числа или,
long
еслиint
не содержит достаточно возможных значений.источник
double
гораздо точнее, но они также являются значениями с плавающей запятой, так что мой ответ должен был включать в себяfloat
иdouble
.В дополнение к предыдущим ответам вы должны знать, что есть странное поведение, связанное с
-0.0f
и+0.0f
(они есть,==
но нетequals
) иFloat.NaN
(это есть,equals
но нет==
) (надеюсь, я правильно понял - ага, не делайте этого!).Изменить: давайте проверим!
Добро пожаловать в IEEE / 754.
источник
==
это не означает, что числа «идентичны битам» (одно и то же число может быть представлено разными битовыми комбинациями, хотя только одна из них имеет нормализованную форму). Также,-0.0f
и0.0f
представлены различными битовыми комбинациями (знак бит отличается), но сравниваются как равные==
(но не сequals
). Ваше предположение о==
побитовом сравнении, вообще говоря, неверно.Вот очень длинное (но, надеюсь, полезное) обсуждение этой и многих других проблем с плавающей запятой, с которыми вы можете столкнуться: Что должен знать каждый компьютерный специалист об арифметике с плавающей запятой
источник
Вы можете использовать Float.floatToIntBits ().
источник
Прежде всего, они плавают или плавают? Если один из них является Float, вы должны использовать метод equals (). Также, вероятно, лучше всего использовать статический метод Float.compare.
источник
Следующее автоматически использует лучшую точность:
Конечно, вы можете выбрать более или менее 5 ULP («единица на последнем месте»).
Если вы в библиотеке Apache Commons, у
Precision
класса естьcompareTo()
иequals()
с epsilon и с ULP.источник
double
чтобы покрыть это.Вы можете хотеть, чтобы это было ==, но 123.4444444444443! = 123.4444444444442
источник
Если вам * нужно * использовать поплавки, может пригодиться ключевое слово strictfp.
http://en.wikipedia.org/wiki/strictfp
источник
Два разных вычисления, которые дают одинаковые действительные числа, не обязательно дают одинаковые числа с плавающей запятой. Люди, которые используют == для сравнения результатов вычислений, обычно удивляются этому, поэтому предупреждение помогает пометить то, что в противном случае могло бы быть скрытой и трудной для воспроизведения ошибкой.
источник
Имеете ли вы дело с внешним кодом, который будет использовать числа с плавающей запятой для вещей с именами sectionID и currentSectionID? Просто любопытно.
@ Билл К: «Бинарное представление с плавающей точкой раздражает». Как так? Как бы вы сделали это лучше? Есть определенные числа, которые не могут быть представлены в любой базе должным образом, потому что они никогда не заканчиваются. Пи хороший пример. Вы можете только приблизить это. Если у вас есть лучшее решение, обратитесь в Intel.
источник
Как упоминалось в других ответах, двойники могут иметь небольшие отклонения. И вы можете написать свой собственный метод, чтобы сравнить их, используя «приемлемое» отклонение. Тем не мение ...
Существует класс apache для сравнения двойников: org.apache.commons.math3.util.Precision
Он содержит несколько интересных констант:
SAFE_MIN
иEPSILON
, которые являются максимально возможными отклонениями простых арифметических операций.Он также предоставляет необходимые методы для сравнения, равенства или округления двойных чисел. (используя ulps или абсолютное отклонение)
источник
В одной строке ответа я могу сказать, вы должны использовать:
Чтобы вы больше узнали о правильном использовании связанных операторов, я рассмотрю здесь несколько случаев: как правило, есть три способа тестирования строк в Java. Вы можете использовать ==, .equals () или Objects.equals ().
Насколько они разные? == проверяет эталонное качество в строках, что означает, что два объекта одинаковы. С другой стороны, .equals () проверяет, имеют ли две строки одинаковое значение логически. Наконец, Objects.equals () проверяет наличие любых нулей в двух строках, а затем определяет, вызывать ли .equals ().
Идеальный оператор для использования
Что ж, это было предметом многочисленных дискуссий, потому что каждый из трех операторов имеет свой уникальный набор сильных и слабых сторон. Например, == часто является предпочтительным вариантом при сравнении ссылки на объект, но в некоторых случаях может показаться, что он также сравнивает строковые значения.
Однако, то, что вы получаете, является падением, потому что Java создает иллюзию того, что вы сравниваете значения, но в действительности это не так. Рассмотрим два случая ниже:
Случай 1:
Случай 2:
Поэтому лучше использовать каждый оператор при тестировании определенного атрибута, для которого он предназначен. Но почти во всех случаях Objects.equals () является более универсальным оператором, поэтому опытные веб-разработчики выбирают его.
Здесь вы можете получить более подробную информацию: http://fluentthemes.com/use-compare-strings-java/
источник
Правильный путь будет
источник
Float.compare(1.1 + 2.2, 3.3) != 0
Один из способов уменьшить ошибку округления - использовать double, а не float. Это не устранит проблему, но уменьшит количество ошибок в вашей программе, и float почти никогда не будет лучшим выбором. ПО МОЕМУ МНЕНИЮ.
источник