I'm calling assertEquals on two lists in a test ca...
# codereview
a
I'm calling assertEquals on two lists in a test case, but unfortunately i've run into the issue that list equality calls
.equals()
instead of
==
, the two of which give me a different results.
Copy code
>>> 0f == -0f
res0: kotlin.Boolean = true
>>> 0f.equals(-0f)
res0: kotlin.Boolean = false
>>> listOf(0f) == listOf(-0f)
res1: kotlin.Boolean = false
The best way I've come up with to solve this is by defining my own assertEquals function that works on List<Float>, but this doesn't seem very clean to me. Is there a better way to do this? Also, why doesn't list equality use
==
?
m
Use an assertions library that does it for you? AssertJ, hamkrest are examples. Or switch to kotest which is a solid testing framework with great matches and other features?
a
I'm using kotest - though it looks like I'm some versions behind. I'll see if upgrading fixes it!
s
as far as why this doesn’t work as you expect it to, I think it’s because of how
operator fun equals()
works — if I understand correctly, there’s no way to override and provide custom operator equals functionality overloaded on a generic type (as
List
is usually implemented, anyhow)
👍 1
list implementations on the JVM just use
.equals()
afaict and can’t make use of Kotlin’s funky double/float
==
s
If you're using kotest, then try
shouldBe
.
assertEquals
is not part of kotest.
👍 1
a
I did the upgrade, but shouldBe is not doing it for me (it's still reporting that listOf(-0f) != listOf(0f). I checked for other matchers but I'm not seeing any would fix this. It seems like from @Shawn’s explanation that there may not actually be a nicer way to do this than writing a custom function to handle my
List<Float>
case?
k
0f == -0f
compares two primitive types. Lists cannot store primitive types. The floats in the lists are objects. Their equals semantics will be different. Kotlin does autoboxing of primitive/object types for us automatically.
The question here should actually be "why is the Float objects equals 0f != -0f" which may actually be an edge case bug that was missed.
s
but there are no “primitives” in kotlin — there are some edge cases and known optimizations to primitive types (specific to target platforms), but to the language itself there isn’t a lower-case f
float
k
We don't see them because they're hidden away from us. There are primitive types in Kotlin.
s
plus, you can get boxed floats out of a list, compare them, and see the intended behavior
k
Any time a generic is involved autoboxing on a float/Float will happen. Lists use generics. I imagine pulling them out of the list and assigning them to a float circumvents that and get you the true primitive type versus the boxed type.
this is true for all primitive types and their object counterparts
As you probably know, the heap allocations are due to boxing and unboxing, which is due to generics.
Getting rid of the boxing/generics in the code you provided requires both inlining and defining specialised (non-generic) versions of the functions.
if he wanted to get around this in his tests, he could use
toTypedArray
which (I believe) should unbox those values into an array of primitives
but there are no “primitives” in kotlin
there's literally a file in Kotlin called Primitives.kt. Directly from the doc comments from
Float
Copy code
/**
 * Represents a single-precision 32-bit IEEE 754 floating point number.
 * On the JVM, non-nullable values of this type are represented as values of the primitive type `float`.
 */
s
You have literally pointed out a platform-specific optimization that doesn’t (or at very least shouldn’t) have bearing on the guarantees the language makes with regard to the semantics of float/double comparison
moreover,
floatArrayOf(0.0F, -0.0F).contentEquals(floatArrayOf(0.0F, 0.0F))
still returns
false
k
from the docs from contentEquals
The elements are compared for equality with the equals function. For floating point numbers it means that
NaN
is equal to itself and
-0.0
is not equal to
0.0
s
precisely
if these floats were being unboxed and treated as primitives as a rule, there would be no
.equals()
function to speak of
but all of this is missing the point — the reason
0.0F == -0.0F
has nothing to do with whether or not the compiler is aware of platform-specific primitive types, it’s because
==
has special semantics when comparing floating-point numbers
special semantics that the collection types and arrays don’t make use of
k
Yes it seem I was wrong about typed array equality. However, Kotlin (JVM at least) certainly has primitives and the equals semantics of those differ from the object equals semantics, like stated above. Autoboxing would be performed if the elements were grabbed out of the array and compared.
Copy code
floatArrayOf(0f).first() == floatArrayOf(-0f).first() // true
precisely
The docs of
Float
don't mention this equals peculiarity anywhere aside from
contentEquals
on floatArray that I can find.