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

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

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

170
задан Morgoth 26 April 2017 в 09:21
поделиться

16 ответов

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

-2
ответ дан 23 November 2019 в 20:47
поделиться

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

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

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

207
ответ дан 23 November 2019 в 20:47
поделиться

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

3
ответ дан 23 November 2019 в 20:47
поделиться

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

java.lang.Float.compare(float1, float2)
-2
ответ дан 23 November 2019 в 20:47
поделиться

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

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

2
ответ дан 23 November 2019 в 20:47
поделиться

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

6
ответ дан 23 November 2019 в 20:47
поделиться

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

2
ответ дан 23 November 2019 в 20:47
поделиться

Это проблема, не относящаяся к 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 есть уникальная проблема в том, что язык универсален для многих различных платформ, каждая из которых может иметь свой собственный уникальный формат с плавающей запятой. Это делает еще более важным избегать ==.

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

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

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

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

8
ответ дан 23 November 2019 в 20:47
поделиться

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

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

8
ответ дан 23 November 2019 в 20:47
поделиться

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

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

53
ответ дан 23 November 2019 в 20:47
поделиться

Я думаю, что существует много путаницы с числами с плавающей запятой (и удвоениями), полезно прояснить это.

  1. Нет ничего принципиально неправильного в использовании чисел с плавающей запятой в качестве идентификаторов в стандартная JVM [*]. Если вы просто установите для идентификатора float значение x, ничего не сделаете с ним (т.е. без арифметики), а затем проверите y == x, все будет в порядке. Также нет ничего плохого в использовании их в качестве ключей в HashMap. Что вы не можете сделать, так это принять равенства типа x == (x - y) + y и т. Д. При этом люди обычно используют целочисленные типы в качестве идентификаторов, и вы можете заметить, что большинство людей здесь откладываются этим кодом, поэтому из практических соображений лучше придерживаться соглашений. Обратите внимание, что существует столько же различных значений double , сколько длинных значений , поэтому вы ничего не получите, используя double . Также, Генерация «следующего доступного идентификатора» может быть сложной задачей с двойными числами и требует некоторых знаний арифметики с плавающей запятой. Не стоит проблем.

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

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

12
ответ дан 23 November 2019 в 20:47
поделиться

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

4
ответ дан 23 November 2019 в 20:47
поделиться

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

Edit: Давайте проверим!

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.

7
ответ дан 23 November 2019 в 20:47
поделиться

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

Потому что неверно, что 0,1 + 0,2 == 0,3

19
ответ дан 23 November 2019 в 20:47
поделиться

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

Двоичное представление числа с плавающей запятой немного раздражает.

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

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

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

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

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

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

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

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

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

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

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

Таким образом, он сохраняет .101011, а затем сохраняет несколько битов в конце, чтобы сказать: переместите десятичную точку на четыре позиции вправо.

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

Таким образом, он сохраняет .101011, а затем сохраняет несколько битов в конце, чтобы сказать: переместите десятичную точку на четыре позиции вправо.

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

22
ответ дан 23 November 2019 в 20:47
поделиться

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

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

3
ответ дан 23 November 2019 в 20:47
поделиться
Другие вопросы по тегам:

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