/**
 * 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 java.io.Serializable;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;

import org.apache.hive.common.util.Decimal128FastBuffer;

/**
 * This code was based on code from Microsoft's PolyBase.
 *
 * Represents an unsigned 128-bit integer. This is the basis for
 * {@link Decimal128} and {@link SignedInt128}. This object is much faster and
 * more compact than BigInteger, but has many limitations below.
 * <ul>
 * <li>Always consumes 128 bits (4 int32) even if the values is, say, 3.</li>
 * <li>Cannot handle values larger than 10**38.</li>
 * <li>Does not support some of arithmetic operations that is not required in
 * SQL (e.g., exact POWER/SQRT).</li>
 * </ul>
 */
public final class UnsignedInt128 implements Comparable<UnsignedInt128>, Serializable {

  /** Number of ints to store this object. */
  public static final int INT_COUNT = 4;

  /** Number of bytes to store this object. */
  public static final int BYTE_SIZE = 4 * INT_COUNT;

  /** Can hold up to 10^38. */
  public static final int MAX_DIGITS = 38;

  /** Maximum value that can be represented in this class. */
  public static final UnsignedInt128 MAX_VALUE = new UnsignedInt128(0xFFFFFFFF,
      0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF);

  /** Minimum value that can be represented in this class. */
  public static final UnsignedInt128 MIN_VALUE = new UnsignedInt128(0);

  /** A special value representing 10**38. */
  public static final UnsignedInt128 TEN_TO_THIRTYEIGHT = new UnsignedInt128(0,
      0x98a2240, 0x5a86c47a, 0x4b3b4ca8);

  /**
   * Int32 elements as little-endian (v[0] is least significant) unsigned
   * integers.
   */
  private int[] v = new int[INT_COUNT];

  /**
   * Number of leading non-zero elements in {@link #v}. For example, if the
   * value of this object is 123 (v0=123, v1=v2=v3=0), then 1. 0 to 4. 0 means
   * that this object represents zero.
   *
   * @see #updateCount()
   */
  private byte count;

  /**
   * Determines the number of ints to store one value.
   *
   * @param precision
   *          precision (0-38)
   * @return the number of ints to store one value
   */
  public static int getIntsPerElement(int precision) {
    assert (precision >= 0 && precision <= 38);
    if (precision <= 9) {
      return 1;
    } else if (precision <= 19) {
      return 2;
    } else if (precision <= 28) {
      return 3;
    }
    return 4;
  }

  /** Creates an instance that represents zero. */
  public UnsignedInt128() {
    zeroClear();
  }

  /**
   * Copy constructor.
   *
   * @param o
   *          The instance to copy from
   */
  public UnsignedInt128(UnsignedInt128 o) {
    update(o);
  }

  /**
   * Creates an instance that has the given values.
   *
   * @param v0
   *          v0
   * @param v1
   *          v1
   * @param v2
   *          v2
   * @param v3
   *          v3
   */
  public UnsignedInt128(int v0, int v1, int v2, int v3) {
    update(v0, v1, v2, v3);
  }

  /**
   * Constructs from the given long value.
   *
   * @param v
   *          long value
   */
  public UnsignedInt128(long v) {
    update(v);
  }

  /**
   * Constructs from the given string.
   *
   * @param str
   *          string
   */
  public UnsignedInt128(String str) {
    update(str);
  }

  /**
   * Constructs from the given string with given offset and length.
   *
   * @param str
   *          string
   * @param offset
   *          offset
   * @param length
   *          length
   */
  public UnsignedInt128(char[] str, int offset, int length) {
    update(str, offset, length);
  }

  /**
   * Constructs from the given BigInteger
   *
   * @param bigInt
   *          java BigInteger
   */
  public UnsignedInt128(BigInteger bigInt) {
    update(bigInt);
  }

  /**
   * Updates the value of this object from the given {@link BigInteger}.
   * Only positive BigIntegers are expected and behavior is undefined for
   * negative BigIntegers.
   *
   * @param bigInt
   *          java BigInteger
   */
  public void update(BigInteger bigInt) {
    int v0 = bigInt.intValue();
    int v1 = bigInt.shiftRight(32).intValue();
    int v2 = bigInt.shiftRight(64).intValue();
    int v3 = bigInt.shiftRight(96).intValue();
    update(v0, v1, v2, v3);
  }

  /** @return v[0] */
  public int getV0() {
    return v[0];
  }

  /** @return v[1] */
  public int getV1() {
    return v[1];
  }

  /** @return v[2] */
  public int getV2() {
    return v[2];
  }

  /** @return v[3] */
  public int getV3() {
    return v[3];
  }

  /**
   * Setter for v0.
   *
   * @param val
   *          value to set
   */
  public void setV0(int val) {
    v[0] = val;
    updateCount();
  }

  /**
   * Setter for v1.
   *
   * @param val
   *          value to set
   */
  public void setV1(int val) {
    v[1] = val;
    updateCount();
  }

  /**
   * Setter for v2.
   *
   * @param val
   *          value to set
   */
  public void setV2(int val) {
    v[2] = val;
    updateCount();
  }

  /**
   * Setter for v3.
   *
   * @param val
   *          value to set
   */
  public void setV3(int val) {
    v[3] = val;
    updateCount();
  }

  /**
   * Returns if we overflowed 10**38, but not 2**128. This code is equivalent to
   * CSsNumeric::FGt10_38 in SQLServer (numeric.cpp). However, be aware that the
   * elements are signed ints, not UI4 in SQLServer.
   *
   * @return whether this value is equal to or larger than 10**38
   */
  public boolean exceedsTenToThirtyEight() {

    // 10**38=
    // v[0]=0(0),v[1]=160047680(98a2240),v[2]=1518781562(5a86c47a),v[3]=1262177448(4b3b4ca8)

    // check most significant part first
    if (v[3] != 0x4b3b4ca8) {
      return (v[3] < 0 || v[3] > 0x4b3b4ca8);
    }

    // check second most significant part
    if (v[2] != 0x5a86c47a) {
      return (v[2] < 0 || v[2] > 0x5a86c47a);
    }

    return (v[1] < 0 || v[1] > 0x098a2240);
  }

  /**
   * Used to check overflows. This is NOT used in {@link UnsignedInt128} itself
   * because this overflow semantics is Decimal's. (throws - but not a checked
   * exception) ArithmeticException if this value is equal to or exceed 10**38.
   */
  public void throwIfExceedsTenToThirtyEight() {
    if (exceedsTenToThirtyEight()) {
      SqlMathUtil.throwOverflowException();
    }
  }

  /**
   * Returns the value of this object as long, throwing error if the value
   * exceeds long.
   *
   * @return the value this object represents
   */
  public long asLong() {
    if (this.count > 2 || v[1] < 0) {
      SqlMathUtil.throwOverflowException();
    }
    return (((long) v[1]) << 32L) | v[0];
  }

  /** Make the value to zero. */
  public void zeroClear() {
    this.v[0] = 0;
    this.v[1] = 0;
    this.v[2] = 0;
    this.v[3] = 0;
    this.count = 0;
  }

  /** @return whether the value is zero */
  public boolean isZero() {
    return this.count == 0;
  }

  /** @return whether the value is one */
  public boolean isOne() {
    return this.v[0] == 1 && this.count == 1;
  }

  /** @return whether 32bits int is enough to represent this value */
  public boolean fitsInt32() {
    return this.count <= 1;
  }

  /**
   * Copy from the given object.
   *
   * @param o
   *          The instance to copy from
   */
  public void update(UnsignedInt128 o) {
    update(o.v[0], o.v[1], o.v[2], o.v[3]);
  }

  /**
   * Updates the value of this object with the given long value.
   *
   * @param v
   *          long value
   */
  public void update(long v) {
    assert (v >= 0);
    update((int) v, (int) (v >> 32), 0, 0);
  }

  /**
   * Updates the value of this object with the given values.
   *
   * @param v0
   *          v0
   * @param v1
   *          v1
   * @param v2
   *          v2
   * @param v3
   *          v3
   */
  public void update(int v0, int v1, int v2, int v3) {
    this.v[0] = v0;
    this.v[1] = v1;
    this.v[2] = v2;
    this.v[3] = v3;
    updateCount();
  }

  /**
   * Updates the value of this object by reading from ByteBuffer, using the
   * required number of ints for the given precision.
   *
   * @param buf
   *          ByteBuffer to read values from
   * @param precision
   *          0 to 38. Decimal digits.
   */
  public void update(IntBuffer buf, int precision) {
    switch (getIntsPerElement(precision)) {
    case 1:
      update32(buf);
      break;
    case 2:
      update64(buf);
      break;
    case 3:
      update96(buf);
      break;
    case 4:
      update128(buf);
      break;
    default:
      throw new RuntimeException();
    }
  }

  /**
   * Updates the value of this object by reading from ByteBuffer, receiving 128
   * bits data (full ranges).
   *
   * @param buf
   *          ByteBuffer to read values from
   */
  public void update128(IntBuffer buf) {
    buf.get(v, 0, INT_COUNT);
    updateCount();
  }

  /**
   * Updates the value of this object by reading from ByteBuffer, receiving only
   * 96 bits data.
   *
   * @param buf
   *          ByteBuffer to read values from
   */
  public void update96(IntBuffer buf) {
    buf.get(v, 0, 3);
    v[3] = 0;
    updateCount();
  }

  /**
   * Updates the value of this object by reading from ByteBuffer, receiving only
   * 64 bits data.
   *
   * @param buf
   *          ByteBuffer to read values from
   */
  public void update64(IntBuffer buf) {
    buf.get(v, 0, 2);
    v[2] = 0;
    v[3] = 0;
    updateCount();
  }

  /**
   * Updates the value of this object by reading from ByteBuffer, receiving only
   * 32 bits data.
   *
   * @param buf
   *          ByteBuffer to read values from
   */
  public void update32(IntBuffer buf) {
    v[0] = buf.get();
    v[1] = 0;
    v[2] = 0;
    v[3] = 0;
    updateCount();
  }

