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

Согласно эта страница java.sun == - оператор сравнения равенств для чисел с плавающей запятой в Java.

Однако, когда я набираю этот код:

if(sectionID == currentSectionID)

в мой редактор и запустить статический анализ, я получаю: "JAVA0078 значения плавающей точки по сравнению с =="

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

Ответы

Ответ 1

правильный способ тестирования float для "равенства":

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

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

Ответ 2

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

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

Ответ 3

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

Двоичное представление поплавка вызывает раздражение.

В двоичном коде большинство программистов знают корреляцию между 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, затем он сохранит несколько бит в конце, чтобы сказать: переместите десятичную точку в четыре места справа.

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

Ответ 4

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

Потому что это не так, что 0.1 + 0.2 == 0.3

Ответ 5

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

  • Нет ничего неправильного в использовании float в качестве идентификаторов в стандартном JVM [*]. Если вы просто установите идентификатор float в x, ничего не сделайте с ним (т.е. Без арифметики), а затем проверите для y == x, все будет в порядке. Также нет ничего плохого в использовании их в качестве ключей в HashMap. То, что вы не можете сделать, это предположить такие равенства, как x == (x - y) + y и т.д. Это говорит о том, что люди обычно используют целые типы в качестве идентификаторов, и вы можете заметить, что большинство людей здесь откладывается этим кодом, поэтому по практическим соображениям лучше придерживаться конвенций. Обратите внимание, что существует столько разных значений double, сколько длинных values, поэтому вы ничего не получаете, используя double. Кроме того, генерация "следующего доступного идентификатора" может быть сложной с удвоениями и требует некоторого знания арифметики с плавающей запятой. Не стоит того.

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

[*] Когда я сказал "совместимый со стандартом JVM", я хотел исключить некоторые модификации JVM, поврежденные мозгом. См. .

Ответ 6

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

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

Ответ 7

Это проблема, не относящаяся к java. Использование == для сравнения двух чисел float/doubles/any decimal может потенциально вызвать проблемы из-за того, как они хранятся. Поплавок с одной точностью (согласно стандарту 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, или любую другую точность, как уже упоминал Виктор.

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

Ответ 8

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

Изменить: пусть проверяет!

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.

Ответ 10

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

Ответ 11

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

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

Ответ 12

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

/**
 * 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 ( "единица на последнем месте" ).

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

Ответ 13

вы можете захотеть, чтобы он был ==, но 123.4444444444443!= 123.4444444444442

Ответ 14

Если вы * должны использовать * float, ключевое слово strictfp может быть полезным.

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

Ответ 15

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

Ответ 16

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

@Bill K: "Бинарное представление поплавка - это раздражает". Как так? Как бы вы сделали это лучше? Есть определенные числа, которые не могут быть представлены ни в одной базе должным образом, потому что они никогда не заканчиваются. Пи - хороший пример. Вы можете только приблизиться к нему. Если у вас есть лучшее решение, обратитесь в корпорацию Intel.

Ответ 17

Правильный способ:

java.lang.Float.compare(float1, float2)

Ответ 18

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

Существует класс apache для сравнения удвоений: org.apache.commons.math3.util.Precision

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

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

Ответ 19

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

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

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

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

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

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

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

Случай 1:

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

Случай 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/

Ответ 20

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

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

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

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

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

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

Ответ 21

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