A way to change Groovy default behavior with numbers

50 views Asked by At

Is there is a way to change Groovy language behavior (and if not I think it could be interesting) that makes it convert numbers to float by default, instead of BigDecimal or Double?

Why do I need that? Because I use libgdx, which uses float in most of its methods for calculations, and when I use numbers with calculations without cast or using (someCalculation).floatValue() after, they always convert to BigDecimal or Double. And it is a bit annoying to always do (x* 5 + 8 /3).floatValue().

If there is a config file, annotation or something like that I could config it to convert always to float(by default), and if needed I specified a type? It will make the code less verbose, but is more verbose than java in that part at least... I didn't find anything like that in Groovy docs and web search

1

There are 1 answers

0
daggett On

unfortunately floatingpoint math implementation in groovy based on double for both - float and double:

https://github.com/apache/groovy/blob/master/src/main/java/org/codehaus/groovy/runtime/typehandling/FloatingPointMath.java

And FloatingPointMath used from here: https://github.com/apache/groovy/blob/84e1299e02dd6d81577e15fe018b7c9a1d70b714/src/main/java/org/codehaus/groovy/runtime/typehandling/NumberMath.java#L230


However according to this example we could change the behavior of operations for a specific class with metaclass programming (there is a simple example for Integer):

https://docs.groovy-lang.org/latest/html/documentation/core-metaprogramming.html#_magic_package

Each numeric operator in groovy mapped to a method:

  • + -> plus
  • - -> minus
  • * -> multiply
  • ...

myFloat * myInt will be processed in groovy as myFloat.multiply(myInt)

With metaprogramming you can override the behavior of those methods for Float meta class. So, whenever Float is a first parameter of math operation we could return a float result.

you should use javac to compile following class to use original java floating point operations instead of groovy.

filename: FloatMetaClass.java

package groovy.runtime.metaclass.java.lang;

import groovy.lang.MetaClass;
import groovy.lang.DelegatingMetaClass;
import org.codehaus.groovy.runtime.wrappers.PojoWrapper;

public class FloatMetaClass extends DelegatingMetaClass {
  public FloatMetaClass(MetaClass metaClass) { super(metaClass); }
  public FloatMetaClass(Class theClass) { super(theClass); }

  public Object invokeMethod(Object obj, String name, Object[] arg) {
    float a = ((Float)obj).floatValue();
    if (arg.length==1) {
      Object arg0 = arg[0];
      if (arg0 instanceof PojoWrapper) arg0 = ((PojoWrapper)arg0).unwrap();
      if (arg0 instanceof Number) {
        Number b = (Number)arg0;
        switch(name.hashCode()){
          case 0x00018491 /* div        */: return a / b.floatValue();
          case 0xb0a37dbe /* divint     */: return a / b.intValue();
          case 0x06316870 /* minus      */: return a - b.floatValue();
          case 0x0001a702 /* mod        */: return a % b.floatValue();
          case 0x26f8a624 /* multiply   */: return a * b.floatValue();
          case 0x00348d9a /* plus       */: return a + b.floatValue();
          case 0x065e8905 /* power      */: return (float) Math.pow(a, b.floatValue());
        }
      }
    } else if (arg.length==0) {
      switch(name.hashCode()){
        case 0x18b9a481 /* unaryMinus */: return -a;
        case 0x3a9c0b69 /* unaryPlus  */: return a;
      }
    }
    return super.invokeMethod(obj, name, arg);
  }
}

command to compile:

javac -cp ${GROOVY_HOME}/lib/* -d ./ FloatMetaClass.java

This should create compiled version of java class in a special folder groovy/runtime/metaclass/java/lang/FloatMetaClass.class

Now running groovy with a classpath that is pointing to this folder will do the trick

Let's imagine the folder with compiled file mentioned above is in a current folder.

groovy -cp . Test.groovy 

Where Test.groovy:

def assertf = {a,b -> assert a==b; assert a.getClass()==b.getClass();}

assertf  3d * 2,   6d             //<-- first operand double - result is double
assertf  1230.1f + 4.4d, 1234.5f  //<-- first operand float - result is float

assertf  3f * 2,   6f
assertf  3f / 2,   1.5f
assertf  3f / 2f,  1.5f
assertf  3f % 2,   1f

//from question:
def x = 123.45f
assertf x * 5 + 8 /3,  619.9167f