/**
* 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.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
/**
*
* HiveDecimal. Simple wrapper for BigDecimal. Adds fixed max precision and non scientific string
* representation
*
*/
public final class HiveDecimalV1 implements Comparable<HiveDecimalV1> {
@HiveDecimalVersionV1
public static final int MAX_PRECISION = 38;
@HiveDecimalVersionV1
public static final int MAX_SCALE = 38;
/**
* Default precision/scale when user doesn't specify in the column metadata, such as
* decimal and decimal(8).
*/
@HiveDecimalVersionV1
public static final int USER_DEFAULT_PRECISION = 10;
@HiveDecimalVersionV1
public static final int USER_DEFAULT_SCALE = 0;
/**
* Default precision/scale when system is not able to determine them, such as in case
* of a non-generic udf.
*/
@HiveDecimalVersionV1
public static final int SYSTEM_DEFAULT_PRECISION = 38;
@HiveDecimalVersionV1
public static final int SYSTEM_DEFAULT_SCALE = 18;
@HiveDecimalVersionV1
public static final HiveDecimalV1 ZERO = new HiveDecimalV1(BigDecimal.ZERO);
@HiveDecimalVersionV1
public static final HiveDecimalV1 ONE = new HiveDecimalV1(BigDecimal.ONE);
@HiveDecimalVersionV1
public static final int ROUND_FLOOR = BigDecimal.ROUND_FLOOR;
@HiveDecimalVersionV1
public static final int ROUND_CEILING = BigDecimal.ROUND_CEILING;
@HiveDecimalVersionV1
public static final int ROUND_HALF_UP = BigDecimal.ROUND_HALF_UP;
@HiveDecimalVersionV1
public static final int ROUND_HALF_EVEN = BigDecimal.ROUND_HALF_EVEN;
private BigDecimal bd = BigDecimal.ZERO;
private HiveDecimalV1(BigDecimal bd) {
this.bd = bd;
}
@HiveDecimalVersionV1
public static HiveDecimalV1 create(BigDecimal b) {
return create(b, true);
}
@HiveDecimalVersionV1
public static HiveDecimalV1 create(BigDecimal b, boolean allowRounding) {
BigDecimal bd = normalize(b, allowRounding);
return bd == null ? null : new HiveDecimalV1(bd);
}
@HiveDecimalVersionV1
public static HiveDecimalV1 create(BigInteger unscaled, int scale) {
BigDecimal bd = normalize(new BigDecimal(unscaled, scale), true);
return bd == null ? null : new HiveDecimalV1(bd);
}
@HiveDecimalVersionV1
public static HiveDecimalV1 create(String dec) {
BigDecimal bd;
try {
bd = new BigDecimal(dec.trim());
} catch (NumberFormatException ex) {
return null;
}
bd = normalize(bd, true);
return bd == null ? null : new HiveDecimalV1(bd);
}
@HiveDecimalVersionV1
public static HiveDecimalV1 create(BigInteger bi) {
BigDecimal bd = normalize(new BigDecimal(bi), true);
return bd == null ? null : new HiveDecimalV1(bd);
}
@HiveDecimalVersionV1
public static HiveDecimalV1 create(int i) {
return new HiveDecimalV1(new BigDecimal(i));
}
@HiveDecimalVersionV1
public static HiveDecimalV1 create(long l) {
return new HiveDecimalV1(new BigDecimal(l));
}
@HiveDecimalVersionV1
@Override
public String toString() {
return bd.toPlainString();
}
/**
* Return a string representation of the number with the number of decimal digits as
* the given scale. Please note that this is different from toString().
* @param scale the number of digits after the decimal point
* @return the string representation of exact number of decimal digits
*/
@HiveDecimalVersionV1
public String toFormatString(int scale) {
return (bd.scale() == scale ? bd :
bd.setScale(scale, RoundingMode.HALF_UP)).toPlainString();
}
@HiveDecimalVersionV1
public HiveDecimalV1 setScale(int i) {
return new HiveDecimalV1(bd.setScale(i, RoundingMode.HALF_UP));
}
@HiveDecimalVersionV1
@Override
public int compareTo(HiveDecimalV1 dec) {
return bd.compareTo(dec.bd);
}
@HiveDecimalVersionV1
@Override
public int hashCode() {
return bd.hashCode();
}
@HiveDecimalVersionV1
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != getClass()) {
return false;
}
return bd.equals(((HiveDecimalV1) obj).bd);
}
@HiveDecimalVersionV1
public int scale() {
return bd.scale();
}
/**
* Returns the number of digits (integer and fractional) in the number, which is equivalent
* to SQL decimal precision. Note that this is different from BigDecimal.precision(),
* which returns the precision of the unscaled value (BigDecimal.valueOf(0.01).precision() = 1,
* whereas HiveDecimal.create("0.01").precision() = 2).
* If you want the BigDecimal precision, use HiveDecimal.bigDecimalValue().precision()
* @return
*/
@HiveDecimalVersionV1
public int precision() {
int bdPrecision = bd.precision();
int bdScale = bd.scale();
if (bdPrecision < bdScale) {
// This can happen for numbers less than 0.1
// For 0.001234: bdPrecision=4, bdScale=6
// In this case, we'll set the type to have the same precision as the scale.
return bdScale;
}
return bdPrecision;
}
/** Note - this method will corrupt the value if it doesn't fit. */
@HiveDecimalVersionV1
public int intValue() {
return bd.intValue();
}
@HiveDecimalVersionV1
public double doubleValue() {
return bd.doubleValue();
}
/** Note - this method will corrupt the value if it doesn't fit. */
@HiveDecimalVersionV1
public long longValue() {
return bd.longValue();
}
/** Note - this method will corrupt the value if it doesn't fit. */
@HiveDecimalVersionV1
public short shortValue() {
return bd.shortValue();
}
@HiveDecimalVersionV1
public float floatValue() {
return bd.floatValue();
}
@HiveDecimalVersionV1
public BigDecimal bigDecimalValue() {
return bd;
}
@HiveDecimalVersionV1
public byte byteValue() {
return bd.byteValue();
}
@HiveDecimalVersionV1
public HiveDecimalV1 setScale(int adjustedScale, int rm) {
return create(bd.setScale(adjustedScale, rm));
}
@HiveDecimalVersionV1
public HiveDecimalV1 subtract(HiveDecimalV1 dec) {
return create(bd.subtract(dec.bd));
}
@HiveDecimalVersionV1
public HiveDecimalV1 multiply(HiveDecimalV1 dec) {
return create(bd.multiply(dec.bd), false);
}
@HiveDecimalVersionV1
public BigInteger unscaledValue() {
return bd.unscaledValue();
}
@HiveDecimalVersionV1
public HiveDecimalV1 scaleByPowerOfTen(int n) {
return create(bd.scaleByPowerOfTen(n));
}
@HiveDecimalVersionV1
public HiveDecimalV1 abs() {
return create(bd.abs());
}
@HiveDecimalVersionV1
public HiveDecimalV1 negate() {
return create(bd.negate());
}
@HiveDecimalVersionV1
public HiveDecimalV1 add(HiveDecimalV1 dec) {
return create(bd.add(dec.bd));
}
@HiveDecimalVersionV1
public HiveDecimalV1 pow(int n) {
BigDecimal result = normalize(bd.pow(n), false);
return result == null ? null : new HiveDecimalV1(result);
}
@HiveDecimalVersionV1
public HiveDecimalV1 remainder(HiveDecimalV1 dec) {
return create(bd.remainder(dec.bd));
}
@HiveDecimalVersionV1
public HiveDecimalV1 divide(HiveDecimalV1 dec) {
return create(bd.divide(dec.bd, MAX_SCALE, RoundingMode.HALF_UP), true);
}
/**
* Get the sign of the underlying decimal.
* @return 0 if the decimal is equal to 0, -1 if less than zero, and 1 if greater than 0
*/
@HiveDecimalVersionV1
public int signum() {
return bd.signum();
}
private static BigDecimal trim(BigDecimal d) {
if (d.compareTo(BigDecimal.ZERO) == 0) {
// Special case for 0, because java doesn't strip zeros correctly on that number.
d = BigDecimal.ZERO;
} else {
d = d.stripTrailingZeros();
if (d.scale() < 0) {
// no negative scale decimals
d = d.setScale(0);
}
}
return d;
}
private static BigDecimal normalize(BigDecimal bd, boolean allowRounding) {
if (bd == null) {
return null;
}
bd = trim(bd);
int intDigits = bd.precision() - bd.scale();
if (intDigits > MAX_PRECISION) {
return null;
}
int maxScale = Math.min(MAX_SCALE, Math.min(MAX_PRECISION - intDigits, bd.scale()));
if (bd.scale() > maxScale ) {
if (allowRounding) {
bd = bd.setScale(maxScale, RoundingMode.HALF_UP);
// Trimming is again necessary, because rounding may introduce new trailing 0's.
bd = trim(bd);
} else {
bd = null;
}
}
return bd;
}
private static BigDecimal enforcePrecisionScale(BigDecimal bd, int maxPrecision, int maxScale) {
if (bd == null) {
return null;
}
/**
* Specially handling the case that bd=0, and we are converting it to a type where precision=scale,
* such as decimal(1, 1).
*/
if (bd.compareTo(BigDecimal.ZERO) == 0 && bd.scale() == 0 && maxPrecision == maxScale) {
return bd.setScale(maxScale);
}
bd = trim(bd);
if (bd.scale() > maxScale) {
bd = bd.setScale(maxScale, RoundingMode.HALF_UP);
}
int maxIntDigits = maxPrecision - maxScale;
int intDigits = bd.precision() - bd.scale();
if (intDigits > maxIntDigits) {
return null;
}
return bd;
}
@HiveDecimalVersionV1
public static HiveDecimalV1 enforcePrecisionScale(HiveDecimalV1 dec, int maxPrecision, int maxScale) {
if (dec == null) {
return null;
}
// Minor optimization, avoiding creating new objects.
if (dec.precision() - dec.scale() <= maxPrecision - maxScale &&
dec.scale() <= maxScale) {
return dec;
}
BigDecimal bd = enforcePrecisionScale(dec.bd, maxPrecision, maxScale);
if (bd == null) {
return null;
}
return HiveDecimalV1.create(bd);
}
@HiveDecimalVersionV1
public long longValueExact() {
return bd.longValueExact();
}
}