23

I am using a findbugs in an ANT script and I can't figure out how to fix two of my errors. I have read the documentation, but don't understand. Here are my errors and the code that goes with them:

Error 1: Test for floating point equality. (FE_FLOATING_POINT_EQUALITY)

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

Error 2: EQ_COMPARETO_USE_OBJECT_EQUALS

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

I've read the documentation for the ComparesTo issue that states

It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y)). Generally speaking, any class that implements the Comparable interface and violates this condition should clearly indicate this fact. The recommended language is "Note: this class has a natural ordering that is inconsistent with equals."

and also the docs regarding the floating point equality

This operation compares two floating point values for equality. Because floating point calculations may involve rounding, calculated float and double values may not be accurate. For values that must be precise, such as monetary values, consider using a fixed-precision type such as BigDecimal. For values that need not be precise, consider comparing for equality within some range, for example: if ( Math.abs(x - y) < .0000001 ). See the Java Language Specification, section 4.2.4.

I don't get it though. Can anyone please help?

martin clayton
  • 76,436
  • 32
  • 213
  • 198
taraloca
  • 9,077
  • 9
  • 44
  • 77

3 Answers3

19

Problem 1:

For the FE_FLOATING_POINT_EQUALITY issue, you should not be comparing two float values directly with the == operator, since due to tiny rounding errors, the values might be semantically "equal" for your application even if the condition value1 == value2 does not hold true.

In order to fix this, modify your code as follows:

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

Where EPSILON is a constant that you should define in your code, and represents small differences that are acceptable to your application, e.g. .0000001.

Problem 2:

For the EQ_COMPARETO_USE_OBJECT_EQUALS issue: It is strongly recommended that wherever x.compareTo(y) returns zero, x.equals(y) should be true. In your code you have implemented compareTo, but you have not overriden equals, so you are inheriting the implementation of equals from Object, and the above condition is not met.

In order to fix this, override equals (and perhaps hashCode) in your class, so that when x.compareTo(y) returns 0, then x.equals(y) will return true.

