Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading