Kotlin 中 Double 和 Int 数字的错误比较

Wrong comparing of Double and Int numbers in Kotlin

我正在从事计算器项目,但是当我尝试使用两种类似的方法来比较双精度数和整数时,我得到了不同的结果。 所以我的问题是为什么这些比较方式不同?

//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)
}

代码结果:

"10.3 + -2" //input String

[10.3, -2.0] //queueNumbers

[+]//queueActions

pointNum = 8.3

8.3

result = 8.3

8

我觉得这很奇怪,因为如果我 运行 类似的代码,我会得到正确的结果:

var pointNum = 8.3

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

所以这段代码的结果是:

8.3

GitHub 上的完整代码:https://github.com/Trilgon/LearningKotlin

这对我来说是一个奇怪甚至有趣的问题,但我想我发现了问题所在。

在 Kotlin 中 Double class 是在 Primitives.kt 中定义的,至少在 build-in files not in the actual source. Here Double class implements an interface named Comparable<Double> which is another built-in Kotlin 文件中是这样。您可以看到该界面的一部分:

public operator fun compareTo(other: T): Int

所以让我们 return 到您的 code。我暂停这个问题的罪魁祸首是第一行。

import java.util.*

java.util 包中还有另一个 Comparable 接口,我想因为 Queue 是 Java class 而 poll方法可能 return 一个可为 null 的值,并且由于您已经将所有内容导入 java.util 包中,因此 compareToComparablejava.util 调用而不是 Kotlin 包中的那个.

我不知道 Java compareTo return 不同结果的原因。

我尝试将 java.util.* 替换为:

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

但没有任何改变。

不过,我找到了另一种解决方案,只需replacevar result = queueNumbers.poll()加上var result = queueNumbers.poll() ?: 0.0var result: Double = queueNumbers.poll()就可以解决了!

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

我认为这是编译器中的错误。讨论是here,我复制我的发现:

最小可重现示例:

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
}

这两个代码示例之间的技术差异在于值在 test1() 中装箱,在 test2() 中取消装箱。如果我们查看为 test2() 生成的字节码,一切看起来都符合预期:

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

它将整数值 8 转换为双精度值,然后将两个值作为双精度值进行比较。

但是如果我们查看 test1(),那里会发生一些奇怪的事情:

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

它做相反的事情:它将双精度值 8.3 转换为整数,然后将值作为整数进行比较。这就是为什么它说值是相同的。

有趣的是,即使是 IntelliJ 中的“Kotlin Bytecode”工具也能显示 test1():

的正确代码
INVOKEVIRTUAL java/lang/Double.doubleValue ()D
BIPUSH 8
I2D
INVOKESTATIC java/lang/Double.compare (DD)I

但真正生成的字节码是不同的,给出不同的结果。

我报告了这个问题:https://youtrack.jetbrains.com/issue/KT-52163