'Wrong comparing of Double and Int numbers in Kotlin

I'm working on calculator project but when I tried to use two similar ways of comparing double and int numbers I got different results. So my question is Why are these ways of comparing works differently?

//some code that parse the string
//...
//code of calculating:

fun calculateIn(queueNumbers: Queue<Double>, queueActions: Queue<Char>) {
var action: Char
var result = queueNumbers.poll()
var operand: Double

while (!queueNumbers.isEmpty()) {
    operand = queueNumbers.poll()
    action = queueActions.poll()
    when (action) {
        '-' -> result -= operand
        '+' -> result += operand
        '*' -> result *= operand
        '/' -> result /= operand
        '%' -> result = result % operand * -1.0
    }
  }
  var pointNum = 8.3

  println("pointNum = " + pointNum)
  println(if(pointNum.compareTo(pointNum.toInt()) == 0) pointNum.toInt() else pointNum)

  println("result = " + result)
  println(if(result.compareTo(result.toInt()) == 0) result.toInt() else result)
}

Result of code:

"10.3 + -2" //input String

[10.3, -2.0] //queueNumbers

[+]//queueActions

pointNum = 8.3

8.3

result = 8.3

8

I think that is strange because if I run similar code I will get the correct result:

var pointNum = 8.3

println(if(pointNum.compareTo(pointNum.toInt()) == 0) pointNum.toInt() else pointNum)

So there is result of this code:

8.3

Full code on GitHub: https://github.com/Trilgon/LearningKotlin



Solution 1:[1]

This was a weird and even funny problem to me, But I think I found out what's the problem.

In Kotlin Double class is defined inside Primitives.kt, well at least in the build-in files not in the actual source. Here Double class implements an interface named Comparable<Double> which is another built-in Kotlin file. You can see a portion of that interface:

public operator fun compareTo(other: T): Int

So let's return to your code. I suspend culprit of this problem is the very first line.

import java.util.*

Inside the java.util package there is another Comparable interface, I think since Queue is a Java class and the poll method may return a nullable value, and since you already imported everything inside java.util package then compareTo is called from Comparable from java.util not the one in Kotlin package.

The reason why Java's compareTo returns a different result is unknown to me.

I tried replacing java.util.* with:

import java.util.Queue
import java.util.LinkedList

But nothing changed.

However, I found another solution, just replace var result = queueNumbers.poll() with var result = queueNumbers.poll() ?: 0.0 or var result: Double = queueNumbers.poll() then it would be fixed!

//original
var result = queueNumbers.poll()
//method 1
var result : Double = queueNumbers.poll()
//method 2
var result = queueNumbers.poll() ?: 0.0

enter image description here

Solution 2:[2]

I believe this is a bug in the compiler. Discussion is here, I copy my findings:

Minimal reproducible example:

fun test1(): Int {
    val d: Double?
    d = 8.3
    return d.compareTo(8) // 0
}

fun test2(): Int {
    val d: Double
    d = 8.3
    return d.compareTo(8) // 1
}

Technical difference between these two code samples is that the value is boxed in test1() and unboxed in test2(). If we look at the generated bytecode for test2(), everything looks as expected:

 7: bipush        8
 9: i2d
10: invokestatic  #63                 // Method java/lang/Double.compare:(DD)I

It converts integer value 8 to double and then compares both values as doubles.

But if we look into test1(), something bizarre happens there:

10: invokevirtual #52                 // Method java/lang/Double.doubleValue:()D
13: d2i
14: bipush        8
16: invokestatic  #58                 // Method kotlin/jvm/internal/Intrinsics.compare:(II)I

It does the opposite: it converts double value 8.3 to integer and then compares values as integers. This is why it says values are the same.

What is interesting, even "Kotlin Bytecode" tool in IntelliJ shows the correct code for test1():

INVOKEVIRTUAL java/lang/Double.doubleValue ()D
BIPUSH 8
I2D
INVOKESTATIC java/lang/Double.compare (DD)I

But the real generated bytecode is different and gives different results.

I reported the problem: https://youtrack.jetbrains.com/issue/KT-52163

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2