Groovy примитивная двойная арифметика

#groovy #primitive-types

#groovy #примитивные типы

Вопрос:

Это дает 127

 double middle = 255 / 2
  

В то время как это дает 127,5

 Double middle = 255 / 2
  

Между тем это также дает 127,5

 double middle = (255 / 2) as double
  

Я знаю, что Groovy работает с BigDecimal по умолчанию, но для меня это огромная ошибка! Как это может быть?

Ответ №1:

На самом деле это не имеет ничего общего с BigDecimals , а скорее с приведением типа от примитивного integer к примитивному double . Эта проблема вызвана компилятором Groovy и (скорее всего) неправильным байт-кодом, который он создает. Взгляните на следующее представление байт-кода первого случая. Следующий заводной код:

 void ex1() {
    double x = 255 / 2
    println x
}
  

компилируется в байт-код, который может быть представлен как:

 public void ex1() {
    CallSite[] var1 = $getCallSiteArray();
    double x = 0.0D;
    if (BytecodeInterface8.isOrigInt() amp;amp; BytecodeInterface8.isOrigD() amp;amp; !__$stMC amp;amp; !BytecodeInterface8.disabledStandardMetaClass()) {
        int var5 = 255 / 2;
        x = (double)var5;
    } else {
        Object var4 = var1[5].call(255, 2);
        x = DefaultTypeTransformation.doubleUnbox(var4);
    }

    var1[6].callCurrent(this, x);
}
  

Это показывает, что в этом случае невозможно получить 127.5 в результате, потому что результат 255 / 2 выражения сохраняется в переменной типа int . Похоже, что это пример непоследовательного поведения, потому что вот как Double выглядит байт-код используемого метода:

 public void ex2() {
    CallSite[] var1 = $getCallSiteArray();
    Double x = null;
    if (BytecodeInterface8.isOrigInt() amp;amp; !__$stMC amp;amp; !BytecodeInterface8.disabledStandardMetaClass()) {
        Object var4 = var1[8].call(255, 2);
        x = (Double)ScriptBytecodeAdapter.castToType(var4, Double.class);
    } else {
        Object var3 = var1[7].call(255, 2);
        x = (Double)ScriptBytecodeAdapter.castToType(var3, Double.class);
    }

    var1[9].callCurrent(this, x);
}
  

Основная проблема с этим вариантом использования заключается в том, что добавление @TypeChecked не мешает вам совершать эту ошибку — компиляция проходит, и возвращается неверный результат. Однако, когда мы добавляем @TypeChecked аннотацию к методу, который использует Double ошибку компиляции. Добавление @CompileStatic решает проблему.

Я провел несколько тестов и могу подтвердить, что эта проблема существует в последних версиях 2.5.6, а также 3.0.0-alpha-4. Я создал отчет об ошибке в проекте Groovy JIRA. Спасибо, что нашли и сообщили о проблеме!

ОБНОВЛЕНИЕ: Java делает то же самое

Похоже, что это не ошибка Groovy — именно так работает Java. В Java вы можете сохранить результат деления двух целых чисел в переменной double, но вы не получите ничего, кроме приведения целого числа к double . С типом {{Double}} все по-другому с точки зрения синтаксиса, но довольно похоже с точки зрения байт-кода. С помощью {{Double}} вам необходимо явно привести хотя бы одну часть уравнения к типу {{double}}, что приводит к байт-коду, который преобразует оба целых числа в {{double}} . Рассмотрим следующий пример на Java:

 final class IntDivEx {

    static double div(int a, int b) {
        return a / b;
    }

    static Double div2(int a, int b) {
        return a / (double) b;
    }

    public static void main(String[] args) {
        System.out.println(div(255,2));
        System.out.println(div2(255,2));
    }
}
  

Когда вы запускаете его, вы получаете:

 127.0
127.5 
  

Теперь, если вы посмотрите на байт-код, который он создает, вы увидите что-то вроде этого:

 //
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

final class IntDivEx {
    IntDivEx() {
    }

    static double div(int a, int b) {
        return (double)(a / b);
    }

    static Double div2(int a, int b) {
        return (double)a / (double)b;
    }

    public static void main(String[] args) {
        System.out.println(div(255, 2));
        System.out.println(div2(255, 2));
    }
}
  

Единственное различие (с точки зрения синтаксиса) между Groovy и Java заключается в том, что Groovy позволяет неявно приводить целое число к Double , и именно поэтому

 Double x = 255 / 2
  

является правильным утверждением в Groovy, в то время как Java в этом случае завершается ошибкой во время компиляции со следующей ошибкой:

 Error:(10, 18) java: incompatible types: int cannot be converted to java.lang.Double
  

Вот почему в Java вам нужно использовать приведение при назначении из целого числа в Double .