BigDecimal comparison?

(BigDecimal(“0E-8”) == BigDecimal.ZERO) - false
(BigDecimal(“0E-8”).compareTo(BigDecimal.ZERO) == 0) - true
(BigDecimal(“0E-8”) == BigDecimal.ZERO.setScale(8)) - true

Is it a bug?

The BigDecimal comparison functions are part of the JDK. Even if this behavior is a bug, it’s not a bug in Kotlin; you’ll see exactly the same with plain Java code.

Ah, so ==, <, > compares object references for BigDecimals in Kotlin?
But in this case (BigDecimal(“0E-8”) == BigDecimal.ZERO.setScale(8)) - should be false always not?

No. The == operator is compiled into a call of BigDecimal.equals(), and < and > are compiled into calls of BigDecimal.compareTo(), like for any other object. Both BigDecimal.equals() and BigDecimal.compareTo() are implemented in the JDK, and if you see any inconsistency, that’s a problem with the JDK implementation.

Consequence of this is that you get the following:

val zero = BigDecimal(“0E-8”)

(zero < BigDecimal.ZERO) // false
(zero > BigDecimal.ZERO) // false
(zero == BigDecimal.ZERO) // false

2 Likes

For BigDecimals equals checks both value and scale. d1.compareTo(d2) == 0 produces correct result and probably would be more meaningful for == operator.

1 Like

I would think if an object implements Comparable, == should rather use compareTo. Expectation would be that < and > have the same semantics as ==

Both Scala and Groovy gives true for BigDecimal(“0E-8”) == BigDecimal.ZERO so looks like they do it this way…

FWIW, over the years there have been major user complaints arguing that Groovy should translate == to equals even for Comparable types (e.g. [GROOVY-3364] == operator does not work anymore if Comparable is implemented! - ASF JIRA).

2 Likes

Equals may be faster (see: String), compareTo isn’t tuned for equality testing.

IMO == operator should be configurable. For most cases equals is fine, but there are classes where equals just doesn’t make sense as ==: BigDecimal, Array. There should be a way to alter default behaviour and call compareTo() for BigDecimals, Arrays.compare for Arrays, etc.

I’m not sure how you envision this configurability, especially in the presence of generic classes. It would be really weird to have == work differently for t: T in Foo<T> where T is BigDecimal and for t: BigDecimal.

1 Like

I agree, it would be strange. Probably no good solution for that. May be it’s worth to implement BigDecimal wrapper to overcome this problem, like Scala did,

I solve that problem using infix

infix fun BigDecimal.equalsTo(other: BigDecimal) = this.compareTo(other)==0

if(result equalsTo BigDecimal.ZERO){
}
1 Like

On the other hand, consider adding something like the spaceship operator that checks compareTo. But the syntax would be really strange, and I can see lexer problems coming (isn’t template<X<Y>> in C++ and x: T<U>=m in kotlin already bad enough?)

May be it’s worth to add isZero() method to the Kotlin implementation of BigDecimal?

fun BigDecimal.isZero() = compareTo(BigDecimal.ZERO) == 0

(got the code from the How to check if BigDecimal variable == 0 in java? - Stack Overflow post)

2 Likes

Use signum

1 Like

Some languages use a <> operator (‘is less than or greater than’) instead of a != or =/= or ¬= (‘is not equal to’).

Perhaps Kotlin could introduce a <> operator for Comparables?

This is not necessary as long as the implementation follows the rules for Comparable. If you look at the documentation you see that a.compareTo(b) == 0 <==> a == b.
Therefor your <> operator is the same as != in kotlin for Comparables.

The documentation for Comparable says:

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.”

So implementations violating that condition are following the rules for Comparable, and code must allow for the possiblity.

Most implementations comply, of course, but BigDecimal is one of those that doesn’t (which is why we’re discussing this). (It was done for a clear reason: while e.g. 1 and 1.0000 have the same numeric value, they have different scales and so it makes sense not to treat them as precisely equal.)

So in cases where the natural ordering is not consistent with equals, <> would behave differently from !=.

1 Like

signum() == 0 is much less obvious for me. And also longer. isZero() conveys intention much better.