  /**
   * Updates the value of this object by reading from the given array, using the
   * required number of ints for the given precision.
   *
   * @param array
   *          array to read values from
   * @param offset
   *          offset of the long array
   * @param precision
   *          0 to 38. Decimal digits.
   */
  public void update(int[] array, int offset, int precision) {
    switch (getIntsPerElement(precision)) {
    case 1:
      update32(array, offset);
      break;
    case 2:
      update64(array, offset);
      break;
    case 3:
      update96(array, offset);
      break;
    case 4:
      update128(array, offset);
      break;
    default:
      throw new RuntimeException();
    }
  }

  /**
   * Updates the value of this object by reading from the given array, receiving
   * 128 bits data (full ranges).
   *
   * @param array
   *          array to read values from
   * @param offset
   *          offset of the long array
   */
  public void update128(int[] array, int offset) {
    System.arraycopy(array, offset, v, 0, 4);
    updateCount();
  }

  /**
   * Updates the value of this object by reading from the given array, receiving
   * only 96 bits data.
   *
   * @param array
   *          array to read values from
   * @param offset
   *          offset of the long array
   */
  public void update96(int[] array, int offset) {
    System.arraycopy(array, offset, v, 0, 3);
    v[3] = 0;
    updateCount();
  }

  /**
   * Updates the value of this object by reading from the given array, receiving
   * only 64 bits data.
   *
   * @param array
   *          array to read values from
   * @param offset
   *          offset of the long array
   */
  public void update64(int[] array, int offset) {
    System.arraycopy(array, offset, v, 0, 2);
    v[2] = 0;
    v[3] = 0;
    updateCount();
  }

  /**
   * Updates the value of this object by reading from the given array, receiving
   * only 32 bits data.
   *
   * @param array
   *          array to read values from
   * @param offset
   *          offset of the long array
   */
  public void update32(int[] array, int offset) {
    v[0] = array[offset];
    v[1] = 0;
    v[2] = 0;
    v[3] = 0;
    updateCount();
  }

  /**
   * Updates the value of this object with the given string.
   *
   * @param str
   *          string
   */
  public void update(String str) {
    update(str.toCharArray(), 0, str.length());
  }

  /**
   * Updates the value of this object from the given string with given offset
   * and length.
   *
   * @param str
   *          string
   * @param offset
   *          offset
   * @param length
   *          length
   */
  public void update(char[] str, int offset, int length) {

    // Skip leading zeros and compute number of digits in magnitude
    final int end = offset + length;
    assert (end <= str.length);
    int cursor = offset;
    while (cursor < end && str[cursor] == '0') {
      ++cursor;
    }

    if (cursor == end) {
      zeroClear();
      return;
    }
    if (end - cursor > MAX_DIGITS) {
      SqlMathUtil.throwOverflowException();
    }

    int accumulated = 0;
    int accumulatedCount = 0;
    while (cursor < end) {
      if (str[cursor] < '0' || str[cursor] > '9') {
        throw new NumberFormatException("Invalid string:"
            + new String(str, offset, length));
      }

      if (accumulatedCount == 9) {
        scaleUpTenDestructive((short) accumulatedCount);
        addDestructive(accumulated);
        accumulated = 0;
        accumulatedCount = 0;
      }
      int digit = str[cursor] - '0';
      accumulated = accumulated * 10 + digit;
      ++accumulatedCount;
      ++cursor;
    }

    if (accumulatedCount > 0) {
      scaleUpTenDestructive((short) accumulatedCount);
      addDestructive(accumulated);
    }
  }

  /**
   * Serialize this object to the given ByteBuffer, putting the required number
   * of ints for the given precision.
   *
   * @param buf
   *          ByteBuffer to write values to
   * @param precision
   *          0 to 38. Decimal digits.
   */
  public void serializeTo(IntBuffer buf, int precision) {
    buf.put(v, 0, getIntsPerElement(precision));
  }

  /**
   * Serialize this object to the given ByteBuffer, putting 128 bits data (full
   * ranges).
   *
   * @param buf
   *          ByteBuffer to write values to
   */
  public void serializeTo128(IntBuffer buf) {
    buf.put(v, 0, 4);
  }

  /**
   * Serialize this object to the given ByteBuffer, putting only 96 bits data.
   *
   * @param buf
   *          ByteBuffer to write values to
   */
  public void serializeTo96(IntBuffer buf) {
    assert (v[3] == 0);
    buf.put(v, 0, 3);
  }

  /**
   * Serialize this object to the given ByteBuffer, putting only 64 bits data.
   *
   * @param buf
   *          ByteBuffer to write values to
   */
  public void serializeTo64(IntBuffer buf) {
    assert (v[2] == 0);
    assert (v[3] == 0);
    buf.put(v, 0, 2);
  }

  /**
   * Serialize this object to the given ByteBuffer, putting only 32 bits data.
   *
   * @param buf
   *          ByteBuffer to write values to
   */
  public void serializeTo32(IntBuffer buf) {
    assert (v[1] == 0);
    assert (v[2] == 0);
    assert (v[3] == 0);
    buf.put(v[0]);
  }

  /**
   * Serialize this object to the given array, putting the required number of
   * ints for the given precision.
   *
   * @param array
   *          array to write values to
   * @param offset
   *          offset of the int array
   * @param precision
   *          0 to 38. Decimal digits.
   */
  public void serializeTo(int[] array, int offset, int precision) {
    System.arraycopy(v, 0, array, offset, getIntsPerElement(precision));
  }

  /**
   * Serialize this object to the given array, putting 128 bits data (full
   * ranges).
   *
   * @param array
   *          array to write values to
   * @param offset
   *          offset of the int array
   */
  public void serializeTo128(int[] array, int offset) {
    System.arraycopy(v, 0, array, offset, 4);
  }

  /**
   * Serialize this object to the given array, putting only 96 bits data.
   *
   * @param array
   *          array to write values to
   * @param offset
   *          offset of the int array
   */
  public void serializeTo96(int[] array, int offset) {
    assert (v[3] == 0);
    System.arraycopy(v, 0, array, offset, 3);
  }

  /**
   * Serialize this object to the given array, putting only 64 bits data.
   *
   * @param array
   *          array to write values to
   * @param offset
   *          offset of the int array
   */
  public void serializeTo64(int[] array, int offset) {
    assert (v[2] == 0);
    assert (v[3] == 0);
    System.arraycopy(v, 0, array, offset, 2);
  }

  /**
   * Serialize this object to the given array, putting only 32 bits data.
   *
   * @param array
   *          array to write values to
   * @param offset
   *          offset of the int array
   */
  public void serializeTo32(int[] array, int offset) {
    assert (v[1] == 0);
    assert (v[2] == 0);
    assert (v[3] == 0);
    array[0] = v[0];
  }

  @Override
  public int compareTo(UnsignedInt128 o) {
    return compareTo(o.v);
  }

  /**
   * @see #compareTo(UnsignedInt128)
   * @param o
   *          the object to be compared.
   * @return a negative integer, zero, or a positive integer as this object is
   *         less than, equal to, or greater than the specified object.
   */
  public int compareTo(int[] o) {
    return compareTo(o[0], o[1], o[2], o[3]);
  }

  /**
   * @see #compareTo(UnsignedInt128)
   * @param o0
   *          o0
   * @param o1
   *          o1
   * @param o2
   *          o2
   * @param o3
   *          o3
   * @return a negative integer, zero, or a positive integer as this object is
   *         less than, equal to, or greater than the specified object.
   */
  public int compareTo(int o0, int o1, int o2, int o3) {
    if (v[3] != o3) {
      return SqlMathUtil.compareUnsignedInt(v[3], o3);
    } else if (v[2] != o2) {
      return SqlMathUtil.compareUnsignedInt(v[2], o2);
    } else if (v[1] != o1) {
      return SqlMathUtil.compareUnsignedInt(v[1], o1);
    } else {
      return SqlMathUtil.compareUnsignedInt(v[0], o0);
    }
  }

  /**
   * Compares with the given object after scaling up/down it for 10**scaleUp.
   * This method is not destructive. Used from {@link Decimal128}.
   *
   * @param o
   *          the object to compare with
   * @param tenScale
   *          power of 10 to scale up (if positive) or down (if negative) the
   *          given object.
   * @return a negative integer, zero, or a positive integer as this object is
   *         less than, equal to, or greater than the specified object.
   */
  public int compareToScaleTen(UnsignedInt128 o, short tenScale) {

    // easier case. take a quick path
    if (tenScale == 0) {
      return compareTo(o);
    }

    // if o is zero, easy.
    if (o.isZero()) {
      return this.isZero() ? 0 : 1;
    } else if (this.isZero()) {

      // if this is zero, we need to check if o might become zero after
      // scaling down.
      // this is not easy because there might be rounding.
      if (tenScale > 0) {

        // scaling up, so o is definitely larger
        return -1;
      }
      if (tenScale < -SqlMathUtil.MAX_POWER_TEN_INT128) {

        // any value will become zero. even no possibility of rounding
        return 0;
      } else {

        // compare with 5 * 10**-tenScale
        // example: tenScale=-1. o will be zero after scaling if o>=5.
        boolean oZero = o
            .compareTo(SqlMathUtil.ROUND_POWER_TENS_INT128[-tenScale]) < 0;
        return oZero ? 0 : -1;
      }
    }

    // another quick path
    if (this.fitsInt32() && o.fitsInt32()
        && tenScale <= SqlMathUtil.MAX_POWER_TEN_INT31) {
      long v0Long = this.v[0] & SqlMathUtil.LONG_MASK;
      long o0;
      if (tenScale < 0) {
        if (tenScale < -SqlMathUtil.MAX_POWER_TEN_INT31) {

          // this scales down o.v[0] to 0 because 2^32 = 4.2E9. No
          // possibility of rounding.
          o0 = 0L;
        } else {

          // divide by 10**-tenScale. check for rounding.
          o0 = (o.v[0] & SqlMathUtil.LONG_MASK)
              / SqlMathUtil.POWER_TENS_INT31[-tenScale];
          long remainder = (o.v[0] & SqlMathUtil.LONG_MASK)
              % SqlMathUtil.POWER_TENS_INT31[-tenScale];
          if (remainder >= SqlMathUtil.ROUND_POWER_TENS_INT31[-tenScale]) {
            assert (o0 >= 0);
            ++o0; // this is safe because o0 is positive
          }
        }
      } else {

        // tenScale <= SqlMathUtil.MAX_POWER_TEN_INT31
        // so, we can make this as a long comparison
        o0 = (o.v[0] & SqlMathUtil.LONG_MASK)
            * (SqlMathUtil.POWER_TENS_INT31[tenScale] & SqlMathUtil.LONG_MASK);
      }
      return SqlMathUtil.compareUnsignedLong(v0Long, o0);
    }

    // unfortunately no quick path. let's do scale up/down
    int[] ov = o.v.clone();
    if (tenScale < 0) {

      // scale down. does rounding
      scaleDownTenArray4RoundUp(ov, (short) -tenScale);
    } else {

      // scale up
      boolean overflow = scaleUpTenArray(ov, tenScale);
      if (overflow) {

        // overflow is not an error here. it just means "this" is
        // smaller
        return -1;
      }
    }

    return compareTo(ov);
  }

