diff --git a/common/src/test/org/apache/hadoop/hive/common/type/TestDecimal128.java b/common/src/test/org/apache/hadoop/hive/common/type/TestDecimal128.java index 3c05352..0d9ca0e 100644 --- a/common/src/test/org/apache/hadoop/hive/common/type/TestDecimal128.java +++ b/common/src/test/org/apache/hadoop/hive/common/type/TestDecimal128.java @@ -17,6 +17,8 @@ import static org.junit.Assert.*; +import java.util.Random; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -47,11 +49,38 @@ public void tearDown() throws Exception { @Test public void testCalculateTenThirtyEight() { + + // find 10^38 Decimal128 ten = new Decimal128(10, (short) 0); Decimal128 val = new Decimal128(1, (short) 0); for (int i = 0; i < 38; ++i) { val.multiplyDestructive(ten, (short) 0); } + + // verify it + String s = val.toFormalString(); + assertEquals("100000000000000000000000000000000000000", s); + boolean overflow = false; + + // show that it is is an overflow for precision 38 + try { + val.checkPrecisionOverflow(38); + } catch (Exception e) { + overflow = true; + } + assertTrue(overflow); + + // subtract one + val.subtractDestructive(one, (short) 0); + overflow = false; + + // show that it does not overflow for precision 38 + try { + val.checkPrecisionOverflow(38); + } catch (Exception e) { + overflow = true; + } + assertFalse(overflow); } @Test @@ -272,6 +301,54 @@ public void testDivide() { } @Test + public void testRandomMultiplyDivideInverse() { + final int N = 100000; + int seed = 897089790; + Random rand = new Random(seed); + long l1, l2; + for (int i = 1; i <= N; i++) { + l1 = rand.nextLong() & 0x00FFFFFFFFFFFFL; + l2 = rand.nextLong() & 0x00FFFFFFFFFFFFL; + verifyMultiplyDivideInverse(l1, l2); + } + } + + /** + * Verify that a * b / b == a + * for decimal division for scale 0 with integer inputs. + * + * Not valid if abs(a * b) >= 10**38. + */ + private void verifyMultiplyDivideInverse(long a, long b) { + final short scale = 0; + + // ignore zero-divide cases + if (b == 0) { + return; + } + Decimal128 decA = new Decimal128(a, scale); + Decimal128 decB = new Decimal128(b, scale); + Decimal128 remainder = new Decimal128(0, scale); + decA.multiplyDestructive(decB, scale); + decA.checkPrecisionOverflow(38); // caller must make sure product of inputs is not too big + decA.divideDestructive(decB, scale, remainder); + assertEquals("Error for a = " + Long.toString(a) + ", b = " + Long.toString(b), + new Decimal128(a, scale), decA); + } + + /** + * During earlier code testing, if we found errors, test them here as regression tests. + */ + @Test + public void testKnownPriorErrors() { + // java.lang.AssertionError: Error for a = 213474114411690, b = 5062120663 expected:<213474114411690(Decimal128: scale=0, signum=1, BigDecimal.toString=213474114411690, unscaledValue=[Int128: count=2,v[0]=1354898602(0x50c21caa), v[1]=49703(0xc227), v[2]=0(0x0), v[3]=0(0x0), BigInteger#toString=213474114411690])> but was:<251599050984618(Decimal128: scale=0, signum=1, BigDecimal.toString=251599050984618, unscaledValue=[Int128: count=2,v[0]=-133215062(0xf80f4caa), v[1]=58579(0xe4d3), v[2]=0(0x0), v[3]=0(0x0), BigInteger#toString=251599050984618])> + + long a = 213474114411690L; + long b = 5062120663L; + verifyMultiplyDivideInverse(a, b); + } + + @Test public void testPiNewton() { // see http://en.wikipedia.org/wiki/Approximations_of_%CF%80 @@ -410,5 +487,29 @@ public void testPrecisionOverflow() { fail(); } catch (ArithmeticException ex) { } + + // Try the extremes of precision and scale. + + // digit measuring stick: + // 12345678901234567890123456789012345678 + new Decimal128("0.99999999999999999999999999999999999999", (short) 38) + .checkPrecisionOverflow(38); + + try { + new Decimal128("0.99999999999999999999999999999999999999", (short) 38) + .checkPrecisionOverflow(37); + fail(); + } catch (ArithmeticException ex) { + } + + new Decimal128("99999999999999999999999999999999999999", (short) 0) + .checkPrecisionOverflow(38); + + try { + new Decimal128("99999999999999999999999999999999999999", (short) 0) + .checkPrecisionOverflow(37); + fail(); + } catch (ArithmeticException ex) { + } } }