Тест на равенство с плавающей точкой. (FE_FLOATING_POINT_EQUALITY)

Я использую findbugs в сценарии ANT, и я не могу понять, как исправить две из моих ошибок. Я прочитал документацию, но не понимаю. Вот мои ошибки и код, который идет с ними:

Ошибка 1: Проверка на равенство с плавающей точкой. (FE_FLOATING_POINT_EQUALITY)

private boolean equals(final Quantity other) {
    return this.mAmount == convertedAmount(other);
}

Ошибка 2: EQ_COMPARETO_USE_OBJECT_EQUALS

public final int compareTo(final Object other) {
    return this.description().compareTo(((Decision) other).description());
}

Я прочитал документацию по проблеме ComparesTo, в которой говорится

Настоятельно рекомендуется, но не обязательно, чтобы (x.compareTo (y) == 0) == (x.equals (y)). Вообще говоря, любой класс, который реализует интерфейс Comparable и нарушает это условие, должен четко указывать на этот факт. Рекомендуемый язык: «Примечание: этот класс имеет естественный порядок, не совместимый с равными».

а также документы, касающиеся равенства с плавающей точкой

Эта операция сравнивает два значения с плавающей точкой на равенство. Поскольку вычисления с плавающей запятой могут включать округление, вычисленные значения с плавающей запятой и двойные значения могут быть неточными. Для значений, которые должны быть точными, таких как денежные значения, рассмотрите возможность использования типа с фиксированной точностью, такого как BigDecimal. Для значений, которые не обязательно должны быть точными, рассмотрите возможность сравнения на равенство в некотором диапазоне, например: if (Math.abs (x - y) <.0000001). См. Спецификацию языка Java, раздел 4.2.4.

Я не понимаю, хотя. Может кто-нибудь, пожалуйста, помогите?

Ответы на вопрос(3)

неточный тип. Для этого приводится стандартная ссылка (которую стоит прочитать один раз):

Что каждый ученый должен знать об арифметике с плавающей точкой Дэвид Голдберг.

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

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

место для введения эпсилонов в сравнениях с плавающей запятой.

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

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

static final double INVALID_VALUE = -99.0;
if (f == INVALID_VALUE)

где f инициализируется с помощью INVALID_VALUE, в Java всегда будет работать идеально. Но findbugs и sonarcube все равно будут жаловаться.

Так что просто добавьте фильтр игнорирования в findbugs, если у вас есть два класса MyPoint2D и Myrectangle2D

<Match>
        <OR>
            <Class name="~.*\.MyPoint2D" />
            <Class name="~.*\.MyRectangle2D" />
        </OR>
        <Bug code="FE" />
        <Justification author="My Name" />
        <Justification
            text="Floating point equals works (here)." />
    </Match>
 AlexWien08 февр. 2016 г., 16:30
Вежливость определяется культурой, поэтому тоже субъективна
 Alfe08 февр. 2016 г., 16:19
Это правда, это субъективно. Теперь вы знаете мое мнение, а я - вашу вежливость.
 Alfe08 февр. 2016 г., 10:55
Документированиебородавка не делает это чистым дизайном. Не пытайтесь продать оптимизацию как чистый дизайн. У него обычно есть другие преимущества (производительность, потребление памяти), которые говорят о его существовании.
 AlexWien05 февр. 2016 г., 16:23
Я согласен, что во многих ситуациях лучше иметь отдельное поле, чтобы показать его обоснованность. Во многих других ситуациях нет. (внутренний код не является общедоступным для любого другого класса). Основная тема здесь - работает сравнение с константами с плавающей точкой.
 AlexWien05 февр. 2016 г., 18:25
Javadoc частного поля, конечно, должен объяснить INVALID_VALUE. Итак, заявлено: не используйте какой-либо код, если вы его не видели.
 Alfe05 февр. 2016 г., 17:11
Это все еще нечисто, потому что тип данных никогда не утверждает, что значение как-99.0 означает «недействительный». Вы должны сначала выяснить это, чтобы понять, почему на земле внезапно происходит резкое падение ниже нуля в ваших данных (что, например, является показателем чего-то, так что должны появляться только положительные значения). Кодовое место, где вы сравниваете значение с константой, не является проблемой. Там становится ясно. Все остальные места, в которых такое странное значение может быть передано без какой-либо документации, есть.
 AlexWien08 февр. 2016 г., 15:41
В чистом дизайне нет определения. дублирование внутренних полей таким образом, что каждое имеет свой собственный недопустимый флаг, делает код нечистым. Глупый код тоже нечист. Это показывает, что программист идиот, возможно, неправильно понял некоторые практики. Дурак, читающий книгу чистых кодов, все еще остается дураком.
 AlexWien05 февр. 2016 г., 16:58
