org.apache.flink.cdc.common.data.DecimalData 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.apache.flink.cdc.common.data;
import org.apache.flink.cdc.common.annotation.PublicEvolving;
import org.apache.flink.cdc.common.types.DecimalType;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import static org.apache.flink.cdc.common.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.
*/
@PublicEvolving
public final class DecimalData implements Comparable {
static final int MAX_COMPACT_PRECISION = 18;
/** Maximum number of decimal digits an Int can represent. (1e9 < Int.MaxValue < 1e10) */
static final int MAX_INT_DIGITS = 9;
/** 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.
DecimalData(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 DecimalData}.
*
* The precision is the number of digits in the unscaled value.
*/
public int precision() {
return precision;
}
/** Returns the scale of this {@link DecimalData}. */
public int scale() {
return scale;
}
/** Converts this {@link DecimalData} 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 DecimalData}.
*
* @throws ArithmeticException if this {@link DecimalData} 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 DecimalData}.
*
* @return the unscaled byte array of this {@link DecimalData}.
*/
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 DecimalData} object. */
public DecimalData copy() {
return new DecimalData(precision, scale, longVal, decimalVal);
}
@Override
public int hashCode() {
return toBigDecimal().hashCode();
}
@Override
public int compareTo(@Nonnull DecimalData 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 DecimalData)) {
return false;
}
DecimalData that = (DecimalData) o;
return this.compareTo(that) == 0;
}
@Override
public String toString() {
return toBigDecimal().toPlainString();
}
// ------------------------------------------------------------------------------------------
// Constructor Utilities
// ------------------------------------------------------------------------------------------
/**
* Creates an instance of {@link DecimalData} 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 DecimalData 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 DecimalData(precision, scale, longVal, bd);
}
/**
* Creates an instance of {@link DecimalData} from an unscaled long value and the given
* precision and scale.
*/
public static DecimalData fromUnscaledLong(long unscaledLong, int precision, int scale) {
checkArgument(precision > 0 && precision <= MAX_LONG_DIGITS);
return new DecimalData(precision, scale, unscaledLong, null);
}
/**
* Creates an instance of {@link DecimalData} from an unscaled byte array value and the given
* precision and scale.
*/
public static DecimalData 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 DecimalData} 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 DecimalData zero(int precision, int scale) {
if (precision <= MAX_COMPACT_PRECISION) {
return new DecimalData(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;
}
}