When == isn't equals()

In Java +0.0 is equal to -0.0 for floats and doubles, but not Float.equals. Is there somewhere where Kotlin clarifies float/double equality? For example:

fun main(args: Array<String>) {
    val negZero = -0.0f
    val posZero = 0.0f
    System.out.println(negZero == posZero)
    System.out.println(negZero.equals(posZero))
}

Outputs true then false which already negates https://kotlinlang.org/docs/reference/equality.html and makes IntelliJ wrong telling my I can remove the equals. I have also tested w/ when statements and it appears to use the == approach rather than the Float.equals. This is the downside of auto-boxing and type aliases. I understand (and prefer) that == does a FCMP/FCMPL in the bytecode, but the semantics of the equals method need to be made clear. For example, what does the following output?

fun main(args: Array<String>) {
    val negZero = -0.0f
    val posZero = 0.0f
    val negZeroAsAny: Any = negZero
    System.out.println(negZeroAsAny == posZero)
}

As might be expected knowing that casting to Any boxes it making == now properly use equals. This issue is obvious in Java because using == is always one thing and equals is always another. So…

  1. Is the fact that IntelliJ telling me I can replace equals with == a bug?
  2. Are the language designers ok with casting to Any changing the semantics off the value?
  3. Do caveats on https://kotlinlang.org/docs/reference/equality.html need to be added about == not always being equals?

My suggestion:

Java got it wrong with Float.equals and should have had strictEquals instead. Since Kotlin aims to have a minimal runtime, it can’t really have its own Float with equals defined more literally. Similarly, it is impossible to know whether an Any is a float/double (and runtime introspection is unacceptable), so the ==-is-equals will have to persist. Since Kotlin is stuck with “== is equals except for primitives where it uses Java equality semantics”, I think some statement should just be made somewhere (if it isn’t already, I cannot find it). IIRC Scala has to deal with this same thing and just does what Kotlin does which is to have unexpected equality when implicitly boxing in these rare cases to save performance…I have not checked.

(I am sure the same happens with NaN where == is different than equals)

Edit: Now I see Intrinsics.java, but I don’t see where areEqual(Float, Float) would be called? Still can’t reasonably check for instanceof Float in the object one due to performance issues IMO.

3 Likes

Sorry for the late reply.

Yes, it has been already reported as https://youtrack.jetbrains.com/issue/KT-25624.

Yes, it is how the equality operator is designed in Kotlin. It was documented on that reference page some time ago (though, admittedly, it hadn’t been when you asked this question):

When an equality check operands are statically known to be Float or Double (nullable or not), the check follows the IEEE 754 Standard for Floating-Point Arithmetic.

See also http://kotlinlang.org/docs/reference/basic-types.html#floating-point-numbers-comparison

1 Like