Skip to content

Commit 407f054

Browse files
authored
Merge pull request #354 from alex268/master
Added support of special +Inf/-Inf values in non default decimal types
2 parents 10219d0 + 279a641 commit 407f054

File tree

4 files changed

+291
-73
lines changed

4 files changed

+291
-73
lines changed

table/src/main/java/tech/ydb/table/values/DecimalType.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,49 @@ public class DecimalType implements Type {
1616

1717
public static final int MAX_PRECISION = 35;
1818

19-
private static final DecimalType YDB_DEFAULT = DecimalType.of(22, 9);
19+
private static final InfValues[] INF_VALUES;
20+
private static final DecimalType YDB_DEFAULT;
21+
22+
static {
23+
// Precalculate +inf/-inf values for all precisions
24+
INF_VALUES = new InfValues[DecimalType.MAX_PRECISION];
25+
26+
long mask32 = 0xFFFFFFFFL;
27+
long infHigh = 0;
28+
long infLow = 1;
29+
30+
for (int precision = 1; precision <= DecimalType.MAX_PRECISION; precision++) {
31+
// multiply by 10
32+
long ll = 10 * (infLow & mask32);
33+
long lh = 10 * (infLow >>> 32) + (ll >>> 32);
34+
long hl = 10 * (infHigh & mask32) + (lh >>> 32);
35+
long hh = 10 * (infHigh >>> 32) + (hl >>> 32);
36+
37+
infLow = (lh << 32) + (ll & mask32);
38+
infHigh = (hh << 32) + (hl & mask32);
39+
40+
INF_VALUES[precision - 1] = new InfValues(infHigh, infLow);
41+
}
42+
43+
YDB_DEFAULT = DecimalType.of(22, 9);
44+
}
2045

2146
private final int precision;
2247
private final int scale;
48+
private final InfValues inf;
49+
50+
private final DecimalValue infValue;
51+
private final DecimalValue negInfValue;
52+
private final DecimalValue nanValue;
2353

2454
private DecimalType(int precision, int scale) {
2555
this.precision = precision;
2656
this.scale = scale;
57+
this.inf = INF_VALUES[precision - 1];
58+
59+
this.infValue = new DecimalValue(this, DecimalValue.INF_HIGH, DecimalValue.INF_LOW);
60+
this.negInfValue = new DecimalValue(this, DecimalValue.NEG_INF_HIGH, DecimalValue.NEG_INF_LOW);
61+
this.nanValue = new DecimalValue(this, DecimalValue.NAN_HIGH, DecimalValue.NAN_LOW);
2762
}
2863

2964
public static DecimalType getDefault() {
@@ -55,6 +90,18 @@ public int getScale() {
5590
return scale;
5691
}
5792

93+
public DecimalValue getInf() {
94+
return infValue;
95+
}
96+
97+
public DecimalValue getNegInf() {
98+
return negInfValue;
99+
}
100+
101+
public DecimalValue getNaN() {
102+
return nanValue;
103+
}
104+
58105
@Override
59106
public boolean equals(Object o) {
60107
if (this == o) {
@@ -115,4 +162,26 @@ public DecimalValue newValueUnscaled(BigInteger value) {
115162
public DecimalValue newValue(String value) {
116163
return DecimalValue.fromString(this, value);
117164
}
165+
166+
boolean isInf(long high, long low) {
167+
return high > inf.posHigh || (high == inf.posHigh && Long.compareUnsigned(low, inf.posLow) >= 0);
168+
}
169+
170+
boolean isNegInf(long high, long low) {
171+
return high < inf.negHigh || (high == inf.negHigh && Long.compareUnsigned(low, inf.negLow) <= 0);
172+
}
173+
174+
private static class InfValues {
175+
private final long posHigh;
176+
private final long posLow;
177+
private final long negHigh;
178+
private final long negLow;
179+
180+
InfValues(long infHigh, long infLow) {
181+
this.posHigh = infHigh;
182+
this.posLow = infLow;
183+
this.negHigh = 0xFFFFFFFFFFFFFFFFL - infHigh;
184+
this.negLow = 0xFFFFFFFFFFFFFFFFL - infLow + 1;
185+
}
186+
}
118187
}

table/src/main/java/tech/ydb/table/values/DecimalValue.java

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,38 @@ public class DecimalValue implements Value<DecimalType> {
1818

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

21+
static final long INF_HIGH = 0x0013426172C74D82L;
22+
static final long INF_LOW = 0x2B878FE800000000L;
23+
static final long NEG_INF_HIGH = 0xFFECBD9E8D38B27DL;
24+
static final long NEG_INF_LOW = 0xD478701800000000L;
25+
static final long NAN_HIGH = 0x0013426172C74D82L;
26+
static final long NAN_LOW = 0x2B878FE800000001L;
27+
28+
2129
/**
22-
* Positive infinity 10^{@value DecimalType#MAX_PRECISION}.
30+
* @deprecated
31+
* Positive infinity 10^MAX_PRECISION.
32+
* Use {@link DecimalType#getInf() } instead
2333
*/
34+
@Deprecated
2435
public static final DecimalValue INF = new DecimalValue(
2536
MAX_DECIMAL, 0x0013426172C74D82L, 0x2B878FE800000000L);
2637

2738
/**
28-
* Negative infinity -10^{@value DecimalType#MAX_PRECISION}.
39+
* @deprecated
40+
* Negative infinity -10^MAX_PRECISI0ON.
41+
* Use {@link DecimalType#getNegInf() } instead
2942
*/
43+
@Deprecated
3044
public static final DecimalValue NEG_INF = new DecimalValue(
3145
MAX_DECIMAL, 0xFFECBD9E8D38B27DL, 0xD478701800000000L);
3246

3347
/**
34-
* Not a number 10^{@value DecimalType#MAX_PRECISION} + 1.
48+
* @deprecated
49+
* Not a number 10^MAX_PRECISION + 1.
50+
* Use {@link DecimalType#getNaN() } instead
3551
*/
52+
@Deprecated
3653
public static final DecimalValue NAN = new DecimalValue(
3754
MAX_DECIMAL, 0x0013426172C74D82L, 0x2B878FE800000001L);
3855

@@ -60,15 +77,15 @@ public long getLow() {
6077
}
6178

6279
public boolean isInf() {
63-
return this.high == INF.high && this.low == INF.low;
80+
return this.high == INF_HIGH && this.low == INF_LOW;
6481
}
6582

6683
public boolean isNegativeInf() {
67-
return this.high == NEG_INF.high && this.low == NEG_INF.low;
84+
return this.high == NEG_INF_HIGH && this.low == NEG_INF_LOW;
6885
}
6986

7087
public boolean isNan() {
71-
return this.high == NAN.high && this.low == NAN.low;
88+
return this.high == NAN_HIGH && this.low == NAN_LOW;
7289
}
7390

7491
public boolean isZero() {
@@ -123,6 +140,9 @@ public BigDecimal toBigDecimal() {
123140
if (isZero()) {
124141
return BigDecimal.ZERO.setScale(type.getScale());
125142
}
143+
if (isInf() || isNegativeInf() || isNan()) {
144+
return new BigDecimal(toUnscaledBigInteger()).setScale(type.getScale());
145+
}
126146

127147
return new BigDecimal(toUnscaledBigInteger(), type.getScale());
128148
}
@@ -276,31 +296,22 @@ private static boolean isNan(long high, long low) {
276296
return NAN.high == high && NAN.low == low;
277297
}
278298

279-
private static boolean isInf(long high, long low) {
280-
return high > INF.high || (high == INF.high && Long.compareUnsigned(low, INF.low) >= 0);
281-
}
282-
283-
private static boolean isNegInf(long high, long low) {
284-
return high < NEG_INF.high || (high == NEG_INF.high && Long.compareUnsigned(low, NEG_INF.low) <= 0);
285-
}
299+
static DecimalValue fromUnscaledLong(DecimalType type, long low) {
300+
if (low == 0) {
301+
return new DecimalValue(type, 0, 0);
302+
}
286303

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

291-
private static DecimalValue newInf(DecimalType type) {
292-
return new DecimalValue(type, INF.high, INF.low);
293-
}
306+
if (type.isInf(high, low)) {
307+
return type.getInf();
308+
}
294309

295-
private static DecimalValue newNegInf(DecimalType type) {
296-
return new DecimalValue(type, NEG_INF.high, NEG_INF.low);
297-
}
298-
static DecimalValue fromUnscaledLong(DecimalType type, long value) {
299-
if (value == 0) {
300-
return new DecimalValue(type, 0, 0);
310+
if (type.isNegInf(high, low)) {
311+
return type.getNegInf();
301312
}
302-
long high = value > 0 ? 0 : -1;
303-
return new DecimalValue(type, high, value);
313+
314+
return new DecimalValue(type, high, low);
304315
}
305316

306317
static DecimalValue fromBits(DecimalType type, long high, long low) {
@@ -309,15 +320,15 @@ static DecimalValue fromBits(DecimalType type, long high, long low) {
309320
}
310321

311322
if (isNan(high, low)) {
312-
return newNan(type);
323+
return type.getNaN();
313324
}
314325

315-
if (isInf(high, low)) {
316-
return newInf(type);
326+
if (type.isInf(high, low)) {
327+
return type.getInf();
317328
}
318329

319-
if (isNegInf(high, low)) {
320-
return newNegInf(type);
330+
if (type.isNegInf(high, low)) {
331+
return type.getNegInf();
321332
}
322333

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

332343
boolean negative = value.signum() < 0;
333344
if (bitLength > 128) {
334-
return negative ? newNegInf(type) : newInf(type);
345+
return negative ? type.getNegInf() : type.getInf();
335346
}
336347

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

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

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

423434
if ((c1 == 'n' || c1 == 'N') && (c2 == 'a' || c2 == 'A') || (c3 == 'n' || c3 == 'N')) {
424-
return new DecimalValue(type, NAN.high, NAN.low);
435+
return type.getNaN();
425436
}
426437
}
427438

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

516527
return DecimalValue.fromUnscaledBigInteger(type, rawValue);
517528
}
518-
519529
}

table/src/test/java/tech/ydb/table/integration/ValuesReadTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import tech.ydb.table.result.ValueReader;
1818
import tech.ydb.table.rpc.grpc.GrpcTableRpc;
1919
import tech.ydb.table.transaction.TxControl;
20+
import tech.ydb.table.values.DecimalType;
21+
import tech.ydb.table.values.DecimalValue;
2022
import tech.ydb.table.values.NullType;
2123
import tech.ydb.table.values.NullValue;
2224
import tech.ydb.table.values.PrimitiveType;
@@ -157,4 +159,68 @@ public void timestampReadTest() {
157159
Assert.assertEquals("Invalid value \"2106-01-01T00:00:00.000000Z\" for type Timestamp", issues[1].getMessage());
158160

159161
}
162+
163+
@Test
164+
public void decimalReadTest() {
165+
DataQueryResult result = CTX.supplyResult(
166+
s -> s.executeDataQuery("SELECT "
167+
+ "Decimal('9', 1, 0) AS d1, "
168+
+ "Decimal('-9', 1, 0) AS d2, "
169+
+ "Decimal('99999999999999999999999999999999999', 35, 0) AS d3, "
170+
+ "Decimal('-99999999999999999999999999999999999', 35, 0) AS d4, "
171+
+ "Decimal('9999999999999999999999999.9999999999', 35, 10) AS d5, "
172+
+ "Decimal('-9999999999999999999999999.9999999999', 35, 10) AS d6, "
173+
+ "Decimal('9.6', 1, 0) AS d7, "
174+
+ "Decimal('-9.6', 1, 0) AS d8, "
175+
+ "Decimal('99999999999999999999999999999999999.6', 35, 0) AS d9, "
176+
+ "Decimal('-99999999999999999999999999999999999.6', 35, 0) AS d10, "
177+
+ "Decimal('9999999999999999999999999.99999999996', 35, 10) AS d11, "
178+
+ "Decimal('-9999999999999999999999999.99999999996', 35, 10) AS d12;",
179+
TxControl.serializableRw()
180+
)
181+
).join().getValue();
182+
183+
Assert.assertEquals(1, result.getResultSetCount());
184+
185+
ResultSetReader rs = result.getResultSet(0);
186+
Assert.assertTrue(rs.next());
187+
188+
DecimalValue d1 = rs.getColumn("d1").getDecimal();
189+
DecimalValue d2 = rs.getColumn("d2").getDecimal();
190+
DecimalValue d3 = rs.getColumn("d3").getDecimal();
191+
DecimalValue d4 = rs.getColumn("d4").getDecimal();
192+
DecimalValue d5 = rs.getColumn("d5").getDecimal();
193+
DecimalValue d6 = rs.getColumn("d6").getDecimal();
194+
DecimalValue d7 = rs.getColumn("d7").getDecimal();
195+
DecimalValue d8 = rs.getColumn("d8").getDecimal();
196+
DecimalValue d9 = rs.getColumn("d9").getDecimal();
197+
DecimalValue d10 = rs.getColumn("d10").getDecimal();
198+
DecimalValue d11 = rs.getColumn("d11").getDecimal();
199+
DecimalValue d12 = rs.getColumn("d12").getDecimal();
200+
201+
Assert.assertEquals(DecimalType.of(1).newValue(9), d1);
202+
Assert.assertEquals(DecimalType.of(1).newValue(-9), d2);
203+
Assert.assertEquals(DecimalType.of(35).newValue("99999999999999999999999999999999999"), d3);
204+
Assert.assertEquals(DecimalType.of(35).newValue("-99999999999999999999999999999999999"), d4);
205+
Assert.assertEquals(DecimalType.of(35, 10).newValue("9999999999999999999999999.9999999999"), d5);
206+
Assert.assertEquals(DecimalType.of(35, 10).newValue("-9999999999999999999999999.9999999999"), d6);
207+
208+
Assert.assertEquals(DecimalType.of(1).getInf(), d7);
209+
Assert.assertEquals(DecimalType.of(1).getNegInf(), d8);
210+
Assert.assertEquals(DecimalType.of(35).getInf(), d9);
211+
Assert.assertEquals(DecimalType.of(35).getNegInf(), d10);
212+
Assert.assertEquals(DecimalType.of(35, 10).getInf(), d11);
213+
Assert.assertEquals(DecimalType.of(35, 10).getNegInf(), d12);
214+
215+
// All infinity values have the same high & low parts
216+
Assert.assertEquals(d7.getHigh(), d9.getHigh());
217+
Assert.assertEquals(d7.getHigh(), d11.getHigh());
218+
Assert.assertEquals(d7.getLow(), d9.getLow());
219+
Assert.assertEquals(d7.getLow(), d11.getLow());
220+
221+
Assert.assertEquals(d8.getHigh(), d10.getHigh());
222+
Assert.assertEquals(d8.getHigh(), d12.getHigh());
223+
Assert.assertEquals(d8.getLow(), d10.getLow());
224+
Assert.assertEquals(d8.getLow(), d12.getLow());
225+
}
160226
}

0 commit comments

Comments
 (0)