[ 
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)

Reply via email to