Groovy примитивная двойная арифметика - PullRequest
3 голосов
/ 22 апреля 2019

Это дает 127

double middle = 255 / 2

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

Double middle = 255 / 2

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

double middle = (255 / 2) as double

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

1 Ответ

2 голосов
/ 22 апреля 2019

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

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

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

public void ex1() {
    CallSite[] var1 = $getCallSiteArray();
    double x = 0.0D;
    if (BytecodeInterface8.isOrigInt() && BytecodeInterface8.isOrigD() && !__$stMC && !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() && !__$stMC && !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}}. Рассмотрим следующий пример на 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...