Не менее чисто по сравнению с внутренним INVALID_VALUE. Проблема, когда поле будет опубликовано, состоит в том, что INVAID_VALUE неизвестен, а иногда даже недокументирован. Нет никакого преимущества в понимании между «if (fieldIsValid ()» и «if (field! = INVALID)», даже если верно обратное: во втором случае есть код, который следует непосредственно после if. В первом случае требуется if (! isFieldsIsValid (), который является двойным отрицанием, известно, что он хуже, или путь else, который является нечистым, так как основной код, который выполняется большую часть времени, должен находиться в блоке if ().
 Alfe05 февр. 2016 г., 16:27
Понятие, что внутренний код может быть менее чистым, опасно. Также внутренний код может нуждаться в поддержке разработчиком-преемником, который должен понимать код. Но я приму «высокооптимизированный» аргумент в некоторых случаях.
 Jilles van Gurp10 июл. 2017 г., 14:46
Согласовано; в моем случае мне нужно проверить, что я инициализирую на 0.0 по умолчаниюsomeVar== 0.0 (т.е. кто-то переопределил это значение) или кто-то предоставил значение не по умолчанию. Это совершенно верно, ИМХО и полностью преднамеренное, не ошибка и не ситуация, которая требует некоторой запутанной проверки с произвольным эпсилоном. Я думаю, вы могли бы превратить это!(someVar<0.0 || someVar>0.0), Но почему испортил совершенно хорошую проверку равных?
 Alfe05 февр. 2016 г., 13:29
Использование «недопустимого значения» - не совсем чистый подход. Но оптимизация (часто преждевременная) может привести к такому взлому и даже может стать лучшим решением в конце. С точки зрения чистого кода вы должны использовать отдельное поле, содержащее информацию о том, является ли число с плавающей точкой действительным или нет. Тогда вам не нужно думать о проверке поплавков на равенство.
Решение Вопроса

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

Чтобы это исправить, измените ваш код следующим образом:

private boolean equals(final Quantity other) {
    return (Math.abs(this.mAmount - convertedAmount(other)) < EPSILON);
}

Где EPSILON является константой, которую вы должны определить в своем коде, и представляет небольшие различия, которые приемлемы для вашего приложения, например, 0,0000001.

Проблема 2:

Для проблемы EQ_COMPARETO_USE_OBJECT_EQUALS: настоятельно рекомендуется везде, гдеx.compareTo(y) возвращает ноль,x.equals(y) должно бытьtrue, В вашем коде вы реализовалиcompareTo, но вы не изменилиequalsТаким образом, вы наследуете реализациюequals отObjectи вышеуказанное условие не выполнено.

Чтобы это исправить, переопределитеequals (и, возможноhashCode) в вашем классе, так что когдаx.compareTo(y) возвращает 0, затемx.equals(y) вернусьtrue.

 Grodriguez06 мар. 2017 г., 09:57
@JaredBurrows Нет, это те же проблемы, что и в сравнении с==, Эти методы возвращают 0 только тогда, когда два значения «абсолютно равны», что часто не то, что вы хотите.
 Jared Burrows07 нояб. 2016 г., 00:32
не должныDouble.compare(x, y) == 0 или жеFloat.compare(x, y) == 0 сделать хорошо?
 Alexander Malakhov17 июл. 2014 г., 12:30
о, подожди. Это будет работать дляequals тоже. Перед сравнением следует округлить до желаемой точности и сравнить округленные значения. Производительность все еще может быть проблемой, хотя.
 Pascal Cuoq27 июл. 2013 г., 20:42
equals метод должен быть переходным (javarevisited.blogspot.fr/2011/02/... ) и в соответствии сhashCode метод. Пожалуйста, расскажите нам больше о вашемequals реализация как(Math.abs(this.mAmount - convertedAmount(other)) < EPSILON) и как это удовлетворяет этим императивам.
 Alexander Malakhov17 июл. 2014 г., 11:34
@PascalCuoq ты решил это? Один из способов, который я могу придумать, - преобразовать float в BigDecimal, округлить до необходимой вам точности и рассчитать для этого хеш. Но это повлияет на производительность.
 Alexander Malakhov17 июл. 2014 г., 11:56
@PascalCuoq. Под «решить» я имел в виду именно это - сделатьequals переходный и синхронно сhashCode, Мое предложение решаетhashCode (преобразовать в BigDecimal только внутри этого метода), но неequals, Будучи математиком, это заставляет меня нервничать :) (определение для справки)
 Pascal Cuoq17 июл. 2014 г., 11:39
@AlexanderMalakhov Я не тот человек, который задал вопрос. Я только прокомментировал уместность определения нетранзитивногоequals метод. Здесь нет проблем, чтобы «решить». Значения с плавающей точкой можно хэшировать и сравнивать на равенство с базовыми операторами, которые Java уже предлагает. Тот, кто определяет нетранзитивныйequals Метод создает проблему для себя, и самый простой способ решить проблему - это не создать ее в первую очередь.

Ваш ответ на вопрос