[ https://issues.apache.org/jira/browse/GROOVY-9094?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16834144#comment-16834144 ]
Szymon Stępniak edited comment on GROOVY-9094 at 5/6/19 7:41 PM: ----------------------------------------------------------------- Apologize for the late response. Thanks [~blackdrag] and [~emilles] for your valuable comments! [~emilles], I can confirm that turning off int optimization made the calculation correct. I checked how Java reacts to the similar corner case, and it turns out Groovy and Java do pretty much the same. Consider the following example. {code: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)); } } {code} Running this example prints: {code} 127.0 127.5 {code} In the {{div2}} method Java compiler forces us to cast at least one integer to double; otherwise compilation fails with the {code} Error:(10, 18) java: incompatible types: int cannot be converted to java.lang.Double {code} error. Now, if we take a look at the decompiled Java code to see what kind of optimizations Java compiler applies, we the following: {code:java} 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)); } } {code} The {{div}} method is quite similar to what we have seen in the optimized Groovy code (the division of two integers that produces an integer gets cast to a double). If we use a Java-based bytecode as a reference of what is the correct behavior on a JVM, then it turns out that the default Groovy behavior is fine, and turning off compiler's int optimization actually "breaks" the compatibility. (By "breaks" I mean it produces the logically correct result, but that is different compared to the Java-based bytecode.) I guess there is nothing to do with this ticket anymore. was (Author: wololock): Apologize for the late response. Thanks [~blackdrag] and [~emilles] for your valuable comments! [~emilles], I can confirm that turning off int optimization made the calculation correct. I checked how Java reacts to the similar corner case, and it turns out Groovy and Java do pretty much the same. Consider the following example. {code: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)); } } {code} Running this example prints: {code:text} 127.0 127.5 {code} In the {{div2}} method Java compiler forces us to cast at least one integer to double; otherwise compilation fails with the {code:text} Error:(10, 18) java: incompatible types: int cannot be converted to java.lang.Double {code} error. Now, if we take a look at the decompiled Java code to see what kind of optimizations Java compiler applies, we the following: {code:java} 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)); } } {code} The {{div}} method is quite similar to what we have seen in the optimized Groovy code (the division of two integers that produces an integer gets cast to a double). If we use a Java-based bytecode as a reference of what is the correct behavior on a JVM, then it turns out that the default Groovy behavior is fine, and turning off compiler's int optimization actually "breaks" the compatibility. (By "breaks" I mean it produces the logically correct result, but that is different compared to the Java-based bytecode.) I guess there is nothing to do with this ticket anymore. > Coercion to primitive double from primitive integer produces different > results in dynamic and static compilation > ---------------------------------------------------------------------------------------------------------------- > > Key: GROOVY-9094 > URL: https://issues.apache.org/jira/browse/GROOVY-9094 > Project: Groovy > Issue Type: Bug > Components: bytecode, Compiler > Affects Versions: 3.0.0-alpha-4, 2.5.6 > Reporter: Szymon Stępniak > Priority: Major > > Ticket created based on the following question on Stack Overflow - > https://stackoverflow.com/questions/55789411/groovy-primitive-double-arithmetic > It seems like primitive double coercion is unstable. Consider the following > example: > {code:groovy} > void ex1() { > double x = 255 / 2 > println x > } > void ex2() { > Double x = 255 / 2 > println x > } > void ex3() { > def x = 255 / 2 > println x > } > void ex4() { > println 255 / 2 > } > ex1() > ex2() > ex3() > ex4() > {code} > It produces the following output: > {code:bash} > 127.0 > 127.5 > 127.5 > 127.5 > {code} > I checked the generated bytecode and here is what the {{ex1}} method > implementation looks like: > {code:java} > 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); > } > {code} > And here is what {{ex2}} method's bytecode looks like: > {code:java} > 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); > } > {code} > If we compile statically the first method, it will produce {{127.5}} output, > and will generate the following bytecode: > {code:java} > public void ex1() { > double x = > DefaultTypeTransformation.doubleUnbox(NumberNumberDiv.div(255, 2)); > ((qweqwe23)this).println(x); > Object var10000 = null; > } > {code} > But the weirdest thing happens if we try to use type checking. My first > impression was that this unstable coercion could be avoided if we use type > checking. However, it doesn't work. If I add {{@TypeChecked}} annotation to > {{ex1}} method, it does not make any effect - the code runs and it still > produces {{127.0}} output. But when I add {{@TypeChecked}} to {{ex2}} method, > it does not run and produces the following compilation error. > {code:bash} > Error:(10, 20) Groovyc: [Static type checking] - Cannot assign value of type > java.math.BigDecimal to variable of type java.lang.Double > {code} -- This message was sent by Atlassian JIRA (v7.6.3#76005)