Repository: groovy Updated Branches: refs/heads/master 679eb23ce -> de9c8803c
GROOVY-7636: Fall back to BigDecimalMath for custom Numbers (closes #151) Project: http://git-wip-us.apache.org/repos/asf/groovy/repo Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/de9c8803 Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/de9c8803 Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/de9c8803 Branch: refs/heads/master Commit: de9c8803c72175a758df8492e504ab3b582ba730 Parents: 679eb23 Author: Thibault Kruse <thibault.kr...@gmx.de> Authored: Mon Oct 19 19:34:52 2015 +0200 Committer: paulk <pa...@asert.com.au> Committed: Mon Jul 25 19:57:18 2016 +1000 ---------------------------------------------------------------------- .../groovy/runtime/typehandling/NumberMath.java | 34 ++- src/test/groovy/NumberMathTest.groovy | 180 -------------- .../runtime/typehandling/NumberMathTest.groovy | 232 +++++++++++++++++++ 3 files changed, 259 insertions(+), 187 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/groovy/blob/de9c8803/src/main/org/codehaus/groovy/runtime/typehandling/NumberMath.java ---------------------------------------------------------------------- diff --git a/src/main/org/codehaus/groovy/runtime/typehandling/NumberMath.java b/src/main/org/codehaus/groovy/runtime/typehandling/NumberMath.java index c953ffd..37fa65f 100644 --- a/src/main/org/codehaus/groovy/runtime/typehandling/NumberMath.java +++ b/src/main/org/codehaus/groovy/runtime/typehandling/NumberMath.java @@ -147,6 +147,14 @@ public abstract class NumberMath { return number instanceof Integer; } + public static boolean isShort(Number number) { + return number instanceof Short; + } + + public static boolean isByte(Number number) { + return number instanceof Byte; + } + public static boolean isLong(Number number) { return number instanceof Long; } @@ -179,27 +187,35 @@ public abstract class NumberMath { * F D D D D D D * L bD bI D D L L * I bD bI D D L I - * + * * Note that for division, if either operand isFloatingPoint, the result will be floating. Otherwise, * the result is BigDecimal */ public static NumberMath getMath(Number left, Number right) { + // FloatingPointMath wins according to promotion Matrix if (isFloatingPoint(left) || isFloatingPoint(right)) { return FloatingPointMath.INSTANCE; } - if (isBigDecimal(left) || isBigDecimal(right)) { + NumberMath leftMath = getMath(left); + NumberMath rightMath = getMath(right); + + if (leftMath == BigDecimalMath.INSTANCE || rightMath == BigDecimalMath.INSTANCE) { return BigDecimalMath.INSTANCE; } - if (isBigInteger(left) || isBigInteger(right)) { + if (leftMath == BigIntegerMath.INSTANCE || rightMath == BigIntegerMath.INSTANCE) { return BigIntegerMath.INSTANCE; } - if (isLong(left) || isLong(right)) { + if (leftMath == LongMath.INSTANCE || rightMath == LongMath.INSTANCE) { return LongMath.INSTANCE; } - return IntegerMath.INSTANCE; + if (leftMath == IntegerMath.INSTANCE || rightMath == IntegerMath.INSTANCE) { + return IntegerMath.INSTANCE; + } + // also for custom Number implementations + return BigDecimalMath.INSTANCE; } - private static NumberMath getMath(Number number) { + /* package private */ static NumberMath getMath(Number number) { if (isLong(number)) { return LongMath.INSTANCE; } @@ -212,7 +228,11 @@ public abstract class NumberMath { if (isBigInteger(number)) { return BigIntegerMath.INSTANCE; } - return IntegerMath.INSTANCE; + if (isInteger(number) || isShort(number) || isByte(number)) { + return IntegerMath.INSTANCE; + } + // also for custom Number implementations + return BigDecimalMath.INSTANCE; } //Subclasses implement according to the type promotion hierarchy rules http://git-wip-us.apache.org/repos/asf/groovy/blob/de9c8803/src/test/groovy/NumberMathTest.groovy ---------------------------------------------------------------------- diff --git a/src/test/groovy/NumberMathTest.groovy b/src/test/groovy/NumberMathTest.groovy deleted file mode 100644 index 686a5ee..0000000 --- a/src/test/groovy/NumberMathTest.groovy +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package groovy - -/** - * Basic NumberMath test. - * @see org.codehaus.groovy.runtime.typehandling.NumberMath - */ -class NumberMathTest extends GroovyTestCase { - - void testPromotions() { - def C = '1'.toCharacter() - def B = new Byte("1") - def I = new Integer(1) - def L = new Long(1) - def F = new Float("1.0") - def D = new Double("1.0") - def BI = new BigInteger("1") - def BD = new BigDecimal("1.0") - - //+, -, and * all promote the same way, so sample the matrix - assert C + B instanceof Integer - assert C - BD instanceof BigDecimal - assert B + C instanceof Integer - assert B + I instanceof Integer - assert B + F instanceof Double - - assert I + I instanceof Integer - assert I - F instanceof Double - assert I * D instanceof Double - assert I + BI instanceof BigInteger - assert I - BD instanceof BigDecimal - - assert F * L instanceof Double - assert D + L instanceof Double - assert BI - L instanceof BigInteger - assert BD * L instanceof BigDecimal - - assert F + F instanceof Double - assert F - BI instanceof Double - assert F * BD instanceof Double - - assert F + D instanceof Double - assert BI - D instanceof Double - assert BD * D instanceof Double - - assert BI + BI instanceof BigInteger - assert BD - BI instanceof BigDecimal - assert BD * BD instanceof BigDecimal - - //Division (/) promotes differently so change the expected results: - assert I / I instanceof BigDecimal - assert I / F instanceof Double - assert I / D instanceof Double - assert I / BI instanceof BigDecimal - assert I / BD instanceof BigDecimal - - assert F / L instanceof Double - assert D / L instanceof Double - assert BI / L instanceof BigDecimal - assert BD / L instanceof BigDecimal - - assert F / F instanceof Double - assert F / BI instanceof Double - assert F / BD instanceof Double - - assert F / D instanceof Double - assert BI / D instanceof Double - assert BD / D instanceof Double - - assert BI / BI instanceof BigDecimal - assert BD / BI instanceof BigDecimal - assert BD / BD instanceof BigDecimal - } - - void testOperations() { - def I1 = new Integer(1) - def I2 = new Integer(2) - def I3 = new Integer(3) - def L1 = new Long(1) - def L2 = new Long(2) - def L3 = new Long(3) - def F1 = new Float("1.0") - def F2 = new Float("2.0") - def D1 = new Double("1.0") - def D2 = new Double("2.0") - def BI1 = new BigInteger("1") - def BI2 = new BigInteger("2") - def BD1 = new BigDecimal("1.0") - def BD2 = new BigDecimal("2.0") - def BD20 = new BigDecimal("2.00") - - assert I1 / I2 instanceof BigDecimal - assert I1 / I2 == new BigDecimal("0.5") - assert F1 / F2 instanceof Double - assertEquals F1 / F2, 0.5, 0.0000000001 - assert D1 / D2 instanceof Double - assertEquals D1 / D2, 0.5, 0.0000000001 - - assert I1.intdiv(I2) instanceof Integer - assert I1.intdiv(I2) == 0 - - assert I3.intdiv(I2) instanceof Integer - assert I3.intdiv(I2) == 1 - - assert L1.intdiv(I2) instanceof Long - assert L1.intdiv(I2) == 0 - - assert L3.intdiv(L2) instanceof Long - assert L3.intdiv(L2) == 1 - - assert BI1.intdiv(BI2) instanceof BigInteger - assert BI1.intdiv(BI2) == 0 - - assert I1 / I3 instanceof BigDecimal - assert I1 / I3 == new BigDecimal("0.3333333333") - - assert I2 / I3 instanceof BigDecimal - assert I2 / I3 == new BigDecimal("0.6666666667") - - assert I1 / BD2 instanceof BigDecimal - - //Test keeping max scale of (L, R or 10) - def BBD1 = new BigDecimal("0.12345678901234567") - assert BD1 + BBD1 == new BigDecimal("1.12345678901234567") - - def BBD2 = new BigDecimal(".000000000000000008") - assert BBD1 + BBD2 == new BigDecimal("0.123456789012345678") - } - - void testUnsupportedIntDivision() { - try { - 1.0.intdiv(3) - } catch (UnsupportedOperationException ignore) { - return - } - fail("Should catch an UnsupportedOperationException") - - try { - 1.0G.intdiv(3) - } catch (UnsupportedOperationException ignore) { - return - } - fail("Should catch an UnsupportedOperationException") - } - - void testGetMath() { - assert 20 == new Short("10") << 1 - assert 2 == new Byte("1") << 1 - } - - void testLongDivAssign() { - long d = 100L - d /= 33L - assert d.class == Long.class - } - - void testIntegerPlusCastException() { - shouldFail(ClassCastException) { - Integer i = 12 - i += " angry men" - } - } -} http://git-wip-us.apache.org/repos/asf/groovy/blob/de9c8803/src/test/org/codehaus/groovy/runtime/typehandling/NumberMathTest.groovy ---------------------------------------------------------------------- diff --git a/src/test/org/codehaus/groovy/runtime/typehandling/NumberMathTest.groovy b/src/test/org/codehaus/groovy/runtime/typehandling/NumberMathTest.groovy new file mode 100644 index 0000000..eff11ce --- /dev/null +++ b/src/test/org/codehaus/groovy/runtime/typehandling/NumberMathTest.groovy @@ -0,0 +1,232 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.codehaus.groovy.runtime.typehandling + +import static org.codehaus.groovy.runtime.typehandling.NumberMath.getMath + +/** + * Basic NumberMath test. + * @see org.codehaus.groovy.runtime.typehandling.NumberMath + */ +class NumberMathTest extends GroovyTestCase { + + void testPromotions() { + def C = '1'.toCharacter() + def B = new Byte("1") + def I = new Integer(1) + def L = new Long(1) + def F = new Float("1.0") + def D = new Double("1.0") + def BI = new BigInteger("1") + def BD = new BigDecimal("1.0") + + //+, -, and * all promote the same way, so sample the matrix + assert C + B instanceof Integer + assert C - BD instanceof BigDecimal + assert B + C instanceof Integer + assert B + I instanceof Integer + assert B + F instanceof Double + + assert I + I instanceof Integer + assert I - F instanceof Double + assert I * D instanceof Double + assert I + BI instanceof BigInteger + assert I - BD instanceof BigDecimal + + assert F * L instanceof Double + assert D + L instanceof Double + assert BI - L instanceof BigInteger + assert BD * L instanceof BigDecimal + + assert F + F instanceof Double + assert F - BI instanceof Double + assert F * BD instanceof Double + + assert F + D instanceof Double + assert BI - D instanceof Double + assert BD * D instanceof Double + + assert BI + BI instanceof BigInteger + assert BD - BI instanceof BigDecimal + assert BD * BD instanceof BigDecimal + + //Division (/) promotes differently so change the expected results: + assert I / I instanceof BigDecimal + assert I / F instanceof Double + assert I / D instanceof Double + assert I / BI instanceof BigDecimal + assert I / BD instanceof BigDecimal + + assert F / L instanceof Double + assert D / L instanceof Double + assert BI / L instanceof BigDecimal + assert BD / L instanceof BigDecimal + + assert F / F instanceof Double + assert F / BI instanceof Double + assert F / BD instanceof Double + + assert F / D instanceof Double + assert BI / D instanceof Double + assert BD / D instanceof Double + + assert BI / BI instanceof BigDecimal + assert BD / BI instanceof BigDecimal + assert BD / BD instanceof BigDecimal + } + + void testOperations() { + def I1 = new Integer(1) + def I2 = new Integer(2) + def I3 = new Integer(3) + def L1 = new Long(1) + def L2 = new Long(2) + def L3 = new Long(3) + def F1 = new Float("1.0") + def F2 = new Float("2.0") + def D1 = new Double("1.0") + def D2 = new Double("2.0") + def BI1 = new BigInteger("1") + def BI2 = new BigInteger("2") + def BD1 = new BigDecimal("1.0") + def BD2 = new BigDecimal("2.0") + def BD20 = new BigDecimal("2.00") + + assert I1 / I2 instanceof BigDecimal + assert I1 / I2 == new BigDecimal("0.5") + assert F1 / F2 instanceof Double + assertEquals F1 / F2, 0.5, 0.0000000001 + assert D1 / D2 instanceof Double + assertEquals D1 / D2, 0.5, 0.0000000001 + + assert I1.intdiv(I2) instanceof Integer + assert I1.intdiv(I2) == 0 + + assert I3.intdiv(I2) instanceof Integer + assert I3.intdiv(I2) == 1 + + assert L1.intdiv(I2) instanceof Long + assert L1.intdiv(I2) == 0 + + assert L3.intdiv(L2) instanceof Long + assert L3.intdiv(L2) == 1 + + assert BI1.intdiv(BI2) instanceof BigInteger + assert BI1.intdiv(BI2) == 0 + + assert I1 / I3 instanceof BigDecimal + assert I1 / I3 == new BigDecimal("0.3333333333") + + assert I2 / I3 instanceof BigDecimal + assert I2 / I3 == new BigDecimal("0.6666666667") + + assert I1 / BD2 instanceof BigDecimal + + //Test keeping max scale of (L, R or 10) + def BBD1 = new BigDecimal("0.12345678901234567") + assert BD1 + BBD1 == new BigDecimal("1.12345678901234567") + + def BBD2 = new BigDecimal(".000000000000000008") + assert BBD1 + BBD2 == new BigDecimal("0.123456789012345678") + } + + void testUnsupportedIntDivision() { + try { + 1.0.intdiv(3) + } catch (UnsupportedOperationException ignore) { + return + } + fail("Should catch an UnsupportedOperationException") + + try { + 1.0G.intdiv(3) + } catch (UnsupportedOperationException ignore) { + return + } + fail("Should catch an UnsupportedOperationException") + } + + + private static class MyNumber extends Number { + def n + + MyNumber(n) { + this.n = n + } + + int intValue() { n } + + long longValue() { n } + + float floatValue() { n } + + double doubleValue() { n } + + int hashCode() { -n } + + boolean equals(other) { + if (other instanceof MyNumber) { + return n == other.n + } + return false + } + + String toString() { return Double.toString(floatValue()) } + } + + void testGetMathCustom() { + assert getMath(1G) == BigIntegerMath.INSTANCE; + assert getMath(1.0G) == BigDecimalMath.INSTANCE; + assert getMath(Long.valueOf(1)) == LongMath.INSTANCE; + assert getMath(Integer.valueOf(1)) == IntegerMath.INSTANCE; + assert getMath(Short.valueOf("1")) == IntegerMath.INSTANCE; + assert getMath(Byte.valueOf("1")) == IntegerMath.INSTANCE; + assert getMath(Double.valueOf(1.0)) == FloatingPointMath.INSTANCE; + assert getMath(Float.valueOf(1.0f)) == FloatingPointMath.INSTANCE; + + MyNumber num = new MyNumber(42); + assert getMath(num) == BigDecimalMath.INSTANCE; + + assert getMath(num, Integer.valueOf(25)) == BigDecimalMath.INSTANCE; + assert getMath(num, Double.valueOf(25.0)) == FloatingPointMath.INSTANCE; + assert getMath(Integer.valueOf(25), num) == BigDecimalMath.INSTANCE; + assert getMath(Double.valueOf(25.0), num) == FloatingPointMath.INSTANCE; + assert getMath(num, 25) == BigDecimalMath.INSTANCE; + assert getMath(25, num) == BigDecimalMath.INSTANCE; + } + + void testGetMath() { + assert 20 == new Short("10") << 1 + assert 2 == new Byte("1") << 1 + } + + void testLongDivAssign() { + long d = 100L + d /= 33L + assert d.class == Long.class + } + + void testIntegerPlusCastException() { + shouldFail(ClassCastException) { + Integer i = 12 + i += " angry men" + } + } +}