Skip to content

Commit

Permalink
Merge pull request #354 from alex268/master
Browse files Browse the repository at this point in the history
Added support of special +Inf/-Inf values in non default decimal types
  • Loading branch information
alex268 authored Dec 25, 2024
2 parents 10219d0 + 279a641 commit 407f054
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 73 deletions.
71 changes: 70 additions & 1 deletion table/src/main/java/tech/ydb/table/values/DecimalType.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,49 @@ public class DecimalType implements Type {

public static final int MAX_PRECISION = 35;

private static final DecimalType YDB_DEFAULT = DecimalType.of(22, 9);
private static final InfValues[] INF_VALUES;
private static final DecimalType YDB_DEFAULT;

static {
// Precalculate +inf/-inf values for all precisions
INF_VALUES = new InfValues[DecimalType.MAX_PRECISION];

long mask32 = 0xFFFFFFFFL;
long infHigh = 0;
long infLow = 1;

for (int precision = 1; precision <= DecimalType.MAX_PRECISION; precision++) {
// multiply by 10
long ll = 10 * (infLow & mask32);
long lh = 10 * (infLow >>> 32) + (ll >>> 32);
long hl = 10 * (infHigh & mask32) + (lh >>> 32);
long hh = 10 * (infHigh >>> 32) + (hl >>> 32);

infLow = (lh << 32) + (ll & mask32);
infHigh = (hh << 32) + (hl & mask32);

INF_VALUES[precision - 1] = new InfValues(infHigh, infLow);
}

YDB_DEFAULT = DecimalType.of(22, 9);
}

private final int precision;
private final int scale;
private final InfValues inf;

private final DecimalValue infValue;
private final DecimalValue negInfValue;
private final DecimalValue nanValue;

private DecimalType(int precision, int scale) {
this.precision = precision;
this.scale = scale;
this.inf = INF_VALUES[precision - 1];

this.infValue = new DecimalValue(this, DecimalValue.INF_HIGH, DecimalValue.INF_LOW);
this.negInfValue = new DecimalValue(this, DecimalValue.NEG_INF_HIGH, DecimalValue.NEG_INF_LOW);
this.nanValue = new DecimalValue(this, DecimalValue.NAN_HIGH, DecimalValue.NAN_LOW);
}

public static DecimalType getDefault() {
Expand Down Expand Up @@ -55,6 +90,18 @@ public int getScale() {
return scale;
}

public DecimalValue getInf() {
return infValue;
}

public DecimalValue getNegInf() {
return negInfValue;
}

public DecimalValue getNaN() {
return nanValue;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down Expand Up @@ -115,4 +162,26 @@ public DecimalValue newValueUnscaled(BigInteger value) {
public DecimalValue newValue(String value) {
return DecimalValue.fromString(this, value);
}

boolean isInf(long high, long low) {
return high > inf.posHigh || (high == inf.posHigh && Long.compareUnsigned(low, inf.posLow) >= 0);
}

boolean isNegInf(long high, long low) {
return high < inf.negHigh || (high == inf.negHigh && Long.compareUnsigned(low, inf.negLow) <= 0);
}

private static class InfValues {
private final long posHigh;
private final long posLow;
private final long negHigh;
private final long negLow;

InfValues(long infHigh, long infLow) {
this.posHigh = infHigh;
this.posLow = infLow;
this.negHigh = 0xFFFFFFFFFFFFFFFFL - infHigh;
this.negLow = 0xFFFFFFFFFFFFFFFFL - infLow + 1;
}
}
}
84 changes: 47 additions & 37 deletions table/src/main/java/tech/ydb/table/values/DecimalValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,38 @@ public class DecimalValue implements Value<DecimalType> {

private static final BigInteger BIGINT_TWO = BigInteger.valueOf(2);

static final long INF_HIGH = 0x0013426172C74D82L;
static final long INF_LOW = 0x2B878FE800000000L;
static final long NEG_INF_HIGH = 0xFFECBD9E8D38B27DL;
static final long NEG_INF_LOW = 0xD478701800000000L;
static final long NAN_HIGH = 0x0013426172C74D82L;
static final long NAN_LOW = 0x2B878FE800000001L;


/**
* Positive infinity 10^{@value DecimalType#MAX_PRECISION}.
* @deprecated
* Positive infinity 10^MAX_PRECISION.
* Use {@link DecimalType#getInf() } instead
*/
@Deprecated
public static final DecimalValue INF = new DecimalValue(
MAX_DECIMAL, 0x0013426172C74D82L, 0x2B878FE800000000L);

/**
* Negative infinity -10^{@value DecimalType#MAX_PRECISION}.
* @deprecated
* Negative infinity -10^MAX_PRECISI0ON.
* Use {@link DecimalType#getNegInf() } instead
*/
@Deprecated
public static final DecimalValue NEG_INF = new DecimalValue(
MAX_DECIMAL, 0xFFECBD9E8D38B27DL, 0xD478701800000000L);

/**
* Not a number 10^{@value DecimalType#MAX_PRECISION} + 1.
* @deprecated
* Not a number 10^MAX_PRECISION + 1.
* Use {@link DecimalType#getNaN() } instead
*/
@Deprecated
public static final DecimalValue NAN = new DecimalValue(
MAX_DECIMAL, 0x0013426172C74D82L, 0x2B878FE800000001L);

Expand Down Expand Up @@ -60,15 +77,15 @@ public long getLow() {
}

public boolean isInf() {
return this.high == INF.high && this.low == INF.low;
return this.high == INF_HIGH && this.low == INF_LOW;
}

public boolean isNegativeInf() {
return this.high == NEG_INF.high && this.low == NEG_INF.low;
return this.high == NEG_INF_HIGH && this.low == NEG_INF_LOW;
}

public boolean isNan() {
return this.high == NAN.high && this.low == NAN.low;
return this.high == NAN_HIGH && this.low == NAN_LOW;
}

public boolean isZero() {
Expand Down Expand Up @@ -123,6 +140,9 @@ public BigDecimal toBigDecimal() {
if (isZero()) {
return BigDecimal.ZERO.setScale(type.getScale());
}
if (isInf() || isNegativeInf() || isNan()) {
return new BigDecimal(toUnscaledBigInteger()).setScale(type.getScale());
}

return new BigDecimal(toUnscaledBigInteger(), type.getScale());
}
Expand Down Expand Up @@ -276,31 +296,22 @@ private static boolean isNan(long high, long low) {
return NAN.high == high && NAN.low == low;
}

private static boolean isInf(long high, long low) {
return high > INF.high || (high == INF.high && Long.compareUnsigned(low, INF.low) >= 0);
}

private static boolean isNegInf(long high, long low) {
return high < NEG_INF.high || (high == NEG_INF.high && Long.compareUnsigned(low, NEG_INF.low) <= 0);
}
static DecimalValue fromUnscaledLong(DecimalType type, long low) {
if (low == 0) {
return new DecimalValue(type, 0, 0);
}

private static DecimalValue newNan(DecimalType type) {
return new DecimalValue(type, NAN.high, NAN.low);
}
long high = low > 0 ? 0 : -1;

private static DecimalValue newInf(DecimalType type) {
return new DecimalValue(type, INF.high, INF.low);
}
if (type.isInf(high, low)) {
return type.getInf();
}

private static DecimalValue newNegInf(DecimalType type) {
return new DecimalValue(type, NEG_INF.high, NEG_INF.low);
}
static DecimalValue fromUnscaledLong(DecimalType type, long value) {
if (value == 0) {
return new DecimalValue(type, 0, 0);
if (type.isNegInf(high, low)) {
return type.getNegInf();
}
long high = value > 0 ? 0 : -1;
return new DecimalValue(type, high, value);

return new DecimalValue(type, high, low);
}

static DecimalValue fromBits(DecimalType type, long high, long low) {
Expand All @@ -309,15 +320,15 @@ static DecimalValue fromBits(DecimalType type, long high, long low) {
}

if (isNan(high, low)) {
return newNan(type);
return type.getNaN();
}

if (isInf(high, low)) {
return newInf(type);
if (type.isInf(high, low)) {
return type.getInf();
}

if (isNegInf(high, low)) {
return newNegInf(type);
if (type.isNegInf(high, low)) {
return type.getNegInf();
}

return new DecimalValue(type, high, low);
Expand All @@ -331,7 +342,7 @@ static DecimalValue fromUnscaledBigInteger(DecimalType type, BigInteger value) {

boolean negative = value.signum() < 0;
if (bitLength > 128) {
return negative ? newNegInf(type) : newInf(type);
return negative ? type.getNegInf() : type.getInf();
}

byte[] buf = value.abs().toByteArray();
Expand Down Expand Up @@ -367,7 +378,7 @@ private static DecimalValue fromUnsignedLong(DecimalType type, boolean positive,
lowHi = lowHi & HALF_LONG_MASK;
if ((high & LONG_SIGN_BIT) != 0) {
// number is too big, return infinite
return positive ? newInf(type) : newNegInf(type);
return positive ? type.getInf() : type.getNegInf();
}
}

Expand Down Expand Up @@ -417,11 +428,11 @@ static DecimalValue fromString(DecimalType type, String value) {
char c3 = value.charAt(cursor + 2);

if ((c1 == 'i' || c1 == 'I') && (c2 == 'n' || c2 == 'N') || (c3 == 'f' || c3 == 'F')) {
return negative ? newNegInf(type) : newInf(type);
return negative ? type.getNegInf() : type.getInf();
}

if ((c1 == 'n' || c1 == 'N') && (c2 == 'a' || c2 == 'A') || (c3 == 'n' || c3 == 'N')) {
return new DecimalValue(type, NAN.high, NAN.low);
return type.getNaN();
}
}

Expand Down Expand Up @@ -515,5 +526,4 @@ static DecimalValue fromBigDecimal(DecimalType type, BigDecimal value) {

return DecimalValue.fromUnscaledBigInteger(type, rawValue);
}

}
66 changes: 66 additions & 0 deletions table/src/test/java/tech/ydb/table/integration/ValuesReadTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import tech.ydb.table.result.ValueReader;
import tech.ydb.table.rpc.grpc.GrpcTableRpc;
import tech.ydb.table.transaction.TxControl;
import tech.ydb.table.values.DecimalType;
import tech.ydb.table.values.DecimalValue;
import tech.ydb.table.values.NullType;
import tech.ydb.table.values.NullValue;
import tech.ydb.table.values.PrimitiveType;
Expand Down Expand Up @@ -157,4 +159,68 @@ public void timestampReadTest() {
Assert.assertEquals("Invalid value \"2106-01-01T00:00:00.000000Z\" for type Timestamp", issues[1].getMessage());

}

@Test
public void decimalReadTest() {
DataQueryResult result = CTX.supplyResult(
s -> s.executeDataQuery("SELECT "
+ "Decimal('9', 1, 0) AS d1, "
+ "Decimal('-9', 1, 0) AS d2, "
+ "Decimal('99999999999999999999999999999999999', 35, 0) AS d3, "
+ "Decimal('-99999999999999999999999999999999999', 35, 0) AS d4, "
+ "Decimal('9999999999999999999999999.9999999999', 35, 10) AS d5, "
+ "Decimal('-9999999999999999999999999.9999999999', 35, 10) AS d6, "
+ "Decimal('9.6', 1, 0) AS d7, "
+ "Decimal('-9.6', 1, 0) AS d8, "
+ "Decimal('99999999999999999999999999999999999.6', 35, 0) AS d9, "
+ "Decimal('-99999999999999999999999999999999999.6', 35, 0) AS d10, "
+ "Decimal('9999999999999999999999999.99999999996', 35, 10) AS d11, "
+ "Decimal('-9999999999999999999999999.99999999996', 35, 10) AS d12;",
TxControl.serializableRw()
)
).join().getValue();

Assert.assertEquals(1, result.getResultSetCount());

ResultSetReader rs = result.getResultSet(0);
Assert.assertTrue(rs.next());

DecimalValue d1 = rs.getColumn("d1").getDecimal();
DecimalValue d2 = rs.getColumn("d2").getDecimal();
DecimalValue d3 = rs.getColumn("d3").getDecimal();
DecimalValue d4 = rs.getColumn("d4").getDecimal();
DecimalValue d5 = rs.getColumn("d5").getDecimal();
DecimalValue d6 = rs.getColumn("d6").getDecimal();
DecimalValue d7 = rs.getColumn("d7").getDecimal();
DecimalValue d8 = rs.getColumn("d8").getDecimal();
DecimalValue d9 = rs.getColumn("d9").getDecimal();
DecimalValue d10 = rs.getColumn("d10").getDecimal();
DecimalValue d11 = rs.getColumn("d11").getDecimal();
DecimalValue d12 = rs.getColumn("d12").getDecimal();

Assert.assertEquals(DecimalType.of(1).newValue(9), d1);
Assert.assertEquals(DecimalType.of(1).newValue(-9), d2);
Assert.assertEquals(DecimalType.of(35).newValue("99999999999999999999999999999999999"), d3);
Assert.assertEquals(DecimalType.of(35).newValue("-99999999999999999999999999999999999"), d4);
Assert.assertEquals(DecimalType.of(35, 10).newValue("9999999999999999999999999.9999999999"), d5);
Assert.assertEquals(DecimalType.of(35, 10).newValue("-9999999999999999999999999.9999999999"), d6);

Assert.assertEquals(DecimalType.of(1).getInf(), d7);
Assert.assertEquals(DecimalType.of(1).getNegInf(), d8);
Assert.assertEquals(DecimalType.of(35).getInf(), d9);
Assert.assertEquals(DecimalType.of(35).getNegInf(), d10);
Assert.assertEquals(DecimalType.of(35, 10).getInf(), d11);
Assert.assertEquals(DecimalType.of(35, 10).getNegInf(), d12);

// All infinity values have the same high & low parts
Assert.assertEquals(d7.getHigh(), d9.getHigh());
Assert.assertEquals(d7.getHigh(), d11.getHigh());
Assert.assertEquals(d7.getLow(), d9.getLow());
Assert.assertEquals(d7.getLow(), d11.getLow());

Assert.assertEquals(d8.getHigh(), d10.getHigh());
Assert.assertEquals(d8.getHigh(), d12.getHigh());
Assert.assertEquals(d8.getLow(), d10.getLow());
Assert.assertEquals(d8.getLow(), d12.getLow());
}
}
Loading

0 comments on commit 407f054

Please sign in to comment.