Это на самом деле не имеет ничего общего с 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
.