/**
 * 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.apache.hadoop.hive.common.type;

import static org.junit.Assert.*;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Random;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;


/**
 * This code was based on code from Microsoft's PolyBase.
 */

public class TestDecimal128 {
  private Decimal128 zero;

  private Decimal128 one;

  private Decimal128 two;

  @Before
  public void setUp() throws Exception {
    zero = new Decimal128(0);
    one = new Decimal128(1);
    two = new Decimal128(2);
  }

  @After
  public void tearDown() throws Exception {
  }

  @Test
  public void testCalculateTenThirtySeven() {

    // find 10^37
    Decimal128 ten = new Decimal128(10, (short) 0);
    Decimal128 val = new Decimal128(1, (short) 0);
    for (int i = 0; i < 37; ++i) {
      val.multiplyDestructive(ten, (short) 0);
    }

    // verify it
    String s = val.toFormalString();
    assertEquals("10000000000000000000000000000000000000", s);
    boolean overflow = false;
  }

  @Test
  public void testHashCode() {
    assertTrue(one.hashCode() != two.hashCode());
    assertTrue(zero.hashCode() != one.hashCode());
    assertTrue(zero.hashCode() != two.hashCode());

    assertEquals(zero.hashCode(), new Decimal128(0).hashCode());
    assertEquals(one.hashCode(), new Decimal128(1).hashCode());
    assertEquals(two.hashCode(), new Decimal128(2).hashCode());

    // scaled value might be not equal, but after scaling it should.
    Decimal128 oneScaled = new Decimal128(1L, (short) 3);
    oneScaled.changeScaleDestructive((short) 0);
    assertEquals(one.hashCode(), oneScaled.hashCode());
  }

  @Test
  public void testEquals() {
    assertTrue(!one.equals(two));
    assertTrue(!zero.equals(one));
    assertTrue(!zero.equals(two));

    assertEquals(zero, new Decimal128(0));
    assertEquals(one, new Decimal128(1));
    assertEquals(two, new Decimal128(2));

    // scaled value might be not equal, but after scaling it should.
    Decimal128 oneScaled = new Decimal128(1L, (short) 3);
    oneScaled.changeScaleDestructive((short) 0);
    assertEquals(one, oneScaled);
  }

  @Test
  public void testCompareTo() {
    assertTrue(one.compareTo(two) < 0);
    assertTrue(two.compareTo(one) > 0);
    assertTrue(one.compareTo(zero) > 0);
    assertTrue(zero.compareTo(two) < 0);

    // compare to must compare with scaling up/down.
    Decimal128 oneScaled = new Decimal128(1L, (short) 3);
    assertTrue(one.compareTo(oneScaled) == 0);

    // exact numbers (power of 2) can do the same
    Decimal128 d1 = new Decimal128(2.0d, (short) 6);
    Decimal128 d2 = new Decimal128(2.0d, (short) 3);
    assertTrue(d1.compareTo(d2) == 0);

    // but, if the value is rounded by more scaling,
    // they will be different values.
    Decimal128 d3 = new Decimal128(2.0d / 3.0d, (short) 5);
    Decimal128 d4 = new Decimal128(2.0d / 3.0d, (short) 8);
    assertTrue(d3.compareTo(d4) != 0);

    Decimal128 d5 = new Decimal128(12, (short) 5);
    Decimal128 d6 = new Decimal128(15, (short) 7);
    assertTrue(d5.compareTo(d6) < 0);
    assertTrue(d6.compareTo(d5) > 0);

    Decimal128 d7 = new Decimal128(15, (short) 5);
    Decimal128 d8 = new Decimal128(12, (short) 7);
    assertTrue(d7.compareTo(d8) > 0);
    assertTrue(d8.compareTo(d7) < 0);
  }