Grodriguez
  • 21,501
  • 10
  • 63
  • 107
  • 5
    An `equals` method should be transitive (http://javarevisited.blogspot.fr/2011/02/how-to-write-equals-method-in-java.html ) and consistent with the `hashCode` method. Please tell us more about your `equals` implementation as `(Math.abs(this.mAmount - convertedAmount(other)) < EPSILON)` and how it satisfies these imperatives. – Pascal Cuoq Jul 27 '13 at 18:42
  • 1
    @PascalCuoq have you solved this ? One way I can think of is to convert float to BigDecimal, round to precision you need and calculate hash for that. But there will be performance impact. – Alexander Malakhov Jul 17 '14 at 09:34
  • 1
    @AlexanderMalakhov I am not the person who asked the question. I only made a comment about the appropriateness of defining a non-transitive `equals` method. There is no problem to “solve” here. Floating-point values can be hashed, and can be compared for equality with basic operators that Java already offers. Anyone who defines a non-transitive `equals` method is creating a problem for themselves, and the simplest way to solve the problem is not to create it in the first place. – Pascal Cuoq Jul 17 '14 at 09:39
  • 1
    @PascalCuoq. By "solve" I meant exactly that - make `equals` transitive and in sync with `hashCode`. My proposal solves `hashCode` (convert to BigDecimal only inside that method), but not `equals`. Being mathematician, that makes me kinda nervous :) ([definition](http://en.wikipedia.org/wiki/Equivalence_relation#Definition) for reference) – Alexander Malakhov Jul 17 '14 at 09:56
  • oh, wait. That will work for `equals` too. Before comparison one should round to desired precision and compare rounded values. Performance might still be an issue, though. – Alexander Malakhov Jul 17 '14 at 10:30
  • Shouldn't `Double.compare(x, y) == 0` or `Float.compare(x, y) == 0` do just fine? – Jared Burrows Nov 06 '16 at 23:32
  • @JaredBurrows No, that has the same problems as comparing with `==`. These methods only return 0 when the two values are "exactly equal", which often is not what you want. – Grodriguez Mar 06 '17 at 08:57
  • There is an excellent discussion here about why using a tolerance equals does not work here: https://softwareengineering.stackexchange.com/a/391107 . – joseph Jan 06 '21 at 16:12
6

For the floating point warning, you should bear in mind that floats are an inexact type. A standard reference oft given for this (which is worth reading once perhaps) is:

What Every Computer Scientist Should Know About Floating-Point Arithmetic by David Goldberg.

Because floats are not exact values - even if they look the same when rounded up to a few decimals - they can differ very slightly, and fail to match.

The Comparable interface expects a certain behaviour by its implementor; the warning is telling you you are not adhering to that, and offering suggested actions.

martin clayton
  • 76,436
  • 32
  • 213
  • 198
3

I do not agree with the answers above. Equals and compareTo are the wrong place to introduce epsilons in floating point comparisons.

Floating point values can be compared exactly by equals and compareTo, just using the "==" operator.
If your application, uses floats that are a result of calculation, need to compare these values with the epsilon approach, it should do that only in that place where this is needed. E.g in a mathematical line intersection method.
But not in equals and compareTo.

This warning is very misleading. It means comparing two floats where at leats one is a result of an calculation might give unexpected result. However, often such floats, to compare, are not a result of a calculation, like

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

where f is initialized with INVALID_VALUE, will in java always work perfectly. But findbugs and sonarcube will still complain.

So just add an ignore filter to findbugs, asuming you have two classes MyPoint2D and 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>
AlexWien
  • 28,470
  • 6
  • 53
  • 83
  • Using an "invalid value" is not a clean approach in the first place. But optimization (often done prematurely) can lead to such a hack and even might be the best solution in the end. From a Clean Code point of view you should rather use a separate field holding the information whether the float is valid or not. Then you would not need to think about checking floats for equality. – Alfe Feb 05 '16 at 12:29
  • I agree,in many situations, it's best to have a separate fiield to show validity. In many other situations not. (internal code not public to any other class) The main topic here is that the compare to floatin point constants works – AlexWien Feb 05 '16 at 15:23
  • The notion that internal code can be less clean is dangerous. Also internal code might need maintenance by a successor developer who must understand the code. But I'll accept the "highly optimized" argument in some cases. – Alfe Feb 05 '16 at 15:27
  • It is not less clean to compare to an internal INVALID_VALUE. The problem when the field would be published is that the INVAID_VALUE is unknown, and sometimes even undocumented. There is no advantage in understanding between "if (fieldIsValid()" and "if (field != INVALID)" even the opposite is true:,the second case has the code which would follow directly after the if. The first case needs an if (!isFieldsIsValid() which is a double negation, known to be worse. or an else path wich is unclean, too since the main code that runs most of the time should be in the if() block. – AlexWien Feb 05 '16 at 15:58
  • It's still unclean because the data type never states that a value like `-99.0` means "invalid". You have to find that out first to understand why on earth there suddenly is a massive drop below zero in your data (which e. g. is a counter of something, so that only positive values should appear). The code spot where you compare the value against the constant is not the problem. There it becomes clear. All the other spots in which such a strange value might be passed without any documentation thereof is. – Alfe Feb 05 '16 at 16:11
  • The javadoc of the private field of course has to explain the INVALID_VALUE. So it is stated, Do not asume any code if you don't have seen it. – AlexWien Feb 05 '16 at 17:25
  • Documenting a [wart](http://www.catb.org/jargon/html/W/wart.html) does not make it a clean design. Don't try to sell an optimization as a clean design. It typically has other advantages (performance, memory consumption) which speak for its existence. – Alfe Feb 08 '16 at 09:55
  • There is no definition in clean design. duplicating internal fields such that each has its own invalid flag, makes the code unclean. Silly code is unclean too. It shows that the programmer is an idiot, probably having misunderstood some practises. A fool reading a clean-code book, still remains a fool. – AlexWien Feb 08 '16 at 14:41
  • It's true, it's subjective. Now you know my opinion and I your politeness. – Alfe Feb 08 '16 at 15:19
  • Politeness is culturally defined, so subjective, too – AlexWien Feb 08 '16 at 15:30
  • Agreed; in my case it is wining about me checking if something that I initialize to 0.0 by default `someVar== 0.0` (i.e. did somebody override the value) or whether somebody provided a non default value. That's perfectly valid IMHO and fully intentional, not a bug, and not a situation that warrants some convoluted check with some arbitrary epsilon. I guess you could turn that into `!(someVar<0.0 || someVar>0.0)`. But why ruin a perfectly good equals check? – Jilles van Gurp Jul 10 '17 at 12:46