Неправильное сравнение чисел Double и Int в Котлине

Я работаю над проектом калькулятора, но когда я попытался использовать два похожих способа сравнения двойных и целых чисел, я получил разные результаты. Итак, мой вопрос: почему эти способы сравнения работают по-разному?

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

apply возвращает приемник. Например. 3.apply { 4 } == 3
gpunto 22.04.2022 19:15

Помимо вашего вопроса, на который вы ответили в предыдущем комментарии, вы должны знать, что Double не подходит для калькулятора из-за потери точности. Вместо этого вы должны использовать BigDecimal.

Tenfour04 22.04.2022 19:17

Спасибо, gpunto, теперь я разобрался с apply, но почему второй способ (без применения) сравнения не работает, как во втором случае?

Trilgon 22.04.2022 19:26

Я имею в виду, почему println(result.compareTo(result.toInt())) возвращает 0?

Trilgon 22.04.2022 19:40

Я не уверен, что вы имеете в виду, это печатает 1: println(8.3.compareTo(8.3.toInt()))

gpunto 22.04.2022 19:45

Да, но этот println(result.compareTo(result.toInt())) печатает 0

Trilgon 22.04.2022 19:47

То, что он печатает, зависит от значения result. Что это в вашем случае? Он напечатает 0, когда result = X.0

gpunto 22.04.2022 19:52

Посмотрите на код в моем вопросе. В обоих случаях это 8.3, но когда я использую println(result.compareTo(result.toInt())), он печатает 0, а когда я использую println(pointNum.compareTo(pointNum.toInt())), он печатает 1.

Trilgon 22.04.2022 19:55

Я отредактировал свой вопрос, чтобы сделать его более понятным

Trilgon 22.04.2022 20:04

Честно говоря, я не совсем уверен. Либо это ошибка, либо я упускаю здесь что-то очень важное (детская площадка)

gpunto 22.04.2022 20:25

Это грустно, но я проверил свой код на игровой площадке и получил тот же результат.

Trilgon 22.04.2022 20:32

@gpunto, я только что опубликовал полный код проекта на GitHub. Может поможет: github.com/Trilgon/LearningKotlin

Trilgon 22.04.2022 20:39

Я имел в виду, что ты прав, и у меня это тоже не работает. Вы должны сообщить об этом в JetBrains.

gpunto 22.04.2022 20:39

Хорошо, я сделаю. Спасибо, в любом случае

Trilgon 22.04.2022 20:42
3 метода стилизации элементов HTML
3 метода стилизации элементов HTML
Когда дело доходит до применения какого-либо стиля к нашему HTML, существует три подхода: встроенный, внутренний и внешний. Предпочтительным обычно...
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
1
14
84
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Для меня это была странная и даже забавная проблема, но, кажется, я понял, в чем проблема.

В Котлине класс Double определен внутри Primitives.kt, ну, по крайней мере, в встроенные файлы, а не в фактическом источнике. Здесь класс Double реализует интерфейс с именем Comparable<Double>, который является еще одним файлом встроенный Kotlin. Вы можете увидеть часть этого интерфейса:

public operator fun compareTo(other: T): Int

Итак, вернемся к вашему код. Подвешиваю виновником этой проблемы самую первую строчку.

import java.util.*

Внутри пакета java.util есть еще один интерфейс Comparable, я думаю, поскольку Queue — это класс Java, а метод poll может возвращать значение, допускающее значение NULL, а поскольку вы уже импортировали все внутри пакета java.util, то compareTo вызывается из Comparable из java.util, а не из Kotlin упаковка.

Причина, по которой compareTo в Java возвращает другой результат, мне неизвестна.

Я попытался заменить java.util.* на:

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

Но ничего не изменилось.

Однако я нашел другое решение, просто заменятьvar result = queueNumbers.poll() с var result = queueNumbers.poll() ?: 0.0 или var result: Double = queueNumbers.poll() тогда бы это было исправлено!

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

enter image description here

Большое спасибо, это работает. Кстати, похоже на проблему с компилятором Kotlin.

Trilgon 22.04.2022 22:39

Я считаю, что это ошибка в компиляторе. Обсуждение здесь, копирую свои выводы:

Минимальный воспроизводимый пример:

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 в целое число, а затем сравнивает значения как целые числа. Вот почему он говорит, что значения одинаковы.

Что интересно, даже инструмент Kotlin Bytecode в IntelliJ показывает правильный код для test1():

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

Но реальный сгенерированный байт-код отличается и дает другие результаты.

Я сообщил о проблеме: https://youtrack.jetbrains.com/issue/KT-52163

У меня были некоторые мысли о проблеме с компилятором, но я не знал, как это проверить. Спасибо за подробный ответ и отчет для JetBrains.

Trilgon 23.04.2022 08:09

Другие вопросы по теме