  @Test
  public void testText() {
    assertEquals("1", one.toFormalString());
    assertEquals(0, new Decimal128("1", (short) 0).compareTo(one));
    assertEquals("2", two.toFormalString());
    assertEquals(0, new Decimal128("2", (short) 0).compareTo(two));
    assertEquals("0", zero.toFormalString());
    assertEquals(0, new Decimal128("0", (short) 0).compareTo(zero));

    assertEquals("1.000", new Decimal128(1L, (short) 3).toFormalString());
    assertEquals(0, new Decimal128("1", (short) 3).compareTo(one));

    assertEquals("2.000000", new Decimal128(2.0d, (short) 6).toFormalString());
    assertEquals("2.000", new Decimal128(2.0d, (short) 3).toFormalString());
    assertEquals(0, new Decimal128("2.0", (short) 6).compareTo(two));
    assertEquals(0, new Decimal128("2.0", (short) 3).compareTo(two));

    assertEquals("1.3330", new Decimal128("1.333", (short) 4).toFormalString());
    assertEquals("1.333000",
        new Decimal128("1.333", (short) 6).toFormalString());
    assertEquals("1.333", new Decimal128("1.333", (short) 3).toFormalString());
    assertEquals("1.33", new Decimal128("1.333", (short) 2).toFormalString());
    assertEquals("1.33", new Decimal128("1.333", (short) 2).toFormalString());

    assertEquals("0.13330",
        new Decimal128("1333E-4", (short) 5).toFormalString());
    assertEquals("0.01333",
        new Decimal128("1333E-5", (short) 5).toFormalString());
    assertEquals("13330000.00",
        new Decimal128("1333E4", (short) 2).toFormalString());

    assertEquals("123456789012345678901234.56789", new Decimal128(
        "123456789012345678901234567.8901234E-3", (short) 5).toFormalString());
  }

  @Test
  public void testAdd() {
    Decimal128 result = new Decimal128();
    Decimal128.add(one, two, result, (short) 2);

    assertEquals(0, new Decimal128(3L, (short) 0).compareTo(result));

    Decimal128.add(two, two, result, (short) 1);

    assertEquals(0, new Decimal128(4L, (short) 0).compareTo(result));

    long l1 = 123456789012345L;
    long l2 = 987654321097L;
    long sum = l1 + l2;
    Decimal128 left = new Decimal128(l1, (short) 3);
    Decimal128 right = new Decimal128(l2, (short) 5);
    Decimal128.add(left, right, result, (short) 2);
    assertEquals(0, new Decimal128(sum, (short) 0).compareTo(result));
    Decimal128.add(right, left, result, (short) 2);
    assertEquals(0, new Decimal128(sum, (short) 0).compareTo(result));
  }

  @Test
  public void testSubtract() {
    Decimal128 result = new Decimal128();
    Decimal128.subtract(one, two, result, (short) 2);
    assertEquals(0, new Decimal128(-1L, (short) 0).compareTo(result));

    Decimal128.subtract(two, one, result, (short) 2);
    assertEquals(0, new Decimal128(1L, (short) 0).compareTo(result));

    Decimal128.subtract(two, two, result, (short) 1);
    assertEquals(0, zero.compareTo(result));
    assertEquals(0, result.getSignum());

    long l1 = 123456789012345L;
    long l2 = 987654321097L;
    long sub = l1 - l2;
    Decimal128 left = new Decimal128(l1, (short) 3);
    Decimal128 right = new Decimal128(l2, (short) 5);
    Decimal128.subtract(left, right, result, (short) 2);
    assertEquals(0, new Decimal128(sub, (short) 0).compareTo(result));
    Decimal128.subtract(right, left, result, (short) 2);
    assertEquals(0, new Decimal128(-sub, (short) 0).compareTo(result));

    Decimal128 val = new Decimal128("1.123", (short) 3);
    val.addDestructive(new Decimal128("4.321", (short) 3), (short) 3);
    assertEquals("5.444", val.toFormalString());
  }