  @Override
  public int hashCode() {

    // note: v[0] ^ v[1] ^ v[2] ^ v[3] would cause too many hash collisions
    return (v[0] * 0x2AB19E23) + (v[1] * 0x4918EACB) + (v[2] * 0xF03051A7)
        + v[3];
  }

  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof UnsignedInt128)) {
      return false;
    }
    return equals((UnsignedInt128) obj);
  }

  /**
   * Specialized version.
   *
   * @see #equals(Object)
   * @param o
   *          the object to compare with
   * @return whether this object is equal to the given object
   */
  public boolean equals(UnsignedInt128 o) {
    return this.v[0] == o.v[0] && this.v[1] == o.v[1] && this.v[2] == o.v[2]
        && this.v[3] == o.v[3];
  }

  /**
   * Specialized version.
   *
   * @see #equals(Object)
   * @param o0
   *          o0
   * @param o1
   *          o1
   * @param o2
   *          o2
   * @param o3
   *          o3
   * @return whether this object is equal to the given object
   */
  public boolean equals(int o0, int o1, int o2, int o3) {
    return this.v[0] == o0 && this.v[1] == o1 && this.v[2] == o2
        && this.v[3] == o3;
  }

  @Override
  protected Object clone() throws CloneNotSupportedException {
    return new UnsignedInt128(this);
  }

  /**
   * Convert this object to {@link BigInteger}. Do not use this method in a
   * performance sensitive place.
   *
   * @return BigInteger to represent this object
   */
  public BigInteger toBigIntegerSlow() {
    BigInteger bigInt = BigInteger.valueOf(v[3] & SqlMathUtil.LONG_MASK);
    bigInt = bigInt.shiftLeft(32);
    bigInt = bigInt.add(BigInteger.valueOf(v[2] & SqlMathUtil.LONG_MASK));
    bigInt = bigInt.shiftLeft(32);
    bigInt = bigInt.add(BigInteger.valueOf(v[1] & SqlMathUtil.LONG_MASK));
    bigInt = bigInt.shiftLeft(32);
    bigInt = bigInt.add(BigInteger.valueOf(v[0] & SqlMathUtil.LONG_MASK));
    return bigInt;
  }

  /**
   * Returns the formal string representation of this value. Unlike the debug
   * string returned by {@link #toString()}, this method returns a string that
   * can be used to re-construct this object. Remember, toString() is only for
   * debugging.
   *
   * @return string representation of this value
   */
  public String toFormalString() {
    char[] buf = new char[MAX_DIGITS + 1];
    int bufCount = 0;
    int nonZeroBufCount = 0;

    final int tenScale = SqlMathUtil.MAX_POWER_TEN_INT31;
    final int tenPower = SqlMathUtil.POWER_TENS_INT31[tenScale];
    UnsignedInt128 tmp = new UnsignedInt128(this);

    while (!tmp.isZero()) {
      int remainder = tmp.divideDestructive(tenPower);
      for (int i = 0; i < tenScale && bufCount < buf.length; ++i) {
        int digit = remainder % 10;
        remainder /= 10;
        buf[bufCount] = (char) (digit + '0');
        ++bufCount;
        if (digit != 0) {
          nonZeroBufCount = bufCount;
        }
      }
    }

    if (bufCount == 0) {
      return "0";
    } else {
      char[] reversed = new char[nonZeroBufCount];
      for (int i = 0; i < nonZeroBufCount; ++i) {
        reversed[i] = buf[nonZeroBufCount - i - 1];
      }
      return new String(reversed);
    }
  }

  /**
   * Similar to {@link #toFormalString()} but returns an array of digits
   * instead of string. The length of the array and the count of trailing
   * zeros are returned in the array passed at first and second positions
   * respectively.
   * @param meta Array of size two that is populated with length of the returned array
   *             and the count of trailing zeros.
   * @return Digits of the this value
   * @throws NullPointerException if meta is null.
   * @throws ArrayIndexOutOfBoundsException if meta is less than size two.
   */
  public char [] getDigitsArray(int [] meta) {
    char[] buf = new char[MAX_DIGITS + 1];
    int bufCount = 0;
    int nonZeroBufCount = 0;
    int trailingZeros = 0;

    final int tenScale = SqlMathUtil.MAX_POWER_TEN_INT31;
    final int tenPower = SqlMathUtil.POWER_TENS_INT31[tenScale];
    UnsignedInt128 tmp = new UnsignedInt128(this);

    while (!tmp.isZero()) {
      int remainder = tmp.divideDestructive(tenPower);
      for (int i = 0; i < tenScale && bufCount < buf.length; ++i) {
        int digit = remainder % 10;
        remainder /= 10;
        buf[bufCount] = (char) (digit + '0');
        ++bufCount;
        if (digit != 0) {
          nonZeroBufCount = bufCount;
        }
        if (nonZeroBufCount == 0) {

          // Count zeros until first non-zero digit is encountered.
          trailingZeros++;
        }
      }
    }

    if (bufCount == 0) {
      meta[0] = 1;
      meta[1] = 1;
      buf[0] = '0';
      return buf;
    } else {
      // Reverse in place
      for (int i = 0, j = nonZeroBufCount - 1; i < j; i++, j--) {
        char t = buf[i];
        buf[i] = buf[j];
        buf[j] = t;
      }
      meta[0] = nonZeroBufCount;
      meta[1] = trailingZeros;
      return buf;
    }
  }

  @Override
  public String toString() {
    StringBuilder str = new StringBuilder();
    str.append("Int128: count=" + count + ",");
    str.append("v[0]=" + v[0] + "(0x" + Integer.toHexString(v[0]) + "), ");
    str.append("v[1]=" + v[1] + "(0x" + Integer.toHexString(v[1]) + "), ");
    str.append("v[2]=" + v[2] + "(0x" + Integer.toHexString(v[2]) + "), ");
    str.append("v[3]=" + v[3] + "(0x" + Integer.toHexString(v[3]) + "), ");
    str.append("BigInteger#toString=" + toBigIntegerSlow().toString());
    return new String(str);
  }

  /**
   * Adds the given value to this value. This version is destructive, meaning it
   * modifies this object.
   *
   * @param right
   *          the value to add
   */
  public void addDestructive(UnsignedInt128 right) {
    addDestructive(right.v);
  }

  /**
   * Adds the given value to this value. This version is destructive, meaning it
   * modifies this object.
   *
   * @param r
   *          the value to add
   */
  public void addDestructive(int[] r) {
    long sum = 0L;
    for (int i = 0; i < INT_COUNT; ++i) {
      sum = (this.v[i] & SqlMathUtil.LONG_MASK)
          + (r[i] & SqlMathUtil.LONG_MASK) + (sum >>> 32);
      this.v[i] = (int) sum;
    }
    updateCount();

    if ((sum >> 32) != 0) {
      SqlMathUtil.throwOverflowException();
    }
  }

  /**
   * Adds the given value to this value. This version is destructive, meaning it
   * modifies this object.
   *
   * @param r
   *          the value to add
   */
  public void addDestructive(int r) {
    if ((this.v[0] & SqlMathUtil.LONG_MASK) + (r & SqlMathUtil.LONG_MASK) >= (1L << 32L)) {
      this.v[0] += r;

      if (this.v[1] == SqlMathUtil.FULLBITS_32) {
        this.v[1] = 0;
        if (this.v[2] == SqlMathUtil.FULLBITS_32) {
          this.v[2] = 0;
          if (this.v[3] == SqlMathUtil.FULLBITS_32) {
            SqlMathUtil.throwOverflowException();
          } else {
            ++this.v[3];
          }
        } else {
          ++this.v[2];
        }
      } else {
        ++this.v[1];
      }
    } else {
      this.v[0] += r;
    }
    updateCount();
  }

  /**
   * Adds one to this value. This version is destructive, meaning it modifies
   * this object.
   */
  public void incrementDestructive() {
    incrementArray(v);
    updateCount();
  }

  /**
   * Subtracts one from this value. This version is destructive, meaning it
   * modifies this object.
   */
  public void decrementDestructive() {
    decrementArray(v);
    updateCount();
  }

  /**
   * Adds the given value after scaling to this value. this := this + (right *
   * 10**tenScale). This version is destructive, meaning it modifies this
   * object.
   *
   * @param right
   *          the value to add
   * @param tenScale
   *          number of ten-based scaling. could be either positive or negative.
   */
  public void addDestructiveScaleTen(UnsignedInt128 right, short tenScale) {
    if (tenScale == 0) {
      addDestructive(right);
      return;
    }

    // scale up/down
    final int[] r = right.v.clone();
    if (tenScale < 0) {

      // scale down
      scaleDownTenArray4RoundUp(r, (short) -tenScale);
    } else if (tenScale > 0) {

      // scale up
      boolean overflow = scaleUpTenArray(r, tenScale);
      if (overflow) {
        SqlMathUtil.throwOverflowException();
      }
    }

    addDestructive(r);
  }

  /**
   * Subtracts the given value from this value. In other words, this := this -
   * right. This method will throw overflow exception if right operand is larger
   * than this value. This version is destructive, meaning it modifies this
   * object.
   *
   * @param right
   *          the value to subtract
   */
  public void subtractDestructive(UnsignedInt128 right) {
    subtractDestructive(right.v);
  }

  /**
   * Subtracts the given value from this value. In other words, this := this -
   * right. This method doesn't work if right operand is larger than this value.
   * This version is destructive, meaning it modifies this object.
   *
   * @param r
   *          the value to subtract
   */
  public void subtractDestructive(int[] r) {
    long sum = 0L;
    for (int i = 0; i < INT_COUNT; ++i) {
      sum = (this.v[i] & SqlMathUtil.LONG_MASK)
          - (r[i] & SqlMathUtil.LONG_MASK) - ((int) -(sum >> 32));
      this.v[i] = (int) sum;
    }
    updateCount();

    if ((sum >> 32) != 0) {
      SqlMathUtil.throwOverflowException();
    }
  }

  /**
   * Calculates absolute difference (remember that this is unsigned) of left and
   * right operator. <code>result := abs (left - right)</code> This is the core
   * implementation of subtract and signed add.
   *
   * @param left
   *          left operand
   * @param right
   *          right operand
   * @param result
   *          the object to receive the result. can be same object as left or
   *          right.
   * @return signum of the result. -1 if left was smaller than right, 1 if
   *         larger, 0 if same (result is zero).
   */
  public static byte difference(UnsignedInt128 left, UnsignedInt128 right,
      UnsignedInt128 result) {
    return differenceInternal(left, right.v, result);
  }

  /**
   * Calculates absolute difference of left and right operator after ten-based
   * scaling on right.
   * <code>result := abs (left - (right * 10**tenScale))</code> This is the core
   * implementation of subtract.
   *
   * @param left
   *          left operand
   * @param right
   *          right operand
   * @param result
   *          the object to receive the result. can be same object as left or
   *          right.
   * @param tenScale
   *          number of ten-based scaling. could be either positive or negative.
   * @return signum of the result. -1 if left was smaller than right, 1 if
   *         larger, 0 if same (result is zero).
   */
  public static byte differenceScaleTen(UnsignedInt128 left,
      UnsignedInt128 right, UnsignedInt128 result, short tenScale) {
    if (tenScale == 0) {
      return difference(left, right, result);
    }

    // scale up/down
    int[] r = right.v.clone();
    if (tenScale < 0) {
      // scale down
      scaleDownTenArray4RoundUp(r, (short) -tenScale);
    } else {
      // scale up
      boolean overflow = scaleUpTenArray(r, tenScale);
      if (overflow) {
        SqlMathUtil.throwOverflowException();
      }
    }

    return differenceInternal(left, r, result);
  }

  /**
   * Multiplies this value with the given integer value. This version is
   * destructive, meaning it modifies this object.
   *
   * @param right
   *          the value to multiply
   */
  public void multiplyDestructive(int right) {
    if (right == 0) {
      zeroClear();
      return;
    } else if (right == 1) {
      return;
    }

    long sum = 0L;
    long rightUnsigned = right & SqlMathUtil.LONG_MASK;
    for (int i = 0; i < INT_COUNT; ++i) {
      sum = (this.v[i] & SqlMathUtil.LONG_MASK) * rightUnsigned + (sum >>> 32);
      this.v[i] = (int) sum;
    }
    updateCount();

    if ((sum >> 32) != 0) {
      SqlMathUtil.throwOverflowException();
    }
  }

  /**
   * Multiplies this value with the given value. This version is destructive,
   * meaning it modifies this object.
   *
   * @param right
   *          the value to multiply
   */
  public void multiplyDestructive(UnsignedInt128 right) {
    if (this.fitsInt32() && right.fitsInt32()) {
      multiplyDestructiveFitsInt32(right, (short) 0, (short) 0);
      return;
    }
    multiplyArrays4And4To4NoOverflow(this.v, right.v);
    updateCount();
  }

  /**
   * Multiplies this value with the given value, followed by right bit shifts to
   * scale it back to this object. This is used from division. This version is
   * destructive, meaning it modifies this object.
   *
   * @param right
   *          the value to multiply
   * @param rightShifts
   *          the number of right-shifts after multiplication
   */
  public void multiplyShiftDestructive(UnsignedInt128 right, short rightShifts) {
    if (this.fitsInt32() && right.fitsInt32()) {
      multiplyDestructiveFitsInt32(right, rightShifts, (short) 0);
      return;
    }

    int[] z = multiplyArrays4And4To8(this.v, right.v);
    shiftRightArray(rightShifts, z, this.v, true);
    updateCount();
  }

  /**
   * Multiply this value with the given value, followed by ten-based scale down.
   * This method does the two operations without cutting off, so it preserves
   * accuracy without throwing a wrong overflow exception.
   *
   * @param right
   *          right operand
   * @param tenScale
   *          distance to scale down
   */
  public void multiplyScaleDownTenDestructive(UnsignedInt128 right,
      short tenScale) {
    assert (tenScale >= 0);
    if (this.fitsInt32() && right.fitsInt32()) {
      multiplyDestructiveFitsInt32(right, (short) 0, tenScale);
      return;
    }
    int[] z = multiplyArrays4And4To8(this.v, right.v);

    // Then, scale back.
    scaleDownTenArray8RoundUp(z, tenScale);
    update(z[0], z[1], z[2], z[3]);
  }

  /**
   * Divides this value with the given value. This version is destructive,
   * meaning it modifies this object.
   *
   * @param right
   *          the value to divide
   * @param remainder
   *          object to receive remainder
   */
  public void divideDestructive(UnsignedInt128 right, UnsignedInt128 remainder) {
    if (right.isZero()) {
      assert (right.isZero());
      SqlMathUtil.throwZeroDivisionException();
    }
    if (right.count == 1) {
      assert (right.v[1] == 0);
      assert (right.v[2] == 0);
      assert (right.v[3] == 0);
      int rem = divideDestructive(right.v[0]);
      remainder.update(rem);
      return;
    }

    int[] quotient = new int[5];
    int[] rem = SqlMathUtil.divideMultiPrecision(this.v, right.v, quotient);

    update(quotient[0], quotient[1], quotient[2], quotient[3]);
    remainder.update(rem[0], rem[1], rem[2], rem[3]);
  }

  /**
   * Scale up this object for 10**tenScale and then divides this value with the
   * given value. This version is destructive, meaning it modifies this object.
   *
   * @param right
   *          the value to divide
   * @param tenScale
   *          ten-based scale up distance
   * @param remainder
   *          object to receive remainder
   */
  public void divideScaleUpTenDestructive(UnsignedInt128 right, short tenScale,
      UnsignedInt128 remainder) {
    // in this case, we have to scale up _BEFORE_ division. otherwise we
    // might lose precision.
    if (tenScale > SqlMathUtil.MAX_POWER_TEN_INT128) {
      // in this case, the result will be surely more than 128 bit even
      // after division
      SqlMathUtil.throwOverflowException();
    }
    int[] scaledUp = multiplyConstructive256(SqlMathUtil.POWER_TENS_INT128[tenScale]);
    int[] quotient = new int[5];
    int[] rem = SqlMathUtil.divideMultiPrecision(scaledUp, right.v, quotient);
    update(quotient[0], quotient[1], quotient[2], quotient[3]);
    remainder.update(rem[0], rem[1], rem[2], rem[3]);
  }

  /**
   * Divides this value with the given value. This version is destructive,
   * meaning it modifies this object.
   *
   * @param right
   *          the value to divide
   * @return remainder
   */
  public int divideDestructive(int right) {
    assert (right >= 0);
    long rightUnsigned = right & SqlMathUtil.LONG_MASK;

    long quotient;
    long remainder = 0;

    for (int i = INT_COUNT - 1; i >= 0; --i) {
      remainder = ((this.v[i] & SqlMathUtil.LONG_MASK) + (remainder << 32));
      quotient = remainder / rightUnsigned;
      remainder %= rightUnsigned;
      this.v[i] = (int) quotient;
    }
    updateCount();
    return (int) remainder;
  }

  /**
   * Divides this value with the given value. This version is destructive,
   * meaning it modifies this object.
   *
   * @param right
   *          the value to divide
   * @return remainder
   */
  public long divideDestructive(long right) {
    assert (right >= 0);

    long quotient;
    long remainder = 0;

    for (int i = INT_COUNT - 1; i >= 0; --i) {
      remainder = ((this.v[i] & SqlMathUtil.LONG_MASK) + (remainder << 32));
      quotient = remainder / right;
      remainder %= right;
      this.v[i] = (int) quotient;
    }
    updateCount();
    return remainder;
  }

  /**
   * Right-shift for the given number of bits. This version is destructive,
   * meaning it modifies this object.
   *
   * @param bits
   *          the number of bits. must be positive
   * @param roundUp
   *          whether to round up the most significant bit that was discarded
   */
  public void shiftRightDestructive(int bits, boolean roundUp) {
    assert (bits >= 0);
    shiftRightDestructive(bits / 32, bits % 32, roundUp);
  }

  /**
   * Left-shift for the given number of bits. This method does not throw an
   * error even if overflow happens. This version is destructive, meaning it
   * modifies this object.
   *
   * @param bits
   *          the number of bits. must be positive
   */
  public void shiftLeftDestructive(int bits) {
    assert (bits >= 0);
    shiftLeftDestructive(bits / 32, bits % 32);
  }

  /**
   * Left-shift for the given number of bits. This method throws an error even
   * if overflow happens. This version is destructive, meaning it modifies this
   * object.
   *
   * @param bits
   *          the number of bits. must be positive
   */
  public void shiftLeftDestructiveCheckOverflow(int bits) {
    if (bitLength() + bits >= 128) {
      SqlMathUtil.throwOverflowException();
    }
    shiftLeftDestructive(bits);
  }

  /**
   * Scale down the value for 10**tenScale (this := this / 10**tenScale). This
   * method rounds-up, eg 44/10=4, 45/10=5. This version is destructive, meaning
   * it modifies this object.
   *
   * @param tenScale
   *          scaling. must be positive
   */
  public void scaleDownTenDestructive(short tenScale) {
    if (tenScale == 0) {
      return;
    }

    if (tenScale < 0) {
      throw new IllegalArgumentException();
    }

    if (isZero()) {
      return;
    }

    scaleDownTenArray4RoundUp(v, tenScale);
    updateCount();
  }

  /**
   * Scale down the value for 5**tenScale (this := this / 5**tenScale). This
   * method rounds-up, eg 42/5=8, 43/5=9. This version is destructive, meaning
   * it modifies this object.
   *
   * @param fiveScale
   *          scaling. must be positive
   */
  public void scaleDownFiveDestructive(short fiveScale) {
    if (fiveScale == 0) {
      return;
    }

    if (fiveScale < 0) {
      throw new IllegalArgumentException();
    }

    if (isZero()) {
      return;
    }

    scaleDownFiveArrayRoundUp(v, fiveScale);
    updateCount();
  }

  /**
   * Scale up the value for 10**tenScale (this := this * 10**tenScale). Scaling
   * up DOES throw an error when an overflow occurs. For example, 42.scaleUp(1)
   * = 420, 42.scaleUp(40) = ArithmeticException. This version is destructive,
   * meaning it modifies this object.
   *
   * @param tenScale
   *          scaling. must be positive
   */
  public void scaleUpTenDestructive(short tenScale) {
    if (tenScale == 0) {
      return;
    }

    if (tenScale < 0) {
      throw new IllegalArgumentException();
    }

    if (isZero()) {
      return;
    }

    // First, scale up with 2. Check overflow
    shiftLeftDestructiveCheckOverflow(tenScale);

    // Then, scale up with 5
    scaleUpFiveDestructive(tenScale);
  }

  /**
   * Scale up the value for 5**tenScale (this := this * 5**tenScale). Scaling up
   * DOES throw an error when an overflow occurs. This version is destructive,
   * meaning it modifies this object.
   *
   * @param fiveScale
   *          scaling. must be positive
   */
  public void scaleUpFiveDestructive(short fiveScale) {
    if (fiveScale == 0) {
      return;
    }

    if (fiveScale < 0) {
      throw new IllegalArgumentException();
    }

    if (isZero()) {
      return;
    }

    // Scale up with 5. This is done via #multiplyDestructive(int).
    while (fiveScale > 0) {
      int powerFive = Math.min(fiveScale, SqlMathUtil.MAX_POWER_FIVE_INT31);
      multiplyDestructive(SqlMathUtil.POWER_FIVES_INT31[powerFive]);
      fiveScale -= powerFive;
    }
  }

  /**
   * This version returns the result as a new object, not modifying the give
   * objects.
   *
   * @param right
   *          right operand
   * @return operation result as a new object
   */
  public UnsignedInt128 addConstructive(UnsignedInt128 right) {
    UnsignedInt128 ret = new UnsignedInt128(this);
    ret.addDestructive(right);
    return ret;
  }

  /**
   * This version returns the result as a new object, not modifying the give
   * objects.
   *
   * @return operation result as a new object
   */
  public UnsignedInt128 incrementConstructive() {
    UnsignedInt128 ret = new UnsignedInt128(this);
    ret.incrementDestructive();
    return ret;
  }

  /**
   * This version returns the result as a new object, not modifying the give
   * objects. This method doesn't work if right operand is larger than this
   * value.
   *
   * @param right
   *          right operand
   * @return operation result as a new object
   */
  public UnsignedInt128 subtractConstructive(UnsignedInt128 right) {
    UnsignedInt128 ret = new UnsignedInt128(this);
    ret.subtractDestructive(right);
    return ret;
  }

  /**
   * This version returns the result as a new object, not modifying the give
   * objects. This method doesn't work if right operand is larger than this
   * value.
   *
   * @return operation result as a new object
   */
  public UnsignedInt128 decrementConstructive() {
    UnsignedInt128 ret = new UnsignedInt128(this);
    ret.decrementDestructive();
    return ret;
  }

  /**
   * This version returns the result as a new object, not modifying the give
   * objects.
   *
   * @param right
   *          right operand
   * @return operation result as a new object
   */
  public UnsignedInt128 multiplyConstructive(int right) {
    UnsignedInt128 ret = new UnsignedInt128(this);
    ret.multiplyDestructive(right);
    return ret;
  }

  /**
   * This version returns the result as a new object, not modifying the give
   * objects.
   *
   * @param right
   *          right operand
   * @return operation result as a new object
   */
  public UnsignedInt128 multiplyConstructive(UnsignedInt128 right) {
    UnsignedInt128 ret = new UnsignedInt128(this);
    ret.multiplyDestructive(right);
    return ret;
  }

  /**
   * This version returns the result of multiplication as 256bit data.
   *
   * @param right
   *          right operand
   * @return operation result as 256bit data
   */
  public int[] multiplyConstructive256(UnsignedInt128 right) {
    return multiplyArrays4And4To8(this.v, right.v);
  }

  /**
   * This version returns the result as a new object, not modifying the give
   * objects. Note that this method cannot receive remainder. Use destructive
   * version for it.
   *
   * @param right
   *          right operand
   * @return operation result as a new object
   */
  public UnsignedInt128 divideConstructive(int right) {
    UnsignedInt128 ret = new UnsignedInt128(this);
    ret.divideDestructive(right);
    return ret;
  }

  /**
   * This version returns the result as a new object, not modifying the give
   * objects.
   *
   * @param right
   *          right operand
   * @param remainder
   *          object to receive remainder
   * @return operation result as a new object
   */
  public UnsignedInt128 divideConstructive(UnsignedInt128 right,
      UnsignedInt128 remainder) {
    UnsignedInt128 ret = new UnsignedInt128(this);
    ret.divideDestructive(right, remainder);
    return ret;
  }

  /**
   * This version returns the result as a new object, not modifying the give
   * objects.
   *
   * @param bits
   *          the number of bits. must be positive
   * @param roundUp
   *          whether to round up the most significant bit that was discarded
   * @return operation result as a new object
   */
  public UnsignedInt128 shiftRightConstructive(int bits, boolean roundUp) {
    UnsignedInt128 ret = new UnsignedInt128(this);
    ret.shiftRightDestructive(bits, roundUp);
    return ret;
  }

  /**
   * This version returns the result as a new object, not modifying the give
   * objects.
   *
   * @param bits
   *          the number of bits. must be positive
   * @return operation result as a new object
   */
  public UnsignedInt128 shiftLeftConstructive(int bits) {
    UnsignedInt128 ret = new UnsignedInt128(this);
    ret.shiftLeftDestructive(bits);
    return ret;
  }

  private short bitLength() {
    return SqlMathUtil.bitLength(v[0], v[1], v[2], v[3]);
  }

  private void shiftRightDestructive(int wordShifts, int bitShiftsInWord,
      boolean roundUp) {
    if (wordShifts == 0 && bitShiftsInWord == 0) {
      return;
    }

    assert (wordShifts >= 0);
    assert (bitShiftsInWord >= 0);
    assert (bitShiftsInWord < 32);
    if (wordShifts >= 4) {
      zeroClear();
      return;
    }

    final int shiftRestore = 32 - bitShiftsInWord;

    // check this because "123 << 32" will be 123.
    final boolean noRestore = bitShiftsInWord == 0;
    final int roundCarryNoRestoreMask = 1 << 31;
    final int roundCarryMask = (1 << (bitShiftsInWord - 1));
    boolean roundCarry;
    int z0 = 0, z1 = 0, z2 = 0, z3 = 0;

    switch (wordShifts) {
    case 3:
      roundCarry = (noRestore ? (this.v[2] & roundCarryNoRestoreMask)
          : (this.v[3] & roundCarryMask)) != 0;
      z0 = this.v[3] >>> bitShiftsInWord;
      break;
    case 2:
      roundCarry = (noRestore ? (this.v[1] & roundCarryNoRestoreMask)
          : (this.v[2] & roundCarryMask)) != 0;
      z1 = this.v[3] >>> bitShiftsInWord;
      z0 = (noRestore ? 0 : this.v[3] << shiftRestore)
          | (this.v[2] >>> bitShiftsInWord);
      break;
    case 1:
      roundCarry = (noRestore ? (this.v[0] & roundCarryNoRestoreMask)
          : (this.v[1] & roundCarryMask)) != 0;
      z2 = this.v[3] >>> bitShiftsInWord;
      z1 = (noRestore ? 0 : this.v[3] << shiftRestore)
          | (this.v[2] >>> bitShiftsInWord);
      z0 = (noRestore ? 0 : this.v[2] << shiftRestore)
          | (this.v[1] >>> bitShiftsInWord);
      break;
    case 0:
      roundCarry = (noRestore ? 0 : (this.v[0] & roundCarryMask)) != 0;
      z3 = this.v[3] >>> bitShiftsInWord;
      z2 = (noRestore ? 0 : this.v[3] << shiftRestore)
          | (this.v[2] >>> bitShiftsInWord);
      z1 = (noRestore ? 0 : this.v[2] << shiftRestore)
          | (this.v[1] >>> bitShiftsInWord);
      z0 = (noRestore ? 0 : this.v[1] << shiftRestore)
          | (this.v[0] >>> bitShiftsInWord);
      break;
    default:
      assert (false);
      throw new RuntimeException();
    }

    update(z0, z1, z2, z3);

    // round up
    if (roundUp && roundCarry) {
      incrementDestructive();
    }
  }

  private void shiftLeftDestructive(int wordShifts, int bitShiftsInWord) {
    if (wordShifts == 0 && bitShiftsInWord == 0) {
      return;
    }
    assert (wordShifts >= 0);
    assert (bitShiftsInWord >= 0);
    assert (bitShiftsInWord < 32);
    if (wordShifts >= 4) {
      zeroClear();
      return;
    }

    final int shiftRestore = 32 - bitShiftsInWord;
    // check this because "123 << 32" will be 123.
    final boolean noRestore = bitShiftsInWord == 0;
    int z0 = 0, z1 = 0, z2 = 0, z3 = 0;

    switch (wordShifts) {
    case 3:
      z3 = this.v[0] << bitShiftsInWord;
      break;
    case 2:
      z2 = (this.v[0] << bitShiftsInWord);
      z3 = (noRestore ? 0 : this.v[0] >>> shiftRestore)
          | this.v[1] << bitShiftsInWord;
      break;
    case 1:
      z1 = (this.v[0] << bitShiftsInWord);
      z2 = (noRestore ? 0 : this.v[0] >>> shiftRestore)
          | (this.v[1] << bitShiftsInWord);
      z3 = (noRestore ? 0 : this.v[1] >>> shiftRestore)
          | this.v[2] << bitShiftsInWord;
      break;
    case 0:
      z0 = (this.v[0] << bitShiftsInWord);
      z1 = (noRestore ? 0 : this.v[0] >>> shiftRestore)
          | (this.v[1] << bitShiftsInWord);
      z2 = (noRestore ? 0 : this.v[1] >>> shiftRestore)
          | (this.v[2] << bitShiftsInWord);
      z3 = (noRestore ? 0 : this.v[2] >>> shiftRestore)
          | this.v[3] << bitShiftsInWord;
      break;
    default:
      assert (false);
    }

    update(z0, z1, z2, z3);
  }

  /**
   * Multiplies this value with the given value.
   *
   * @param left
   *          the value to multiply. in AND out.
   * @param right
   *          the value to multiply. in
   */
  private static void multiplyArrays4And4To4NoOverflow(int[] left, int[] right) {
    assert (left.length == 4);
    assert (right.length == 4);
    long product;

    product = (right[0] & SqlMathUtil.LONG_MASK)
        * (left[0] & SqlMathUtil.LONG_MASK);
    int z0 = (int) product;

    product = (right[0] & SqlMathUtil.LONG_MASK)
        * (left[1] & SqlMathUtil.LONG_MASK)
        + (right[1] & SqlMathUtil.LONG_MASK)
        * (left[0] & SqlMathUtil.LONG_MASK) + (product >>> 32);
    int z1 = (int) product;

    product = (right[0] & SqlMathUtil.LONG_MASK)
        * (left[2] & SqlMathUtil.LONG_MASK)
        + (right[1] & SqlMathUtil.LONG_MASK)
        * (left[1] & SqlMathUtil.LONG_MASK)
        + (right[2] & SqlMathUtil.LONG_MASK)
        * (left[0] & SqlMathUtil.LONG_MASK) + (product >>> 32);
    int z2 = (int) product;

    // v[3]
    product = (right[0] & SqlMathUtil.LONG_MASK)
        * (left[3] & SqlMathUtil.LONG_MASK)
        + (right[1] & SqlMathUtil.LONG_MASK)
        * (left[2] & SqlMathUtil.LONG_MASK)
        + (right[2] & SqlMathUtil.LONG_MASK)
        * (left[1] & SqlMathUtil.LONG_MASK)
        + (right[3] & SqlMathUtil.LONG_MASK)
        * (left[0] & SqlMathUtil.LONG_MASK) + (product >>> 32);
    int z3 = (int) product;
    if ((product >>> 32) != 0) {
      SqlMathUtil.throwOverflowException();
    }

    // the combinations below definitely result in overflow
    if ((right[3] != 0 && (left[3] != 0 || left[2] != 0 || left[1] != 0))
        || (right[2] != 0 && (left[3] != 0 || left[2] != 0))
        || (right[1] != 0 && left[3] != 0)) {
      SqlMathUtil.throwOverflowException();
    }

    left[0] = z0;
    left[1] = z1;
    left[2] = z2;
    left[3] = z3;
  }

  private static int[] multiplyArrays4And4To8(int[] left, int[] right) {
    assert (left.length == 4);
    assert (right.length == 4);
    long product;

    // this method could go beyond the integer ranges until we scale back
    // so, we need twice more variables.
    int[] z = new int[8];

    product = (right[0] & SqlMathUtil.LONG_MASK)
        * (left[0] & SqlMathUtil.LONG_MASK);
    z[0] = (int) product;

    product = (right[0] & SqlMathUtil.LONG_MASK)
        * (left[1] & SqlMathUtil.LONG_MASK)
        + (right[1] & SqlMathUtil.LONG_MASK)
        * (left[0] & SqlMathUtil.LONG_MASK) + (product >>> 32);
    z[1] = (int) product;

    product = (right[0] & SqlMathUtil.LONG_MASK)
        * (left[2] & SqlMathUtil.LONG_MASK)
        + (right[1] & SqlMathUtil.LONG_MASK)
        * (left[1] & SqlMathUtil.LONG_MASK)
        + (right[2] & SqlMathUtil.LONG_MASK)
        * (left[0] & SqlMathUtil.LONG_MASK) + (product >>> 32);
    z[2] = (int) product;

    product = (right[0] & SqlMathUtil.LONG_MASK)
        * (left[3] & SqlMathUtil.LONG_MASK)
        + (right[1] & SqlMathUtil.LONG_MASK)
        * (left[2] & SqlMathUtil.LONG_MASK)
        + (right[2] & SqlMathUtil.LONG_MASK)
        * (left[1] & SqlMathUtil.LONG_MASK)
        + (right[3] & SqlMathUtil.LONG_MASK)
        * (left[0] & SqlMathUtil.LONG_MASK) + (product >>> 32);
    z[3] = (int) product;

    product = (right[1] & SqlMathUtil.LONG_MASK)
        * (left[3] & SqlMathUtil.LONG_MASK)
        + (right[2] & SqlMathUtil.LONG_MASK)
        * (left[2] & SqlMathUtil.LONG_MASK)
        + (right[3] & SqlMathUtil.LONG_MASK)
        * (left[1] & SqlMathUtil.LONG_MASK) + (product >>> 32);
    z[4] = (int) product;

    product = (right[2] & SqlMathUtil.LONG_MASK)
        * (left[3] & SqlMathUtil.LONG_MASK)
        + (right[3] & SqlMathUtil.LONG_MASK)
        * (left[2] & SqlMathUtil.LONG_MASK) + (product >>> 32);
    z[5] = (int) product;

    // v[1], v[0]
    product = (right[3] & SqlMathUtil.LONG_MASK)
        * (left[3] & SqlMathUtil.LONG_MASK) + (product >>> 32);
    z[6] = (int) product;
    z[7] = (int) (product >>> 32);

    return z;
  }

  private static void incrementArray(int[] array) {
    for (int i = 0; i < INT_COUNT; ++i) {
      if (array[i] != SqlMathUtil.FULLBITS_32) {
        array[i] = (int) ((array[i] & SqlMathUtil.LONG_MASK) + 1L);
        break;
      }
      array[i] = 0;
      if (i == INT_COUNT - 1) {
        SqlMathUtil.throwOverflowException();
      }
    }
  }

  private static void decrementArray(int[] array) {
    for (int i = 0; i < INT_COUNT; ++i) {
      if (array[i] != 0) {
        array[i] = (int) ((array[i] & SqlMathUtil.LONG_MASK) - 1L);
        break;
      }
      array[i] = SqlMathUtil.FULLBITS_32;
      if (i == INT_COUNT - 1) {
        SqlMathUtil.throwOverflowException();
      }
    }
  }

  /** common implementation of difference. */
  private static byte differenceInternal(UnsignedInt128 left, int[] r,
      UnsignedInt128 result) {
    int cmp = left.compareTo(r);
    if (cmp == 0) {
      result.zeroClear();
      return 0;
    }

    long sum = 0L;
    if (cmp > 0) {

      // left is larger
      for (int i = 0; i < INT_COUNT; ++i) {
        sum = (left.v[i] & SqlMathUtil.LONG_MASK)
            - (r[i] & SqlMathUtil.LONG_MASK) - ((int) -(sum >> 32));
        result.v[i] = (int) sum;
      }
    } else {

      // right is larger
      for (int i = 0; i < INT_COUNT; ++i) {
        sum = (r[i] & SqlMathUtil.LONG_MASK)
            - (left.v[i] & SqlMathUtil.LONG_MASK) - ((int) -(sum >> 32));
        result.v[i] = (int) sum;
      }
    }

    if ((sum >> 32) != 0) {
      SqlMathUtil.throwOverflowException();
    }

    result.updateCount();

    return cmp > 0 ? (byte) 1 : (byte) -1;
  }

  /**
   * @see #compareTo(UnsignedInt128)
   */
  private static int compareTo(int l0, int l1, int l2, int l3, int r0, int r1,
      int r2, int r3) {
    if (l3 != r3) {
      return SqlMathUtil.compareUnsignedInt(l3, r3);
    }
    if (l2 != r2) {
      return SqlMathUtil.compareUnsignedInt(l2, r2);
    }
    if (l1 != r1) {
      return SqlMathUtil.compareUnsignedInt(l1, r1);
    }
    if (l0 != r0) {
      return SqlMathUtil.compareUnsignedInt(l0, r0);
    }
    return 0;
  }

  /**
   * @param array
   * @param tenScale
   * @return Whether it overflowed
   */
  private static boolean scaleUpTenArray(int[] array, short tenScale) {
    while (tenScale > 0) {
      long sum = 0L;
      int powerTen = Math.min(tenScale, SqlMathUtil.MAX_POWER_TEN_INT31);
      tenScale -= powerTen;

      final long rightUnsigned = SqlMathUtil.POWER_TENS_INT31[powerTen]
          & SqlMathUtil.LONG_MASK;
      for (int i = 0; i < INT_COUNT; ++i) {
        sum = (array[i] & SqlMathUtil.LONG_MASK) * rightUnsigned + (sum >>> 32);
        array[i] = (int) sum;
      }

      if ((sum >> 32) != 0) {

        // overflow means this is smaller
        return true;
      }
    }
    return false;
  }

  /**
   * Scales down the given array (4 elements) for 10**tenScale. This method is
   * only used from add/subtract/compare, and most likely with smaller tenScale.
   * So, this method is not as optimized as
   * {@link #scaleDownTenArray8RoundUp(int[], short)}.
   *
   * @param array
   *          array to scale down. in AND out. length must be 4.
   * @param tenScale
   *          distance to scale down
   */
  private static void scaleDownTenArray4RoundUp(int[] array, short tenScale) {
    scaleDownFiveArray(array, tenScale);
    shiftRightArray(tenScale, array, array, true);
  }

  /**
   * Scales down the given array (8 elements) for 10**tenScale. This method is
   * frequently called from multiply(), so highly optimized. It's lengthy, but
   * this is inevitable to avoid array/object creation. <h2>Summary of this
   * method</h2>
   * <p>
   * This method employs division by inverse multiplication (except easy cases).
   * because all inverses of powers of tens are pre-calculated, this is much
   * faster than doing divisions. The matrix multiplication benchmark hit 3x
   * performance with this. because the inverse is a little bit smaller than
   * real value, the result is same or smaller than accurate answer, not larger.
   * we take care of it after the first multiplication.
   * </p>
   * <p>
   * Then, multiply it with power of tens (which is accurate) to correct +1
   * error and rounding up. let D = array - z * power: if D >= power/2, then
   * ++z. Otherwise, do nothing.
   * </p>
   *
   * @param array
   *          array to scale down. in AND out. length must be 8.
   * @param tenScale
   *          distance to scale down
   */
  private static void scaleDownTenArray8RoundUp(int[] array, short tenScale) {
    assert (array.length == 8);
    if (tenScale > MAX_DIGITS) {

      // then definitely this will end up zero.
      Arrays.fill(array, 0);
      return;
    }
    if (tenScale <= SqlMathUtil.MAX_POWER_TEN_INT31) {
      int divisor = SqlMathUtil.POWER_TENS_INT31[tenScale];
      assert (divisor > 0);
      boolean round = divideCheckRound(array, divisor);
      if (round) {
        incrementArray(array);
      }
      return;
    }

    // division by inverse multiplication.
    final int[] inverse = SqlMathUtil.INVERSE_POWER_TENS_INT128[tenScale].v;
    final int inverseWordShift = SqlMathUtil.INVERSE_POWER_TENS_INT128_WORD_SHIFTS[tenScale];
    assert (inverseWordShift <= 3);
    assert (inverse[3] != 0);
    for (int i = 5 + inverseWordShift; i < 8; ++i) {
      if (array[i] != 0) {
        SqlMathUtil.throwOverflowException(); // because inverse[3] is
                                              // not zero
      }
    }

    int z4 = 0, z5 = 0, z6 = 0, z7 = 0; // because inverse is scaled 2^128,
                                        // these will become v0-v3
    int z8 = 0, z9 = 0, z10 = 0; // for wordshift
    long product = 0L;

    product += (inverse[0] & SqlMathUtil.LONG_MASK)
        * (array[4] & SqlMathUtil.LONG_MASK)
        + (inverse[1] & SqlMathUtil.LONG_MASK)
        * (array[3] & SqlMathUtil.LONG_MASK)
        + (inverse[2] & SqlMathUtil.LONG_MASK)
        * (array[2] & SqlMathUtil.LONG_MASK)
        + (inverse[3] & SqlMathUtil.LONG_MASK)
        * (array[1] & SqlMathUtil.LONG_MASK);
    z4 = (int) product;
    product >>>= 32;

    product += (inverse[0] & SqlMathUtil.LONG_MASK)
        * (array[5] & SqlMathUtil.LONG_MASK)
        + (inverse[1] & SqlMathUtil.LONG_MASK)
        * (array[4] & SqlMathUtil.LONG_MASK)
        + (inverse[2] & SqlMathUtil.LONG_MASK)
        * (array[3] & SqlMathUtil.LONG_MASK)
        + (inverse[3] & SqlMathUtil.LONG_MASK)
        * (array[2] & SqlMathUtil.LONG_MASK);
    z5 = (int) product;
    product >>>= 32;

    product += (inverse[0] & SqlMathUtil.LONG_MASK)
        * (array[6] & SqlMathUtil.LONG_MASK)
        + (inverse[1] & SqlMathUtil.LONG_MASK)
        * (array[5] & SqlMathUtil.LONG_MASK)
        + (inverse[2] & SqlMathUtil.LONG_MASK)
        * (array[4] & SqlMathUtil.LONG_MASK)
        + (inverse[3] & SqlMathUtil.LONG_MASK)
        * (array[3] & SqlMathUtil.LONG_MASK);
    z6 = (int) product;
    product >>>= 32;

    product += (inverse[0] & SqlMathUtil.LONG_MASK)
        * (array[7] & SqlMathUtil.LONG_MASK)
        + (inverse[1] & SqlMathUtil.LONG_MASK)
        * (array[6] & SqlMathUtil.LONG_MASK)
        + (inverse[2] & SqlMathUtil.LONG_MASK)
        * (array[5] & SqlMathUtil.LONG_MASK)
        + (inverse[3] & SqlMathUtil.LONG_MASK)
        * (array[4] & SqlMathUtil.LONG_MASK);
    z7 = (int) product;
    product >>>= 32;

    if (inverseWordShift >= 1) {
      product += (inverse[1] & SqlMathUtil.LONG_MASK)
          * (array[7] & SqlMathUtil.LONG_MASK)
          + (inverse[2] & SqlMathUtil.LONG_MASK)
          * (array[6] & SqlMathUtil.LONG_MASK)
          + (inverse[3] & SqlMathUtil.LONG_MASK)
          * (array[5] & SqlMathUtil.LONG_MASK);
      z8 = (int) product;
      product >>>= 32;

      if (inverseWordShift >= 2) {
        product += (inverse[2] & SqlMathUtil.LONG_MASK)
            * (array[7] & SqlMathUtil.LONG_MASK)
            + (inverse[3] & SqlMathUtil.LONG_MASK)
            * (array[6] & SqlMathUtil.LONG_MASK);
        z9 = (int) product;
        product >>>= 32;

        if (inverseWordShift >= 3) {
          product += (inverse[3] & SqlMathUtil.LONG_MASK)
              * (array[7] & SqlMathUtil.LONG_MASK);
          z10 = (int) product;
          product >>>= 32;
        }
      }
    }

    if (product != 0) {
      SqlMathUtil.throwOverflowException();
    }

    // if inverse is word-shifted for accuracy, shift it back here.
    switch (inverseWordShift) {
    case 1:
      z4 = z5;
      z5 = z6;
      z6 = z7;
      z7 = z8;
      break;
    case 2:
      z4 = z6;
      z5 = z7;
      z6 = z8;
      z7 = z9;
      break;
    case 3:
      z4 = z7;
      z5 = z8;
      z6 = z9;
      z7 = z10;
      break;
    default:
      break;
    }

    // now, correct +1 error and rounding up.
    final int[] power = SqlMathUtil.POWER_TENS_INT128[tenScale].v;
    final int[] half = SqlMathUtil.ROUND_POWER_TENS_INT128[tenScale].v;

    int d0, d1, d2, d3, d4;
    product = (array[0] & SqlMathUtil.LONG_MASK)
        - (power[0] & SqlMathUtil.LONG_MASK) * (z4 & SqlMathUtil.LONG_MASK);
    d0 = (int) product;

    product = (array[1] & SqlMathUtil.LONG_MASK)
        - (power[0] & SqlMathUtil.LONG_MASK) * (z5 & SqlMathUtil.LONG_MASK)
        - (power[1] & SqlMathUtil.LONG_MASK) * (z4 & SqlMathUtil.LONG_MASK)
        - ((int) -(product >> 32));
    d1 = (int) product;

    product = (array[2] & SqlMathUtil.LONG_MASK)
        - (power[0] & SqlMathUtil.LONG_MASK) * (z6 & SqlMathUtil.LONG_MASK)
        - (power[1] & SqlMathUtil.LONG_MASK) * (z5 & SqlMathUtil.LONG_MASK)
        - (power[2] & SqlMathUtil.LONG_MASK) * (z4 & SqlMathUtil.LONG_MASK)
        - ((int) -(product >> 32));
    d2 = (int) product;

    product = (array[3] & SqlMathUtil.LONG_MASK)
        - (power[0] & SqlMathUtil.LONG_MASK) * (z7 & SqlMathUtil.LONG_MASK)
        - (power[1] & SqlMathUtil.LONG_MASK) * (z6 & SqlMathUtil.LONG_MASK)
        - (power[2] & SqlMathUtil.LONG_MASK) * (z5 & SqlMathUtil.LONG_MASK)
        - (power[3] & SqlMathUtil.LONG_MASK) * (z4 & SqlMathUtil.LONG_MASK)
        - ((int) -(product >> 32));
    d3 = (int) product;

    product = (array[4] & SqlMathUtil.LONG_MASK)
        - (power[1] & SqlMathUtil.LONG_MASK) * (z7 & SqlMathUtil.LONG_MASK)
        - (power[2] & SqlMathUtil.LONG_MASK) * (z6 & SqlMathUtil.LONG_MASK)
        - (power[3] & SqlMathUtil.LONG_MASK) * (z5 & SqlMathUtil.LONG_MASK)
        - ((int) -(product >> 32));
    d4 = (int) product;

    // If the difference is larger than 2^128 (d4 != 0), then D is
    // definitely larger than power, so increment.
    // otherwise, compare it with power and half.
    boolean increment = (d4 != 0)
        || (compareTo(d0, d1, d2, d3, half[0], half[1], half[2], half[3]) >= 0);

    array[0] = z4;
    array[1] = z5;
    array[2] = z6;
    array[3] = z7;
    if (increment) {
      incrementArray(array);
    }
  }

  /**
   * Scales down the given array for 5**fiveScale.
   *
   * @param array
   *          array to scale down
   * @param fiveScale
   *          distance to scale down
   * @return Whether it requires incrementing if rounding
   */
  private static boolean scaleDownFiveArray(int[] array, short fiveScale) {
    while (true) {
      int powerFive = Math.min(fiveScale, SqlMathUtil.MAX_POWER_FIVE_INT31);
      fiveScale -= powerFive;

      int divisor = SqlMathUtil.POWER_FIVES_INT31[powerFive];
      assert (divisor > 0);
      if (fiveScale == 0) {
        return divideCheckRound(array, divisor);
      } else {
        divideCheckRound(array, divisor);
      }
    }
  }

  private static boolean divideCheckRound(int[] array, int divisor) {
    long remainder = 0;
    for (int i = array.length - 1; i >= 0; --i) {
      remainder = ((array[i] & SqlMathUtil.LONG_MASK) + (remainder << 32));
      array[i] = (int) (remainder / divisor);
      remainder %= divisor;
    }

    return (remainder >= (divisor >> 1));
  }

  private static void scaleDownFiveArrayRoundUp(int[] array, short tenScale) {
    boolean rounding = scaleDownFiveArray(array, tenScale);
    if (rounding) {
      incrementArray(array);
    }
  }

  /**
   * Internal method to apply the result of multiplication with right-shifting.
   * This method does round the value while right-shifting (SQL Numeric
   * semantics).
   *
   * @param rightShifts
   *          distance of right-shifts
   */
  private static void shiftRightArray(int rightShifts, int[] z, int[] result,
      boolean round) {
    assert (rightShifts >= 0);
    if (rightShifts == 0) {
      for (int i = 0; i < INT_COUNT; ++i) {
        if (z[i + INT_COUNT] != 0) {
          SqlMathUtil.throwOverflowException();
        }
      }
      result[0] = z[0];
      result[1] = z[1];
      result[2] = z[2];
      result[3] = z[3];
    } else {
      final int wordShifts = rightShifts / 32;
      final int bitShiftsInWord = rightShifts % 32;
      final int shiftRestore = 32 - bitShiftsInWord;

      // check this because "123 << 32" will be 123.
      final boolean noRestore = bitShiftsInWord == 0;

      // overflow checks
      if (z.length > INT_COUNT) {
        if (wordShifts + INT_COUNT < z.length
            && (z[wordShifts + INT_COUNT] >>> bitShiftsInWord) != 0) {
          SqlMathUtil.throwOverflowException();
        }

        for (int i = 1; i < INT_COUNT; ++i) {
          if (i + wordShifts < z.length - INT_COUNT
              && z[i + wordShifts + INT_COUNT] != 0) {
            SqlMathUtil.throwOverflowException();
          }
        }
      }

      // check round-ups before settings values to result.
      // be aware that result could be the same object as z.
      boolean roundCarry = false;
      if (round) {
        if (bitShiftsInWord == 0) {
          assert (wordShifts > 0);
          roundCarry = z[wordShifts - 1] < 0;
        } else {
          roundCarry = (z[wordShifts] & (1 << (bitShiftsInWord - 1))) != 0;
        }
      }

      // extract the values.
      for (int i = 0; i < INT_COUNT; ++i) {
        int val = 0;
        if (!noRestore && i + wordShifts + 1 < z.length) {
          val = z[i + wordShifts + 1] << shiftRestore;
        }
        if (i + wordShifts < z.length) {
          val |= (z[i + wordShifts] >>> bitShiftsInWord);
        }
        result[i] = val;
      }

      if (roundCarry) {
        incrementArray(result);
      }
    }
  }

  /**
   * helper method for multiplication. used when either left/right fits int32.
   */
  private void multiplyDestructiveFitsInt32(UnsignedInt128 right,
      short rightShifts, short tenScaleDown) {
    assert (this.fitsInt32() && right.fitsInt32());
    assert (rightShifts == 0 || tenScaleDown == 0); // only one of them
    if (this.isZero()) {
      return; // zero. no need to shift/scale
    } else if (right.isZero()) {
      zeroClear();
      return; // zero. no need to shift/scale
    } else if (this.isOne()) {
      this.update(right);
    } else {
      this.multiplyDestructive(right.v[0]);
    }

    if (rightShifts > 0) {
      this.shiftRightDestructive(rightShifts, true);
    } else if (tenScaleDown > 0) {
      this.scaleDownTenDestructive(tenScaleDown);
    }
  }

  /** Updates the value of {@link #count} by checking {@link #v}. */
  private void updateCount() {
    if (v[3] != 0) {
      this.count = (byte) 4;
    } else if (v[2] != 0) {
      this.count = (byte) 3;
    } else if (v[1] != 0) {
      this.count = (byte) 2;
    } else if (v[0] != 0) {
      this.count = (byte) 1;
    } else {
      this.count = (byte) 0;
    }
  }
  
   /*(non-Javadoc)
   * Serializes one int part into the given @{link #ByteBuffer} 
   *  considering two's complement for negatives.
   */
    private static void fastSerializeIntPartForHiveDecimal(ByteBuffer buf, 
        int pos, int value, byte signum, boolean isFirstNonZero) {
      if (signum == -1 && value != 0) {
        value = (isFirstNonZero ? -value : ~value);
      }
      buf.putInt(pos, value);
    }

    /* (non-Javadoc)
     * Serializes this value into the format used by @{link #java.math.BigInteger}
     * This is used for fast assignment of a Decimal128 to a HiveDecimalWritable internal storage.
     * See OpenJDK BigInteger.toByteArray for a reference implementation. 
     * @param scratch
     * @param signum
     * @return
     */
  public int fastSerializeForHiveDecimal(Decimal128FastBuffer scratch, byte signum) {
    int bufferUsed = this.count;
    ByteBuffer buf = scratch.getByteBuffer(bufferUsed);
    buf.put(0, (byte) (signum == 1 ? 0 : signum));
    int pos = 1;
    int firstNonZero = 0;
    while(firstNonZero < this.count && v[firstNonZero] == 0) {
        ++firstNonZero;
    }
    switch(this.count) {
    case 4:
      fastSerializeIntPartForHiveDecimal(buf, pos, v[3], signum, firstNonZero == 3);
      pos+=4;
      // intentional fall through
    case 3:
      fastSerializeIntPartForHiveDecimal(buf, pos, v[2], signum, firstNonZero == 2);
      pos+=4;
      // intentional fall through
    case 2:
      fastSerializeIntPartForHiveDecimal(buf, pos, v[1], signum, firstNonZero == 1);
      pos+=4;
      // intentional fall through
    case 1:
      fastSerializeIntPartForHiveDecimal(buf, pos, v[0], signum, true);
    }
    return bufferUsed;
  }

  /**
   * Updates this value from a serialized unscaled {@link java.math.BigInteger} representation.
   * This is used for fast update of a Decimal128 from a HiveDecimalWritable internal storage.
   * @param internalStorage
   * @return
   */
    public byte fastUpdateFromInternalStorage(byte[] internalStorage) {
        byte signum = 0;
        int skip = 0;
        this.count = 0;
        // Skip over any leading 0s or 0xFFs
        byte firstByte = internalStorage[0];
        if (firstByte == 0 || firstByte == -1) {
            while((skip < internalStorage.length) &&
              (internalStorage[skip] == firstByte)) {
                ++skip;
            }
        }
        if (skip == internalStorage.length) {
          // The entire storage is 0x00s or 0xFFs
          // 0x00s means is 0
          // 0xFFs means is -1
          assert (firstByte == 0 || firstByte == -1);
          if (firstByte == -1) {
             signum = -1;
             this.count = 1;
             this.v[0] = 1;
          }
          else {
            signum = 0;
          }
        }
        else {
            // We skipped over leading 0x00s and 0xFFs
            // Important, signum is given by the firstByte, not by byte[keep]!
            signum = (firstByte < 0) ? (byte) -1 : (byte) 1;

            // Now we read the big-endian compacted two's complement int parts
            // Compacted means they are stripped of leading 0x00s and 0xFFs
            // This is why we do the intLength/pos tricks bellow
            // 'length' is all the bytes we have to read, after we skip 'skip'
            // 'pos' is where to start reading the current int
            // 'intLength' is how many bytes we read for the current int

            int length = internalStorage.length - skip;
            int pos = skip;
            int intLength = 0;
            switch(length) {
            case 16: ++intLength; //intentional fall through
            case 15: ++intLength;
            case 14: ++intLength;
            case 13: ++intLength;
                v[3] = fastUpdateIntFromInternalStorage(internalStorage, signum, pos, intLength);
                ++this.count;
                pos += intLength;
                intLength = 0;
                //intentional fall through
            case 12: ++intLength; //intentional fall through
            case 11: ++intLength;
            case 10: ++intLength;
            case 9: ++intLength;
                v[2] = fastUpdateIntFromInternalStorage(internalStorage, signum, pos, intLength);
                ++this.count;
                pos += intLength;
                intLength = 0;
                //intentional fall through
            case 8: ++intLength; //intentional fall through
            case 7: ++intLength;
            case 6: ++intLength;
            case 5: ++intLength;
                v[1] = fastUpdateIntFromInternalStorage(internalStorage, signum, pos, intLength);
                ++this.count;
                pos += intLength;
                intLength = 0;
                //intentional fall through
            case 4: ++intLength; //intentional fall through
            case 3: ++intLength;
            case 2: ++intLength;
            case 1: ++intLength;
                v[0] = fastUpdateIntFromInternalStorage(internalStorage, signum, pos, intLength);
                ++this.count;
                break;
            default:
                // This should not happen
                throw new RuntimeException("Impossible HiveDecimal internal storage length!");
            }
            if (signum == -1) {
                // So far we've read the one's complement
                // add 1 to turn it into two's complement
                for(int i = 0; i < this.count; ++i) {
                    if (v[i] != 0) {
                        v[i] = (int)((v[i] & 0xFFFFFFFFL) + 1);
                        if (v[i] != 0) {
                            break;
                        }
                    }
                }
            }
        }
        return signum;
    }

    /**
     * reads one int part from the two's complement Big-Endian compacted representation,
     * starting from index pos
     * @param internalStorage {@link java.math.BigInteger} serialized representation
     * @param pos
     * @return
     */
    private int fastUpdateIntFromInternalStorage(byte[] internalStorage,
            byte signum, int pos, int length) {
      // due to the way we use the allocation-free cast from HiveDecimalWriter to decimal128,
      // we do not have the luxury of a ByteBuffer...
      byte b0, b1, b2, b3;
      if (signum == -1) {
          b1=b2=b3 = (byte)-1;
      }
      else {
          b1=b2=b3=0;
      }
      switch(length) {
      case 4:
          b3 = internalStorage[pos];
          ++pos;
          //intentional fall through
      case 3:
          b2 = internalStorage[pos];
          ++pos;
          //intentional fall through
      case 2:
          b1 = internalStorage[pos];
          ++pos;
          //intentional fall through
      case 1:
          b0 = internalStorage[pos];
          break;
      default:
          // this should never happen
          throw new RuntimeException("Impossible HiveDecimal internal storage position!");
      }

      int value = ((int)b0        & 0x000000FF) |
              (((int)b1 <<  8) & 0x0000FF00) |
              (((int)b2 << 16) & 0x00FF0000) |
              (((int)b3 << 24) & 0xFF000000);

      if (signum == -1 && value != 0) {
          // Make one's complement, masked only for the bytes read
          int mask = -1 >>> (8*(4-length));
          value = ~value & mask;
      }
      return value;
    }

  public int[] getV() {
    return v;
  }

  /**
   * This setter is only for de-serialization, should not be used otherwise.
   */
  public void setV(int [] v) {
    this.v[0] = v[0];
    this.v[1] = v[1];
    this.v[2] = v[2];
    this.v[3] = v[3];
    updateCount();
  }

  public byte getCount() {
    return count;
  }

  /**
   * This setter is only for de-serialization, should not be used otherwise.
   */
  public void setCount(byte count) {
    this.count = count;
  }
}
