org.dinky.shaded.paimon.data.Decimal Maven / Gradle / Ivy
/*
* 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.dinky.shaded.paimon.data;
import org.dinky.shaded.paimon.annotation.Public;
import org.dinky.shaded.paimon.types.DecimalType;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import static org.dinky.shaded.paimon.utils.Preconditions.checkArgument;
/**
* An internal data structure representing data of {@link DecimalType}.
*
* This data structure is immutable and might store decimal values in a compact representation
* (as a long value) if values are small enough.
*
* @since 0.4.0
*/
@Public
public final class Decimal implements Comparable, Serializable {
private static final long serialVersionUID = 1L;
// member fields and static fields are package-visible,
// in order to be accessible for DecimalUtils
static final int MAX_COMPACT_PRECISION = 18;
/** Maximum number of decimal digits a Long can represent. (1e18 < Long.MaxValue < 1e19) */
static final int MAX_LONG_DIGITS = 18;
static final long[] POW10 = new long[MAX_COMPACT_PRECISION + 1];
static {
POW10[0] = 1;
for (int i = 1; i < POW10.length; i++) {
POW10[i] = 10 * POW10[i - 1];
}
}
// The semantics of the fields are as follows:
// - `precision` and `scale` represent the precision and scale of SQL decimal type
// - If `decimalVal` is set, it represents the whole decimal value
// - Otherwise, the decimal value is longVal/(10^scale).
//
// Note that the (precision, scale) must be correct.
// if precision > MAX_COMPACT_PRECISION,
// `decimalVal` represents the value. `longVal` is undefined
// otherwise, (longVal, scale) represents the value
// `decimalVal` may be set and cached
final int precision;
final int scale;
final long longVal;
BigDecimal decimalVal;
// this constructor does not perform any sanity check.
Decimal(int precision, int scale, long longVal, BigDecimal decimalVal) {
this.precision = precision;
this.scale = scale;
this.longVal = longVal;
this.decimalVal = decimalVal;
}
// ------------------------------------------------------------------------------------------
// Public Interfaces
// ------------------------------------------------------------------------------------------
/**
* Returns the precision of this {@link Decimal}.
*
* The precision is the number of digits in the unscaled value.
*/
public int precision() {
return precision;
}
/** Returns the scale of this {@link Decimal}. */
public int scale() {
return scale;
}
/** Converts this {@link Decimal} into an instance of {@link BigDecimal}. */
public BigDecimal toBigDecimal() {
BigDecimal bd = decimalVal;
if (bd == null) {
decimalVal = bd = BigDecimal.valueOf(longVal, scale);
}
return bd;
}
/**
* Returns a long describing the unscaled value of this {@link Decimal}.
*
* @throws ArithmeticException if this {@link Decimal} does not exactly fit in a long.
*/
public long toUnscaledLong() {
if (isCompact()) {
return longVal;
} else {
return toBigDecimal().unscaledValue().longValueExact();
}
}
/**
* Returns a byte array describing the unscaled value of this {@link Decimal}.
*
* @return the unscaled byte array of this {@link Decimal}.
*/
public byte[] toUnscaledBytes() {
return toBigDecimal().unscaledValue().toByteArray();
}
/** Returns whether the decimal value is small enough to be stored in a long. */
public boolean isCompact() {
return precision <= MAX_COMPACT_PRECISION;
}
/** Returns a copy of this {@link Decimal} object. */
public Decimal copy() {
return new Decimal(precision, scale, longVal, decimalVal);
}
@Override
public int hashCode() {
return toBigDecimal().hashCode();
}
@Override
public int compareTo(@Nonnull Decimal that) {
if (this.isCompact() && that.isCompact() && this.scale == that.scale) {
return Long.compare(this.longVal, that.longVal);
}
return this.toBigDecimal().compareTo(that.toBigDecimal());
}
@Override
public boolean equals(final Object o) {
if (!(o instanceof Decimal)) {
return false;
}
Decimal that = (Decimal) o;
return this.compareTo(that) == 0;
}
@Override
public String toString() {
return toBigDecimal().toPlainString();
}
// ------------------------------------------------------------------------------------------
// Constructor Utilities
// ------------------------------------------------------------------------------------------
/**
* Creates an instance of {@link Decimal} from a {@link BigDecimal} and the given precision and
* scale.
*
*
The returned decimal value may be rounded to have the desired scale. The precision will be
* checked. If the precision overflows, null will be returned.
*/
public static @Nullable Decimal fromBigDecimal(BigDecimal bd, int precision, int scale) {
bd = bd.setScale(scale, RoundingMode.HALF_UP);
if (bd.precision() > precision) {
return null;
}
long longVal = -1;
if (precision <= MAX_COMPACT_PRECISION) {
longVal = bd.movePointRight(scale).longValueExact();
}
return new Decimal(precision, scale, longVal, bd);
}
/**
* Creates an instance of {@link Decimal} from an unscaled long value and the given precision
* and scale.
*/
public static Decimal fromUnscaledLong(long unscaledLong, int precision, int scale) {
checkArgument(precision > 0 && precision <= MAX_LONG_DIGITS);
return new Decimal(precision, scale, unscaledLong, null);
}
/**
* Creates an instance of {@link Decimal} from an unscaled byte array value and the given
* precision and scale.
*/
public static Decimal fromUnscaledBytes(byte[] unscaledBytes, int precision, int scale) {
BigDecimal bd = new BigDecimal(new BigInteger(unscaledBytes), scale);
return fromBigDecimal(bd, precision, scale);
}
/**
* Creates an instance of {@link Decimal} for a zero value with the given precision and scale.
*
*
The precision will be checked. If the precision overflows, null will be returned.
*/
public static @Nullable Decimal zero(int precision, int scale) {
if (precision <= MAX_COMPACT_PRECISION) {
return new Decimal(precision, scale, 0, null);
} else {
return fromBigDecimal(BigDecimal.ZERO, precision, scale);
}
}
// ------------------------------------------------------------------------------------------
// Utilities
// ------------------------------------------------------------------------------------------
/** Returns whether the decimal value is small enough to be stored in a long. */
public static boolean isCompact(int precision) {
return precision <= MAX_COMPACT_PRECISION;
}
}