  @Test
  public void testMultiply() {
    Decimal128 result = new Decimal128();
    Decimal128.multiply(one, two, result, (short) 2);
    assertEquals(0, two.compareTo(result));

    Decimal128.multiply(two, two, result, (short) 2);
    assertEquals(0, new Decimal128(4L, (short) 0).compareTo(result));

    long l1 = 123456789012345L;
    long l2 = 987654321097L;
    Decimal128 left = new Decimal128(l1, (short) 0);
    Decimal128 right = new Decimal128(l2, (short) 0);
    UnsignedInt128 unscaled = new UnsignedInt128(l1)
        .multiplyConstructive(new UnsignedInt128(l2));
    Decimal128 ans = new Decimal128(unscaled, (short) 0, false);
    Decimal128.multiply(left, right, result, (short) 0);
    assertEquals(0, ans.compareTo(result));
    Decimal128.multiply(right, left, result, (short) 0);
    assertEquals(0, ans.compareTo(result));

    Decimal128.multiply(new Decimal128(1.123d, (short) 10), new Decimal128(
        4.321d, (short) 10), result, (short) 10);
    assertEquals(1.123d * 4.321d, result.doubleValue(), 0.00001d);

    // because only 10 fractional digits, it's not this much accurate
    assertNotEquals(1.123d * 4.321d, result.doubleValue(), 0.00000000000000001d);

    Decimal128.multiply(new Decimal128(1.123d, (short) 2), new Decimal128(
        4.321d, (short) 2), result, (short) 2);

    // this time even more inaccurate
    assertEquals(1.123d * 4.321d, result.doubleValue(), 1.0d);
    assertNotEquals(1.123d * 4.321d, result.doubleValue(), 0.000001d);

    Decimal128 val = new Decimal128("1.123", (short) 3);
    val.multiplyDestructive(new Decimal128("4.321", (short) 3), (short) 6);
    assertEquals("4.852483", val.toFormalString());

    Decimal128 val1 = new Decimal128("1.0001", (short) 4);
    val1.multiplyDestructive(new Decimal128("1.0001", (short) 4), (short) 8);
    assertEquals("1.00020001", val1.toFormalString());

  }

  // Assert that a and b are not the same, within epsilon tolerance.
  private void assertNotEquals(double a, double b, double epsilon) {
    assertTrue(Math.abs(a - b) > epsilon);
  }

  @Test
  public void testDivide() {
    Decimal128 quotient = new Decimal128();
    Decimal128.divide(two, one, quotient, (short) 2);
    assertEquals(0, quotient.compareTo(two));

    Decimal128.divide(two, two, quotient, (short) 2);
    assertEquals(0, quotient.compareTo(one));

    Decimal128 three = new Decimal128(3);
    Decimal128 four = new Decimal128(4);
    Decimal128.divide(three, four, quotient, (short) 2);
    assertEquals("0.75", quotient.toFormalString());

    Decimal128.divide(three, four, quotient, (short) 1);
    assertEquals("0.8", quotient.toFormalString());

    Decimal128.divide(three, four, quotient, (short) 0);
    assertEquals("1", quotient.toFormalString());

    Decimal128 two = new Decimal128(2);
    Decimal128.divide(two, three, quotient, (short) 4);
    assertEquals("0.6667", quotient.toFormalString());
  }

  @Test
  public void testRandomMultiplyDivideInverse() {
    final int N = 100000;
    final long MASK56 = 0x00FFFFFFFFFFFFL; // 56 bit mask to generate positive 56 bit longs
                                           // from random signed longs
    int seed = 897089790;
    Random rand = new Random(seed);
    long l1, l2;
    for (int i = 1; i <= N; i++) {
      l1 = rand.nextLong() & MASK56;
      l2 = rand.nextLong() & MASK56;
      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);
    decA.multiplyDestructive(decB, scale);
    decA.checkPrecisionOverflow(38); // caller must make sure product of inputs is not too big
    decA.divideDestructive(decB, scale);
    assertEquals("Error for a = " + Long.toString(a) + ", b = " + Long.toString(b),
        new Decimal128(a, scale), decA);
  }


  @Test
  public void testRandomAddSubtractInverse() {
    final int N = 1000000;
    int seed = 1427480960;
    Random rand = new Random(seed);
    long l1, l2;
    for (int i = 1; i <= N; i++) {
      l1 = rand.nextLong();
      l2 = rand.nextLong();
      verifyAddSubtractInverse(l1, l2);
    }
  }

