Деление Scala на ноль дает разные результаты

Меня смущает то, как Scala обрабатывает деление на ноль. Вот фрагмент кода REPL.

scala> 1/0
java.lang.ArithmeticException: / by zero
  ... 33 elided

scala> 1.toDouble/0.toDouble
res1: Double = Infinity

scala> 0.0/0.0
res2: Double = NaN

scala> 0/0
java.lang.ArithmeticException: / by zero
  ... 33 elided

scala> 1.toInt/0.toInt
java.lang.ArithmeticException: / by zero
  ... 33 elided

Как вы можете видеть в приведенном выше примере, в зависимости от того, как вы делите на ноль, вы получаете одно из следующих:

  • "java.lang.ArithmeticException:/нулем"
  • "Двойной = NaN"
  • «Двойной = бесконечность»

Это делает отладку довольно сложной, особенно при работе с данными с неизвестными характеристиками. В чем причина этого подхода или, что еще лучше, вопрос, как унифицированно обрабатывать деление на ноль в Scala?


person Ahmedov    schedule 12.05.2017    source источник
comment
Я думаю, что это связано с разными типами данных. При выполнении разных, но связанных вычислений старайтесь всегда использовать одни и те же типы данных. Например: Double, Int и т. д.   -  person Titulum    schedule 12.05.2017
comment
Вы можете иметь дело с данными с неизвестными характеристиками, но в языке со статической типизацией, таком как Scala, вы не имеете дело с данными неизвестного типа.   -  person Alexey Romanov    schedule 12.05.2017
comment
@AlexeyRomanov Я понимаю, что ты имеешь в виду. Тем не менее, я полагаю, что большинство людей согласится с тем, что такой подход подвержен некоторым очень грязным ошибкам, а также очень утомителен для обработки каждой части арифметической операции, которую вы пишете.   -  person Ahmedov    schedule 12.05.2017


Ответы (3)


Все дело в делении по нулевым правилам для разных типов.

0 / 0 – это целочисленное деление на ноль (поскольку оба аргумента являются целочисленными литералами), и это требуется для выдачи java.lang.ArithmeticException.

1.toDouble/0.toDouble – это деление числа с плавающей запятой на ноль с положительным числителем, которое необходимо для получения +Infinity.

0.0/0.0 — это деление с плавающей запятой на ноль с нулевым числителем, которое необходимо для вычисления +NaN.

Первое — это соглашение Java и Scala, два других — свойства плавающей запятой IEEE754, которые используют и Java, и Scala.

person Bathsheba    schedule 12.05.2017

Doubles и Floats являются значениями floating-point (подробнее здесь), которые могут быть представлены как +Infinity -Infinity и NaN как определено в стандарте IEEE 754.
Integers — это fixed numbers, у которых нет возможности явно указать неверные данные, поэтому они выдают exceptions
Единым решением этой проблемы будет использование метода getOrElse для Try

Try(x/y).getOrElse(0)

Если вы хотите восстановиться только на ArithmeticException, вы можете использовать recover и get

Try(x/y).recover{ case _: ArithmeticException => 0 }.get

recover позволяет преобразовать Failure в Success
Вы также можете использовать Try в Option, чтобы вернуть "нет результата" без отображения исключения

Try(x/y).toOption
person Ramesh Maharjan    schedule 12.05.2017
comment
Спасибо. Ваш ответ весьма полезен тем, что в нем подробно описаны возможные решения. - person Ahmedov; 12.05.2017
comment
Я обнаружил, что Try(1.0 / 0.0) вернет Success(Infinity), а не Failure, а OrElse не будет возвращено. - person D-Dᴙum; 06.07.2019

Вы можете использовать частичные функции для чего-то подобного. Например:

object MyObject {
    def main(args: Array[String]) {

        println(safeDiv.isDefinedAt(1.0, 1.0)) // true
        println(safeDiv.isDefinedAt(1.0, 0.0)) // false
        println(safeDiv(1.0, 1.0))             // 1.0 
        println(safeDiv(1.0, 0.0))             // crash

    }

    def safeDiv: PartialFunction[(Double, Double), Double] = { 
        case(a,b) if b != 0.0 => a/b 

    }   
}

Частичные функции позволяют проверить, определена ли функция для данного входа. В приведенном выше примере я сказал, что функция safeDiv не определена, если делитель равен 0,0. Поэтому вы можете проверить, будет ли функция выполняться с учетом ввода. Проверка необязательна, однако safeDiv(1.0, 0.0) не выполнится.

Частичные функции — ваш друг против чего-то вроде этого:

scala> (1.0/0.0).toInt
res22: Int = 2147483647
person Akavall    schedule 13.05.2017
comment
Большой. думаю пригодится - person Ahmedov; 17.05.2017