Skip to content

Commit 7f640bc

Browse files
committed
GH-4950 LMDB: Encode doubles with a maximum number of 63 bits to avoid negative ids.
1 parent 2fb2c30 commit 7f640bc

4 files changed

Lines changed: 40 additions & 42 deletions

File tree

core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Varint.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,7 @@ public static void writeUnsigned(final ByteBuffer bb, final long value) {
133133
}
134134

135135
if (value < 0) {
136-
int bytes = descriptor(value) + 1;
137-
bb.put((byte) (250 + (bytes - 3)));
138-
writeSignificantBits(bb, value, bytes);
136+
throw new IllegalArgumentException("Negative value can not be encoded as varint: " + value);
139137
} else if (value <= 240) {
140138
bb.put((byte) value);
141139
} else if (value <= 2287) {
@@ -221,13 +219,12 @@ private static void writeSignificantBits(ByteBuffer bb, long value, int bytes) {
221219
/**
222220
* Calculates required length in bytes to encode the given long value using variable-length encoding.
223221
*
224-
* @param value the value value
222+
* @param value the value
225223
* @return length in bytes
226224
*/
227225
public static int calcLengthUnsigned(long value) {
228226
if (value < 0) {
229-
int bytes = descriptor(value) + 1;
230-
return 1 + bytes;
227+
throw new IllegalArgumentException("Negative value can not be encoded as varint: " + value);
231228
} else if (value <= 240) {
232229
return 1;
233230
} else if (value <= 2287) {

core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/inlined/Decimals.java

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ public class Decimals {
2525
static final BigInteger MIN_DECIMAL_VALUE = BigInteger.valueOf(-(1L << (DECIMAL_VALUE_BITS - 1)));
2626
static final int MAX_DECIMAL_SCALE = 2 ^ (DECIMAL_SCALE_BITS - 1) - 1;
2727
static final int MIN_DECIMAL_SCALE = -2 ^ (DECIMAL_SCALE_BITS - 1);
28+
static final int DOUBLE_EXPONENT_BITS = 9;
29+
private static final int DOUBLE_EXPONENT_ZERO_OR_SUBNORMAL = 0;
30+
private static final int DOUBLE_EXPONENT_INF_OR_NAN = (1 << DOUBLE_EXPONENT_BITS) - 1;
31+
private static final int DOUBLE_EXPONENT_BIAS = (1 << (DOUBLE_EXPONENT_BITS - 1)) - 1;
32+
private static final int DOUBLE_EXPONENT_MIN_NORMAL = -DOUBLE_EXPONENT_BIAS + 1;
33+
private static final int DOUBLE_EXPONENT_MAX_NORMAL = DOUBLE_EXPONENT_BIAS;
2834

2935
/**
3036
* Encodes a {@link BigDecimal} in 56 bits [48 bits value, 8 bits scale].
@@ -46,66 +52,60 @@ static long packDecimal(BigDecimal value) {
4652
}
4753

4854
/**
49-
* Extracts the exponent of a double, unbiased, and encodes it into 10 bits if possible. Handles special cases: NaN
50-
* and Infinity.
55+
* Encodes a double exponent into 9 bits if possible. Handles special cases for zero/subnormal and NaN/Infinity.
5156
*
5257
* @param exponent11 The original 11-bit exponent.
53-
* @return Encoded 10-bit exponent as int (0-1023), or -1 if not encodable.
58+
* @return Encoded 9-bit exponent as int (0-511), or -1 if not encodable.
5459
*/
55-
public static int encodeExponent10Bits(int exponent11) {
56-
// isNaN or Inf - we do not distinguish between them in the compact representation, but reserve a special
57-
// pattern for both
60+
public static int encodeExponent9Bits(int exponent11) {
5861
if (exponent11 == 0x7FF) {
59-
// Reserve special pattern, e.g., 0x3FF (all 10 bits set) for NaN/Inf
60-
return 0x3FF;
62+
return DOUBLE_EXPONENT_INF_OR_NAN;
6163
}
6264

6365
if (exponent11 == 0) {
64-
// Subnormal number or zero, exponent = -1022
65-
return 0; // Use 0 for subnormal/zero
66+
return DOUBLE_EXPONENT_ZERO_OR_SUBNORMAL;
6667
}
6768

68-
// Normal number, unbiased exponent in [-1022, 1023]
6969
int unbiasedExp = exponent11 - 1023;
70-
int encoded = unbiasedExp + 511; // Shift range to [0, 1023]
71-
if (encoded < 1 || encoded > 1022) {
72-
// Out of range for 10 bits (excluding reserved 0 and 0x3FF)
70+
if (unbiasedExp < DOUBLE_EXPONENT_MIN_NORMAL || unbiasedExp > DOUBLE_EXPONENT_MAX_NORMAL) {
7371
return -1;
7472
}
75-
return encoded;
73+
return unbiasedExp + DOUBLE_EXPONENT_BIAS;
7674
}
7775

7876
/**
79-
* Decodes a 10-bit encoded exponent back to unbiased exponent.
77+
* Decodes a 9-bit exponent back to the original 11-bit exponent.
8078
*
81-
* @param encoded 10-bit encoded exponent
79+
* @param encoded 9-bit encoded exponent
8280
* @return 11-bit biased exponent or special values for reserved patterns
8381
*/
84-
public static int decodeExponent10Bits(int encoded) {
85-
if (encoded == 0) {
86-
// Subnormal/zero
87-
return 0; // -1022;
82+
public static int decodeExponent9Bits(int encoded) {
83+
if (encoded == DOUBLE_EXPONENT_ZERO_OR_SUBNORMAL) {
84+
return 0;
8885
}
89-
if (encoded == 0x3FF) {
90-
// Reserved for NaN/Inf
86+
if (encoded == DOUBLE_EXPONENT_INF_OR_NAN) {
9187
return 0x7FF;
9288
}
93-
// Normal
94-
int unbiased = encoded - 511;
89+
int unbiased = encoded - DOUBLE_EXPONENT_BIAS;
9590
return unbiased + 1023;
9691
}
9792

93+
/**
94+
* @deprecated Use {@link #decodeExponent9Bits(int)}.
95+
*/
96+
@Deprecated
97+
public static int decodeExponent10Bits(int encoded) {
98+
return decodeExponent9Bits(encoded);
99+
}
100+
98101
static long packDouble(double value) {
99102
long valueBits = Double.doubleToRawLongBits(value);
100-
// 11-bit exponent
101103
int exponent11 = (int) ((valueBits >>> 52) & 0x7FF);
102-
// encode to 10 bits
103-
int exponent10 = encodeExponent10Bits(exponent11);
104-
if (exponent10 >= 0) {
105-
// encoding of exponent was possible
104+
int exponent9 = encodeExponent9Bits(exponent11);
105+
if (exponent9 >= 0) {
106106
int sign = value < 0 ? 1 : 0;
107107
long mantissa = valueBits & 0x000fffffffffffffL;
108-
return ((long) exponent10) << 54 | mantissa << 2 | sign << 1 | 1;
108+
return ((long) exponent9) << 54 | mantissa << 2 | sign << 1 | 1;
109109
}
110110
return 0L;
111111
}
@@ -127,12 +127,10 @@ static Literal unpackDouble(long value, ValueFactory valueFactory) {
127127
}
128128
int sign = (int) ((value >> 1) & 1);
129129
long mantissa = (value >> 2) & 0x000fffffffffffffL;
130-
int exponent10 = (int) (value >>> 54);
130+
int exponent9 = (int) ((value >>> 54) & DOUBLE_EXPONENT_INF_OR_NAN);
131131

132-
// Decode back to original exponent
133-
int exponent11 = decodeExponent10Bits(exponent10);
132+
int exponent11 = decodeExponent9Bits(exponent9);
134133

135-
// Reconstruct raw bits
136134
long valueBits = ((long) sign << 63) |
137135
((long) (exponent11 & 0x7FF) << 52) |
138136
mantissa;

core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/inlined/Strings.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ static long packString(Literal literal) {
2828
byte[] bytes = label.getBytes(StandardCharsets.UTF_8);
2929
int maxLength = Values.MAX_LENGTH - 1;
3030
if (bytes.length > maxLength) {
31-
// multi-byte string is longer than maximum encodable length
31+
// multibyte string is longer than maximum encodable length
3232
return 0L;
3333
}
3434

core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/inlined/DecimalsTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
package org.eclipse.rdf4j.sail.lmdb.inlined;
1212

1313
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertFalse;
1415
import static org.junit.jupiter.api.Assertions.assertNotEquals;
1516
import static org.junit.jupiter.api.Assertions.assertTrue;
1617

@@ -70,6 +71,7 @@ void testPackDouble() {
7071

7172
for (double value : values) {
7273
long packedValue = Decimals.packDouble(value);
74+
assertFalse(packedValue < 0, "Packed value should be non-negative for value: " + value);
7375
assertNotEquals(0L, packedValue, "Packing failed for value: " + value);
7476
Literal literal = Decimals.unpackDouble(packedValue, SimpleValueFactory.getInstance());
7577
if (Double.isNaN(value)) {
@@ -98,6 +100,7 @@ void testPackFloat() {
98100

99101
for (float value : values) {
100102
long packedValue = Decimals.packFloat(value);
103+
assertFalse(packedValue < 0, "Packed value should be non-negative for value: " + value);
101104
assertNotEquals(0L, packedValue, "Packing failed for value: " + value);
102105
Literal literal = Decimals.unpackFloat(packedValue, SimpleValueFactory.getInstance());
103106
if (Float.isNaN(value)) {

0 commit comments

Comments
 (0)