  /**
   * Verify that (a + b) - b == a
   * for decimal add and subtract for scale 0 with long integer inputs.
   */
  private void verifyAddSubtractInverse(long a, long b) {
    final short scale = 0;
    Decimal128 decA = new Decimal128(a, scale);
    Decimal128 decB = new Decimal128(b, scale);
    decA.addDestructive(decB, scale);

    decA.subtractDestructive(decB, scale);
    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() {

    // Regression test for defect reported in HIVE-6243
    long a = 213474114411690L;
    long b = 5062120663L;
    verifyMultiplyDivideInverse(a, b);

    // Regression test for defect reported in HIVE-6399
    String a2 = "-605044214913338382";  // 18 digits
    String b2 = "55269579109718297360"; // 20 digits

    // -33440539101030154945490585226577271520 is expected result
    verifyHighPrecisionMultiplySingle(a2, b2);
  }

  // Test a set of random adds at high precision.
  @Test
  public void testHighPrecisionDecimal128Add() {
    final int N = 10000;
    for (int i = 0; i < N; i++) {
      verifyHighPrecisionAddSingle();
    }
  }

  // Test one random hi-precision decimal add.
  private void verifyHighPrecisionAddSingle() {

    Decimal128 a, b, r;
    String sA, sB;

    a = new Decimal128();
    sA = makeNumericString(37);
    a.update(sA, (short) 0);
    b = new Decimal128();
    sB = makeNumericString(37);
    b.update(sB, (short) 0);

    r = new Decimal128();
    r.addDestructive(a, (short) 0);
    r.addDestructive(b, (short) 0);

    String res1 = r.toFormalString();

    // Now do the add with Java BigDecimal
    BigDecimal bdA = new BigDecimal(sA);
    BigDecimal bdB = new BigDecimal(sB);
    BigDecimal bdR = bdA.add(bdB);

    String res2 = bdR.toPlainString();

    // Compare the results
    String message = "For operation " + a.toFormalString() + " + " + b.toFormalString();
    assertEquals(message, res2, res1);
  }

  // Test a set of random subtracts at high precision.
  @Test
  public void testHighPrecisionDecimal128Subtract() {
    final int N = 10000;
    for (int i = 0; i < N; i++) {
      verifyHighPrecisionSubtractSingle();
    }
  }

  // Test one random high-precision subtract.
  private void verifyHighPrecisionSubtractSingle() {

    Decimal128 a, b, r;
    String sA, sB;

    a = new Decimal128();
    sA = makeNumericString(37);
    a.update(sA, (short) 0);
    b = new Decimal128();
    sB = makeNumericString(37);
    b.update(sB, (short) 0);

    r = new Decimal128();
    r.addDestructive(a, (short) 0);
    r.subtractDestructive(b, (short) 0);

    String res1 = r.toFormalString();

    // Now do the add with Java BigDecimal
    BigDecimal bdA = new BigDecimal(sA);
    BigDecimal bdB = new BigDecimal(sB);
    BigDecimal bdR = bdA.subtract(bdB);

    String res2 = bdR.toPlainString();

    // Compare the results
    String message = "For operation " + a.toFormalString() + " - " + b.toFormalString();
    assertEquals(message, res2, res1);
  }

  // Test a set of random multiplications at high precision.
  @Test
  public void testHighPrecisionDecimal128Multiply() {
    final int N = 10000;
    for (int i = 0; i < N; i++) {
      verifyHighPrecisionMultiplySingle();
    }
  }

  // Test a single, high-precision multiply of random inputs.
  private void verifyHighPrecisionMultiplySingle() {

    Decimal128 a, b, r;
    String sA, sB;

    Random rand = new Random();
    int aDigits = rand.nextInt(37) + 1; // number of digits in a (1..37)
    int bDigits = 38 - aDigits;         // number of digits in b (1..37)
    assertTrue(aDigits + bDigits == 38 && aDigits > 0 && bDigits > 0);

    a = new Decimal128();
    sA = makeNumericString(aDigits);
    a.update(sA, (short) 0);
    b = new Decimal128();
    sB = makeNumericString(bDigits);
    b.update(sB, (short) 0);

    r = new Decimal128();
    r.addDestructive(a, (short) 0);
    r.multiplyDestructive(b, (short) 0);

    String res1 = r.toFormalString();

    // Now do the operation with Java BigDecimal
    BigDecimal bdA = new BigDecimal(sA);
    BigDecimal bdB = new BigDecimal(sB);
    BigDecimal bdR = bdA.multiply(bdB);

    String res2 = bdR.toPlainString();

    // Compare the results
    String message = "For operation " + a.toFormalString() + " * " + b.toFormalString();
    assertEquals(message, res2, res1);
  }

  // Test a single, high-precision multiply of random inputs.
  // Arguments must be integers with optional - sign, represented as strings.
  // Arguments must have 1 to 37 digits and the number of total digits
  // must be <= 38.
  private void verifyHighPrecisionMultiplySingle(String argA, String argB) {

    Decimal128 a, b, r;
    String sA, sB;

    // verify number of digits is <= 38 and each number has 1 or more digits
    int aDigits = argA.length();
    aDigits -= argA.charAt(0) == '-' ? 1 : 0;
    int bDigits = argB.length();
    bDigits -= argB.charAt(0) == '-' ? 1 : 0;
    assertTrue(aDigits + bDigits <= 38 && aDigits > 0 && bDigits > 0);

    a = new Decimal128();
    sA = argA;
    a.update(sA, (short) 0);
    b = new Decimal128();
    sB = argB;
    b.update(sB, (short) 0);

    r = new Decimal128();
    r.addDestructive(a, (short) 0);
    r.multiplyDestructive(b, (short) 0);

    String res1 = r.toFormalString();

    // Now do the operation with Java BigDecimal
    BigDecimal bdA = new BigDecimal(sA);
    BigDecimal bdB = new BigDecimal(sB);
    BigDecimal bdR = bdA.multiply(bdB);

    String res2 = bdR.toPlainString();

    // Compare the results
    String message = "For operation " + a.toFormalString() + " * " + b.toFormalString();
    assertEquals(message, res2, res1);
  }


  // Test a set of random divisions at high precision.
  @Test
  public void testHighPrecisionDecimal128Divide() {
    final int N = 10000;
    for (int i = 0; i < N; i++) {
      verifyHighPrecisionDivideSingle();
    }
  }

  // Test a single, high-precision divide of random inputs.
  private void verifyHighPrecisionDivideSingle() {

    Decimal128 a, b, r;
    String sA, sB;

    Random rand = new Random();
    int aDigits = rand.nextInt(37) + 1; // number of digits in a (1..37)
    int bDigits = 38 - aDigits;         // number of digits in b (1..37)
    int temp;

    // make sure b will have less digits than A
    if (bDigits > aDigits) {
      temp = aDigits;
      aDigits = bDigits;
      bDigits = temp;
    }
    if (bDigits == aDigits) {
      return;
    }
    assertTrue(aDigits + bDigits == 38 && aDigits > 0 && bDigits > 0);

    a = new Decimal128();
    sA = makeNumericString(aDigits);
    a.update(sA, (short) 0);
    b = new Decimal128();
    sB = makeNumericString(bDigits);
    b.update(sB, (short) 0);
    if (b.isZero()) {

      // don't do zero-divide if one comes up at random
      return;
    }

    r = new Decimal128();
    r.addDestructive(a, (short) 0);
    r.divideDestructive(b, (short) 0);

    String res1 = r.toFormalString();

    // Now do the operation with Java BigDecimal
    BigDecimal bdA = new BigDecimal(sA);
    BigDecimal bdB = new BigDecimal(sB);
    BigDecimal bdR = bdA.divide(bdB, 0, RoundingMode.HALF_UP);

    String res2 = bdR.toPlainString();

    // Compare the results
    String message = "For operation " + a.toFormalString() + " / " + b.toFormalString();
    assertEquals(message, res2, res1);
  }

  /* Return a random number with length digits, as a string. Results may be
   * negative or positive.
   */
  private String makeNumericString(int length) {
    Random r = new Random();
    StringBuilder b = new StringBuilder();
    for(int i = 0; i < length; i++) {
      b.append(r.nextInt(10));
    }

    // choose a random sign
    String sign = r.nextInt(2) == 0 ? "-" : "";
    return sign + b.toString();
  }

  @Test
  public void testPiNewton() {

    // see http://en.wikipedia.org/wiki/Approximations_of_%CF%80
    // Below is the simple Newton's equation
    final int LOOPS = 100;
    final short SCALE = 33;
    Decimal128 current = new Decimal128(1, SCALE);
    Decimal128 multiplier = new Decimal128();
    Decimal128 dividor = new Decimal128();
    Decimal128 one = new Decimal128(1);
    for (int i = LOOPS; i > 0; --i) {
      multiplier.update(i, SCALE);
      current.multiplyDestructive(multiplier, SCALE);
      dividor.update(1 + 2 * i, SCALE);
      current.divideDestructive(dividor, SCALE);
      current.addDestructive(one, SCALE);
    }
    current.multiplyDestructive(new Decimal128(2), SCALE);
    assertTrue(current.toFormalString().startsWith("3.141592653589793238"));
  }

  @Test
  public void testPiArcsine() {

    // This one uses the arcsin method. Involves more multiplications/divisions.
    // pi=Sum (3 * 2n!/(16^n * (2n+1) * n! * n!))
    // =Sum (3 * ((n+1)(n+2)...2n)/n!*16^n/(2n+1))
    // =Sum (3 / (2n+1) * (n+1)/16 * (n+2)/32... * 2n/16(n+1))
    // (note that it is split so that each term is not overflown)
    final int LOOPS = 50;
    final short SCALE = 30;
    Decimal128 total = new Decimal128(0);
    Decimal128 multiplier = new Decimal128();
    Decimal128 dividor = new Decimal128();
    Decimal128 current = new Decimal128();
    for (int i = 0; i < LOOPS; ++i) {
      current.update(3, SCALE);
      dividor.update(2 * i + 1, SCALE);
      current.divideDestructive(dividor, SCALE);
      for (int j = 1; j <= i; ++j) {
        multiplier.update(i + j, SCALE);
        dividor.update(16 * j, SCALE);
        current.multiplyDestructive(multiplier, SCALE);
        current.divideDestructive(dividor, SCALE);
      }

      total.addDestructive(current, SCALE);
    }

    assertTrue(total.toFormalString().startsWith("3.141592653589793238462"));
  }

  @Test
  public void testDoubleValue() {
    Decimal128 quotient = new Decimal128();

    Decimal128 three = new Decimal128(3);
    Decimal128 four = new Decimal128(9);
    Decimal128.divide(three, four, quotient, (short) 38);
    assertEquals(0.33333333333333333333333333d, quotient.doubleValue(),
        0.0000000000000000000000001d);

    Decimal128 minusThree = new Decimal128(-3);
    Decimal128.divide(minusThree, four, quotient, (short) 38);
    assertEquals(-0.33333333333333333333333333d, quotient.doubleValue(),
        0.0000000000000000000000001d);
  }

  @Test
  public void testFloatValue() {
    Decimal128 quotient = new Decimal128();

    Decimal128 three = new Decimal128(3);
    Decimal128 four = new Decimal128(9);
    Decimal128.divide(three, four, quotient, (short) 38);
    assertEquals(0.3333333333333333f, quotient.floatValue(), 0.00000000001f);

    Decimal128 minusThree = new Decimal128(-3);
    Decimal128.divide(minusThree, four, quotient, (short) 38);
    assertEquals(-0.333333333333333f, quotient.floatValue(), 0.00000000001f);
  }

  @Test
  public void testSqrtAsDouble() {
    Decimal128 val1 = new Decimal128("1.00435134913958923485982394892384",
        (short) 36);
    Decimal128 val2 = new Decimal128("1.00345982739817298323423423", (short) 36);
    assertEquals(1.00217331292526d, val1.sqrtAsDouble(), 0.000000000000001d);
    assertEquals(1.00172841998127d, val2.sqrtAsDouble(), 0.000000000000001d);

  }

  @Test
  public void testPowAsDouble() {
    Decimal128 val1 = new Decimal128("1.00435134913958923485982394892384",
        (short) 36);
    assertEquals(1.004366436877081d,
        val1.powAsDouble(1.00345982739817298323423423d), 0.000000000000001d);

    Decimal128 val2 = new Decimal128("1.001", (short) 36);
    assertEquals(1.0100451202102512d, val2.powAsDouble(10), 0.000000000000001d);
  }

  @Test
  public void testPrecisionOverflow() {
    new Decimal128("1.004", (short) 3).checkPrecisionOverflow(4);

    try {
      new Decimal128("1.004", (short) 3).checkPrecisionOverflow(3);
      fail();
    } catch (ArithmeticException ex) {
    }

    try {
      new Decimal128("1.004", (short) 3).checkPrecisionOverflow(2);
      fail();
    } catch (ArithmeticException ex) {
    }

    new Decimal128("1.004", (short) 3).checkPrecisionOverflow(38);

    new Decimal128("-3322", (short) 0).checkPrecisionOverflow(4);
    try {
      new Decimal128("-3322", (short) 0).checkPrecisionOverflow(3);
      fail();
    } catch (ArithmeticException ex) {
    }

    new Decimal128("-3322", (short) 1).checkPrecisionOverflow(5);
    try {
      new Decimal128("-3322", (short) 1).checkPrecisionOverflow(4);
      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) {
    }
  }

  @Test
  public void testToLong() {
    Decimal128 d = new Decimal128("1.25", (short) 2);
    assertEquals(1, d.longValue());
    d.update("4294967295", (short) 0); // 2^32-1
    assertEquals(4294967295L, d.longValue());
    d.update("4294967296", (short) 0); // 2^32 -- needs 2 32 bit words
    assertEquals(4294967296L, d.longValue());
    d.update("-4294967295", (short) 0); // -(2^32-1)
    assertEquals(-4294967295L, d.longValue());
    d.update("-4294967296", (short) 0); // -(2^32)
    assertEquals(-4294967296L, d.longValue());
    d.update("4294967295.01", (short) 2); // 2^32-1 + .01
    assertEquals(4294967295L, d.longValue());
    d.update("4294967296.01", (short) 2); // 2^32 + .01
    assertEquals(4294967296L, d.longValue());

    // Compare long value with HiveDecimal#longValue
    d.update(37.678, (short)5);
    HiveDecimal hd = HiveDecimal.create(BigDecimal.valueOf(37.678));
    assertEquals(hd.longValue(), d.longValue());
  }

  @Test
  public void testToHiveDecimalString() {
    Decimal128 d1 = new Decimal128("4134.923076923077", (short) 15);
    assertEquals("4134.923076923077", d1.getHiveDecimalString());

    Decimal128 d2 = new Decimal128("0.00923076923", (short) 15);
    assertEquals("0.00923076923", d2.getHiveDecimalString());

    Decimal128 d3 = new Decimal128("0.00923076000", (short) 15);
    assertEquals("0.00923076", d3.getHiveDecimalString());

    Decimal128 d4 = new Decimal128("4294967296.01", (short) 15);
    assertEquals("4294967296.01", d4.getHiveDecimalString());

    Decimal128 d5 = new Decimal128("4294967296.01", (short) 2);
    assertEquals("4294967296.01", d5.getHiveDecimalString());

    Decimal128 d6 = new Decimal128();
    HiveDecimal hd1 = HiveDecimal.create(new BigInteger("42949672"));
    d6.update(hd1.bigDecimalValue());
    assertEquals(hd1.toString(), d6.getHiveDecimalString());

    Decimal128 d7 = new Decimal128();
    HiveDecimal hd2 = HiveDecimal.create(new BigDecimal("0.0"));
    d7.update(hd2.bigDecimalValue());
    assertEquals(hd2.toString(), d7.getHiveDecimalString());

    Decimal128 d8 = new Decimal128();
    HiveDecimal hd3 = HiveDecimal.create(new BigDecimal("0.00023000"));
    d8.update(hd3.bigDecimalValue());
    assertEquals(hd3.toString(), d8.getHiveDecimalString());

    Decimal128 d9 = new Decimal128();
    HiveDecimal hd4 = HiveDecimal.create(new BigDecimal("0.1"));
    d9.update(hd4.bigDecimalValue());
    assertEquals(hd4.toString(), d9.getHiveDecimalString());

    Decimal128 d10 = new Decimal128();
    HiveDecimal hd5 = HiveDecimal.create(new BigDecimal("-00.100"));
    d10.update(hd5.bigDecimalValue());
    assertEquals(hd5.toString(), d10.getHiveDecimalString());

    Decimal128 d11 = new Decimal128();
    HiveDecimal hd6 = HiveDecimal.create(new BigDecimal("00.1"));
    d11.update(hd6.bigDecimalValue());
    assertEquals(hd6.toString(), d11.getHiveDecimalString());

    Decimal128 d12 = new Decimal128(27.000, (short)3);
    HiveDecimal hd7 = HiveDecimal.create(new BigDecimal("27.000"));
    assertEquals(hd7.toString(), d12.getHiveDecimalString());
    assertEquals("27", d12.getHiveDecimalString());

    Decimal128 d13 = new Decimal128(1234123000, (short)3);
    HiveDecimal hd8 = HiveDecimal.create(new BigDecimal("1234123000"));
    assertEquals(hd8.toString(), d13.getHiveDecimalString());
    assertEquals("1234123000", d13.getHiveDecimalString());
  }

  @Test
  public void testUpdateWithScale() {
    Decimal128 d1 = new Decimal128(1234.123, (short)4);
    Decimal128 d2 = new Decimal128(0, (short)3);
    d2.update(d1, (short)3);
    assertEquals(0, d1.compareTo(d2));
  }
}
