diff options
author | Brian S. O'Neill <bronee@gmail.com> | 2008-11-03 02:42:40 +0000 |
---|---|---|
committer | Brian S. O'Neill <bronee@gmail.com> | 2008-11-03 02:42:40 +0000 |
commit | d91f8b64e31e912378ec2396908ee4801514a9fa (patch) | |
tree | 07b2b552b5d565481a5a6bb3ed1d38816a66855c /src/main | |
parent | b477ed85f86ec4ffe541dc026b3f573745ddda9d (diff) |
Add back support for BigDecimal now that key encoding works correctly.
Diffstat (limited to 'src/main')
6 files changed, 286 insertions, 227 deletions
diff --git a/src/main/java/com/amazon/carbonado/raw/DataDecoder.java b/src/main/java/com/amazon/carbonado/raw/DataDecoder.java index ce5b7c9..407df0c 100644 --- a/src/main/java/com/amazon/carbonado/raw/DataDecoder.java +++ b/src/main/java/com/amazon/carbonado/raw/DataDecoder.java @@ -400,7 +400,7 @@ public class DataDecoder { * @return amount of bytes read from source
* @throws CorruptEncodingException if source data is corrupt
* @since 1.2
- * /
+ */
public static int decode(byte[] src, int srcOffset, BigDecimal[] valueRef)
throws CorruptEncodingException
{
@@ -446,7 +446,6 @@ public class DataDecoder { throw new CorruptEncodingException(null, e);
}
}
- */
/**
* Decodes the given byte array.
diff --git a/src/main/java/com/amazon/carbonado/raw/DataEncoder.java b/src/main/java/com/amazon/carbonado/raw/DataEncoder.java index 990b7e7..4b6cab4 100644 --- a/src/main/java/com/amazon/carbonado/raw/DataEncoder.java +++ b/src/main/java/com/amazon/carbonado/raw/DataEncoder.java @@ -362,7 +362,7 @@ public class DataEncoder { * @param dstOffset offset into destination array
* @return amount of bytes written
* @since 1.2
- * /
+ */
public static int encode(BigDecimal value, byte[] dst, int dstOffset) {
if (value == null) {
dst[dstOffset] = NULL_BYTE_HIGH;
@@ -378,14 +378,13 @@ public class DataEncoder { * @param value BigDecimal value to encode, may be null
* @return amount of bytes needed to encode
* @since 1.2
- * /
+ */
public static int calculateEncodedLength(BigDecimal value) {
if (value == null) {
return 1;
}
return signedVarIntLength(value.scale()) + calculateEncodedLength(value.unscaledValue());
}
- */
/**
* Encodes the given optional byte array into a variable amount of
diff --git a/src/main/java/com/amazon/carbonado/raw/EncodingConstants.java b/src/main/java/com/amazon/carbonado/raw/EncodingConstants.java index 997e491..5defdc2 100644 --- a/src/main/java/com/amazon/carbonado/raw/EncodingConstants.java +++ b/src/main/java/com/amazon/carbonado/raw/EncodingConstants.java @@ -18,6 +18,8 @@ package com.amazon.carbonado.raw;
+import java.math.BigInteger;
+
/**
*
*
@@ -40,4 +42,7 @@ class EncodingConstants { /** Byte to terminate variable data encoded for ascending order */
static final byte TERMINATOR = (byte)1;
+
+ static final BigInteger ONE_HUNDRED = BigInteger.valueOf(100);
+ static final BigInteger ONE_THOUSAND = BigInteger.valueOf(1000);
}
diff --git a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java index c68f06b..c5417c4 100644 --- a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java +++ b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java @@ -383,8 +383,8 @@ public class GenericEncodingStrategy<S extends Storable> { Class clazz = propertyType.toClass();
if (clazz != null) {
return Lob.class.isAssignableFrom(clazz) ||
- BigInteger.class.isAssignableFrom(clazz);/* ||
- BigDecimal.class.isAssignableFrom(clazz);*/
+ BigInteger.class.isAssignableFrom(clazz) ||
+ BigDecimal.class.isAssignableFrom(clazz);
}
return false;
}
diff --git a/src/main/java/com/amazon/carbonado/raw/KeyDecoder.java b/src/main/java/com/amazon/carbonado/raw/KeyDecoder.java index eb44c53..a14a1a3 100644 --- a/src/main/java/com/amazon/carbonado/raw/KeyDecoder.java +++ b/src/main/java/com/amazon/carbonado/raw/KeyDecoder.java @@ -443,60 +443,11 @@ public class KeyDecoder { * @return amount of bytes read from source
* @throws CorruptEncodingException if source data is corrupt
* @since 1.2
- * /
+ */
public static int decode(byte[] src, int srcOffset, BigDecimal[] valueRef)
throws CorruptEncodingException
{
- final int originalOffset;
- BigInteger unscaledValue;
- int scale;
-
- try {
- int header = src[srcOffset] & 0xff;
-
- int headerSize;
- switch (header) {
- case (NULL_BYTE_HIGH & 0xff): case (NULL_BYTE_LOW & 0xff):
- valueRef[0] = null;
- return 1;
-
- case 0x7f: case 0x80:
- valueRef[0] = BigDecimal.ZERO;
- return 1;
-
- case 1: case 0x7e: case 0x81: case 0xfe:
- headerSize = 5;
- break;
-
- default:
- headerSize = 1;
- break;
- }
-
- originalOffset = srcOffset;
- srcOffset += headerSize;
-
- BigInteger[] unscaledRef = new BigInteger[1];
- srcOffset += decode(src, srcOffset, unscaledRef);
-
- header = src[srcOffset++] & 0xff;
- if (header > 0 && header < 0xff) {
- scale = header - 0x80;
- } else {
- scale = DataDecoder.decodeInt(src, srcOffset);
- srcOffset += 4;
- }
-
- unscaledValue = unscaledRef[0];
- if (unscaledValue.signum() < 0) {
- scale = ~scale;
- }
- } catch (IndexOutOfBoundsException e) {
- throw new CorruptEncodingException(null, e);
- }
-
- valueRef[0] = new BigDecimal(unscaledValue, scale);
- return srcOffset - originalOffset;
+ return decode(src, srcOffset, valueRef, 0);
}
/**
@@ -508,18 +459,29 @@ public class KeyDecoder { * @return amount of bytes read from source
* @throws CorruptEncodingException if source data is corrupt
* @since 1.2
- * /
+ */
public static int decodeDesc(byte[] src, int srcOffset, BigDecimal[] valueRef)
throws CorruptEncodingException
{
- final int originalOffset;
+ return decode(src, srcOffset, valueRef, -1);
+ }
+
+ /**
+ * @param xorMask 0 for normal decoding, -1 for descending decoding
+ */
+ private static int decode(byte[] src, int srcOffset, BigDecimal[] valueRef, int xorMask)
+ throws CorruptEncodingException
+ {
+ final int originalOffset = srcOffset;
BigInteger unscaledValue;
int scale;
try {
- int header = src[srcOffset] & 0xff;
+ int header = (src[srcOffset] ^ xorMask) & 0xff;
+
+ int digitAdjust;
+ int exponent;
- int headerSize;
switch (header) {
case (NULL_BYTE_HIGH & 0xff): case (NULL_BYTE_LOW & 0xff):
valueRef[0] = null;
@@ -529,33 +491,92 @@ public class KeyDecoder { valueRef[0] = BigDecimal.ZERO;
return 1;
- case 1: case 0x7e: case 0x81: case 0xfe:
- headerSize = 5;
+ case 1: case 0x7e:
+ digitAdjust = 999 + 12;
+ exponent = (~DataDecoder.decodeInt(src, srcOffset + 1)) ^ xorMask;
+ srcOffset += 5;
+ break;
+
+ case 0x81: case 0xfe:
+ digitAdjust = 12;
+ exponent = DataDecoder.decodeInt(src, srcOffset + 1) ^ xorMask;
+ srcOffset += 5;
break;
default:
- headerSize = 1;
+ exponent = (src[srcOffset++] ^ xorMask) & 0xff;
+ if (exponent >= 0x82) {
+ digitAdjust = 12;
+ exponent -= 0xc0;
+ } else {
+ digitAdjust = 999 + 12;
+ exponent = 0x3f - exponent;
+ }
break;
}
- originalOffset = srcOffset;
- srcOffset += headerSize;
-
- BigInteger[] unscaledRef = new BigInteger[1];
- srcOffset += decodeDesc(src, srcOffset, unscaledRef);
-
- header = src[srcOffset++] & 0xff;
- if (header > 0 && header < 0xff) {
- scale = 0x7f - header;
- } else {
- scale = ~DataDecoder.decodeInt(src, srcOffset);
- srcOffset += 4;
+ // Significand is base 1000 encoded, 10 bits per digit.
+
+ unscaledValue = null;
+ int precision = 0;
+
+ int accum = 0;
+ int bits = 0;
+ BigInteger lastDigit = null;
+
+ loop: while (true) {
+ accum = (accum << 8) | ((src[srcOffset++] ^ xorMask) & 0xff);
+ bits += 8;
+ if (bits >= 10) {
+ int digit = (accum >> (bits - 10)) & 0x3ff;
+
+ switch (digit) {
+ case 0:
+ case 1023:
+ lastDigit = lastDigit.divide(ONE_HUNDRED);
+ if (unscaledValue == null) {
+ unscaledValue = lastDigit;
+ } else {
+ unscaledValue = unscaledValue.multiply(BigInteger.TEN).add(lastDigit);
+ }
+ precision += 1;
+ break loop;
+ case 1:
+ case 1022:
+ lastDigit = lastDigit.divide(BigInteger.TEN);
+ if (unscaledValue == null) {
+ unscaledValue = lastDigit;
+ } else {
+ unscaledValue = unscaledValue.multiply(ONE_HUNDRED).add(lastDigit);
+ }
+ precision += 2;
+ break loop;
+ case 2:
+ case 1021:
+ if (unscaledValue == null) {
+ unscaledValue = lastDigit;
+ } else {
+ unscaledValue = unscaledValue.multiply(ONE_THOUSAND).add(lastDigit);
+ }
+ precision += 3;
+ break loop;
+
+ default:
+ if (unscaledValue == null) {
+ if ((unscaledValue = lastDigit) != null) {
+ precision += 3;
+ }
+ } else {
+ unscaledValue = unscaledValue.multiply(ONE_THOUSAND).add(lastDigit);
+ precision += 3;
+ }
+ bits -= 10;
+ lastDigit = BigInteger.valueOf(digit - digitAdjust);
+ }
+ }
}
- unscaledValue = unscaledRef[0];
- if (unscaledValue.signum() < 0) {
- scale = ~scale;
- }
+ scale = precision - exponent;
} catch (IndexOutOfBoundsException e) {
throw new CorruptEncodingException(null, e);
}
@@ -563,7 +584,6 @@ public class KeyDecoder { valueRef[0] = new BigDecimal(unscaledValue, scale);
return srcOffset - originalOffset;
}
- */
/**
* Decodes the given byte array as originally encoded for ascending order.
diff --git a/src/main/java/com/amazon/carbonado/raw/KeyEncoder.java b/src/main/java/com/amazon/carbonado/raw/KeyEncoder.java index 0c90499..bea676b 100644 --- a/src/main/java/com/amazon/carbonado/raw/KeyEncoder.java +++ b/src/main/java/com/amazon/carbonado/raw/KeyEncoder.java @@ -434,90 +434,19 @@ public class KeyEncoder { * @param dstOffset offset into destination array
* @return amount of bytes written
* @since 1.2
- * /
+ */
public static int encode(BigDecimal value, byte[] dst, int dstOffset) {
- /* Encoding of first byte:
-
- 0x00: null low (unused)
- 0x01: negative signum; four bytes follow for positive exponent
- 0x02..0x3f: negative signum; positive exponent; 3e range, 61..0
- 0x40..0x7d: negative signum; negative exponent; 3e range, -1..-62
- 0x7e: negative signum; four bytes follow for negative exponent
- 0x7f: negative zero (unused)
- 0x80: zero
- 0x81: positive signum; four bytes follow for negative exponent
- 0x82..0xbf: positive signum; negative exponent; 3e range, -62..-1
- 0xc0..0xfd: positive signum; positive exponent; 3e range, 0..61
- 0xfe: positive signum; four bytes follow for positive exponent
- 0xff: null high
- * /
-
if (value == null) {
dst[dstOffset] = NULL_BYTE_HIGH;
return 1;
}
- int signum = value.signum();
- if (signum == 0) {
+ if (value.signum() == 0) {
dst[dstOffset] = (byte) 0x80;
return 1;
}
- final int originalOffset = dstOffset;
-
- int scale = value.scale();
- int exponent = value.precision() - scale;
- System.out.println("exponent: " + exponent);
- System.out.println("unscaled: " + value.unscaledValue());
-
- if (signum < 0) {
- if (exponent >= -0x3e && exponent < 0x3e) {
- dst[dstOffset++] = (byte) (0x3f - exponent);
- } else {
- if (exponent < 0) {
- dst[dstOffset++] = (byte) 0x7e;
- } else {
- dst[dstOffset++] = (byte) 1;
- }
- DataEncoder.encode(~exponent, dst, dstOffset);
- dstOffset += 4;
- }
- } else {
- if (exponent >= -0x3e && exponent < 0x3e) {
- dst[dstOffset++] = (byte) (exponent + 0xc0);
- } else {
- if (exponent < 0) {
- dst[dstOffset++] = (byte) 0x81;
- } else {
- dst[dstOffset++] = (byte) 0xfe;
- }
- DataEncoder.encode(exponent, dst, dstOffset);
- dstOffset += 4;
- }
- }
-
- dstOffset += encode(value.unscaledValue(), dst, dstOffset);
-
- /* Encoding of scale:
-
- 0x00: negative scale; four bytes follow for scale
- 0x01..0x7f: negative scale; 7f range, -127..-1
- 0x80..0xfe: positive scale; 7f range, 0..126
- 0xff: positive scale; four bytes follow for scale
- * /
-
- if (signum < 0) {
- scale = ~scale;
- }
- if (scale >= -0x7f && scale < 0x7f) {
- dst[dstOffset++] = (byte) (scale + 0x80);
- } else {
- dst[dstOffset++] = scale < 0 ? ((byte) 0) : ((byte) 0xff);
- DataEncoder.encode(scale, dst, dstOffset);
- dstOffset += 4;
- }
-
- return dstOffset - originalOffset;
+ return encode(value).copyTo(dst, dstOffset);
}
/**
@@ -534,114 +463,221 @@ public class KeyEncoder { * @param dstOffset offset into destination array
* @return amount of bytes written
* @since 1.2
- * /
+ */
public static int encodeDesc(BigDecimal value, byte[] dst, int dstOffset) {
- /* Encoding of first byte:
-
- 0x00: null high (unused)
- 0x01: positive signum; four bytes follow for positive exponent
- 0x02..0x3f: positive signum; positive exponent; 3e range, 0..61
- 0x40..0x7d: positive signum; negative exponent; 3e range, -62..-1
- 0x7e: positive signum; four bytes follow for negative exponent
- 0x7f: zero
- 0x80: negative zero (unused)
- 0x81: negative signum; four bytes follow for negative exponent
- 0x82..0xbf: negative signum; negative exponent; 3e range, -1..-62
- 0xc0..0xfd: negative signum; positive exponent; 3e range, 61..0
- 0xfe: negative signum; four bytes follow for positive exponent
- 0xff: null low
- * /
-
if (value == null) {
dst[dstOffset] = NULL_BYTE_LOW;
return 1;
}
- int signum = value.signum();
- if (signum == 0) {
+ if (value.signum() == 0) {
dst[dstOffset] = (byte) 0x7f;
return 1;
}
- final int originalOffset = dstOffset;
+ return encode(value).copyDescTo(dst, dstOffset);
+ }
+
+ /**
+ * Returns the amount of bytes required to encode a BigDecimal.
+ *
+ * <p><i>Note:</i> It is recommended that value be normalized by stripping
+ * trailing zeros. This makes searching by value much simpler.
+ *
+ * @param value BigDecimal value to encode, may be null
+ * @return amount of bytes needed to encode
+ * @since 1.2
+ */
+ public static int calculateEncodedLength(BigDecimal value) {
+ if (value == null || value.signum() == 0) {
+ return 1;
+ }
+
+ return encode(value).mLength;
+ }
+
+ private static class CachedBigDecimal {
+ static final ThreadLocal<CachedBigDecimal> cLocal =
+ new ThreadLocal<CachedBigDecimal>();
+
+ final BigDecimal mValue;
+ final byte[] mEncoded;
+ final int mLength;
+
+ CachedBigDecimal(BigDecimal value, byte[] encoded, int length) {
+ mValue = value;
+ mEncoded = encoded;
+ mLength = length;
+ }
+
+ int copyTo(byte[] dst, int dstOffset) {
+ int length = mLength;
+ System.arraycopy(mEncoded, 0, dst, dstOffset, length);
+ return length;
+ }
+
+ int copyDescTo(byte[] dst, int dstOffset) {
+ byte[] encoded = mEncoded;
+ int length = mLength;
+ for (int i=0; i<length; i++) {
+ dst[dstOffset++] = (byte) ~encoded[i];
+ }
+ return length;
+ }
+ }
+
+ /**
+ * @param value cannot be null or zero
+ * @return non-null cached encoding
+ */
+ private static CachedBigDecimal encode(BigDecimal value) {
+ CachedBigDecimal cached = CachedBigDecimal.cLocal.get();
+ if (cached != null && cached.mValue.equals(value)) {
+ return cached;
+ }
+
+ // Exactly predicting encoding length is hard. Instead, overestimate
+ // and compare with actual encoded result. Result is cached to avoid
+ // recomputation.
+
+ // 5: maximum header encoding length
+ // 1: extra byte for last digit
+ // 10: bits for rare extra digit
+ // 10: bits for terminator digit
+ int length = (5 + 1) + (((value.unscaledValue().bitLength() + (10 + 10)) + 7) >> 3);
+
+ byte[] encoded = new byte[length];
+ length = encodeUncached(value, encoded);
+
+ cached = new CachedBigDecimal(value, encoded, length);
+ CachedBigDecimal.cLocal.set(cached);
+
+ return cached;
+ }
+
+ /**
+ * @param value cannot be null or zero
+ */
+ private static int encodeUncached(BigDecimal value, byte[] dst) {
+ /* Encoding of header:
+
+ 0x00: null low (unused)
+ 0x01: negative signum; four bytes follow for positive exponent
+ 0x02..0x3f: negative signum; positive exponent; 3e range, 61..0
+ 0x40..0x7d: negative signum; negative exponent; 3e range, -1..-62
+ 0x7e: negative signum; four bytes follow for negative exponent
+ 0x7f: negative zero (unused)
+ 0x80: zero
+ 0x81: positive signum; four bytes follow for negative exponent
+ 0x82..0xbf: positive signum; negative exponent; 3e range, -62..-1
+ 0xc0..0xfd: positive signum; positive exponent; 3e range, 0..61
+ 0xfe: positive signum; four bytes follow for positive exponent
+ 0xff: null high
+ */
- int scale = value.scale();
- int exponent = value.precision() - scale;
+ int dstOffset = 0;
+ int precision = value.precision();
+ int exponent = precision - value.scale();
- if (signum < 0) {
+ if (value.signum() < 0) {
if (exponent >= -0x3e && exponent < 0x3e) {
- dst[dstOffset++] = (byte) (exponent + 0xc0);
+ dst[dstOffset++] = (byte) (0x3f - exponent);
} else {
if (exponent < 0) {
- dst[dstOffset++] = (byte) 0x81;
+ dst[dstOffset] = (byte) 0x7e;
} else {
- dst[dstOffset++] = (byte) 0xfe;
+ dst[dstOffset] = (byte) 1;
}
- DataEncoder.encode(exponent, dst, dstOffset);
- dstOffset += 4;
+ DataEncoder.encode(~exponent, dst, dstOffset + 1);
+ dstOffset += 5;
}
} else {
if (exponent >= -0x3e && exponent < 0x3e) {
- dst[dstOffset++] = (byte) (0x3f - exponent);
+ dst[dstOffset++] = (byte) (exponent + 0xc0);
} else {
if (exponent < 0) {
- dst[dstOffset++] = (byte) 0x7e;
+ dst[dstOffset] = (byte) 0x81;
} else {
- dst[dstOffset++] = (byte) 1;
+ dst[dstOffset] = (byte) 0xfe;
}
- DataEncoder.encode(~exponent, dst, dstOffset);
- dstOffset += 4;
+ DataEncoder.encode(exponent, dst, dstOffset + 1);
+ dstOffset += 5;
}
}
- dstOffset += encodeDesc(value.unscaledValue(), dst, dstOffset);
-
- /* Encoding of scale:
+ // Significand must be decimal encoded to maintain proper sort order.
+ // Base 1000 is more efficient than base 10 and still maintains proper
+ // sort order. A minimum of two bytes must be generated, however.
+
+ BigInteger unscaledValue = value.unscaledValue();
+
+ // Ensure a non-fractional amount of base 1000 digits.
+ int terminator;
+ switch (precision % 3) {
+ case 0: default:
+ terminator = 2;
+ break;
+ case 1:
+ terminator = 0;
+ unscaledValue = unscaledValue.multiply(ONE_HUNDRED);
+ break;
+ case 2:
+ terminator = 1;
+ unscaledValue = unscaledValue.multiply(BigInteger.TEN);
+ break;
+ }
- 0x00: positive scale; four bytes follow for scale
- 0x01..0x7f: positive scale; 7f range, 0..126
- 0x80..0xfe: negative scale; 7f range, -127..-1
- 0xff: negative scale; four bytes follow for scale
- * /
+ // 10 bits per digit and 1 extra terminator digit. Digit values 0..999
+ // are encoded as 12..1011. Digit values 0..11 and 1012..1023 are used
+ // for terminators.
- if (signum < 0) {
- scale = ~scale;
- }
- if (scale >= -0x7f && scale < 0x7f) {
- dst[dstOffset++] = (byte) (0x7f - scale);
+ int digitAdjust;
+ if (unscaledValue.signum() >= 0) {
+ digitAdjust = 12;
} else {
- dst[dstOffset++] = scale < 0 ? ((byte) 0xff) : ((byte) 0);
- DataEncoder.encode(~scale, dst, dstOffset);
- dstOffset += 4;
+ digitAdjust = 999 + 12;
+ terminator = 1023 - terminator;
}
- return dstOffset - originalOffset;
- }
+ int pos = ((unscaledValue.bitLength() + 9) / 10) + 1;
+ int[] digits = new int[pos];
+ digits[--pos] = terminator;
- /**
- * Returns the amount of bytes required to encode a BigDecimal.
- *
- * <p><i>Note:</i> It is recommended that value be normalized by stripping
- * trailing zeros. This makes searching by value much simpler.
- *
- * @param value BigDecimal value to encode, may be null
- * @return amount of bytes needed to encode
- * @since 1.2
- * /
- public static int calculateEncodedLength(BigDecimal value) {
- if (value == null || value.signum() == 0) {
- return 1;
+ while (unscaledValue.signum() != 0) {
+ BigInteger[] divrem = unscaledValue.divideAndRemainder(ONE_THOUSAND);
+
+ if (--pos < 0) {
+ // Handle rare case when an extra digit is required.
+ int[] newDigits = new int[digits.length + 1];
+ System.arraycopy(digits, 0, newDigits, 1, digits.length);
+ digits = newDigits;
+ pos = 0;
+ }
+
+ digits[pos] = divrem[1].intValue() + digitAdjust;
+
+ unscaledValue = divrem[0];
+ }
+
+ // Now encode digits in proper order, 10 bits per digit. 1024 possible
+ // values per 10 bits, and so base 1000 is quite efficient.
+
+ int accum = 0;
+ int bits = 0;
+ for (int i=0; i<digits.length; i++) {
+ accum = (accum << 10) | digits[i];
+ bits += 10;
+ do {
+ dst[dstOffset++] = (byte) (accum >> (bits -= 8));
+ } while (bits >= 8);
}
- int scale = value.scale();
- int exponent = value.precision() - value.scale();
+ if (bits != 0) {
+ dst[dstOffset++] = (byte) (accum << (8 - bits));
+ }
- int headerSize = (exponent >= -0x3e && exponent < 0x3e) ? 1 : 5;
- int scaleSize = (scale >= -0x7f && scale < 0x7f) ? 1 : 5;
-
- return headerSize + calculateEncodedLength(value.unscaledValue()) + scaleSize;
+ return dstOffset;
}
- */
/**
* Encodes the given optional unsigned byte array into a variable amount of
|