![JAR search and dependency download from the Maven repository](/logo.png)
oracle.kv.impl.api.table.NumberUtils Maven / Gradle / Ivy
/*-
* Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle NoSQL
* Database made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle NoSQL Database for a copy of the license and
* additional information.
*/
package oracle.kv.impl.api.table;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import com.sleepycat.bind.tuple.TupleInput;
/**
* This class encapsulates methods to serialize/deserialize BigDecimal value
* to/from a byte array.
*/
public class NumberUtils {
/* The terminator byte */
private static final byte TERMINATOR = (byte)-1;
/* The leading byte of INFINITY */
private static final byte POSI_INFINITY = (byte)0xFF;
/* The leading byte of -INFINITY */
private static final byte NEG_INFINITY = (byte)0;
/* The leading byte of ZERO */
private static final byte ZERO = excess128(0);
/* The byte array that represents ZERO */
private static final byte[] BYTES_ZERO = new byte[]{ ZERO };
/* The number of digits of Long.MAX_VALUE */
private static final int LONG_MAX_DIGITS_NUM =
String.valueOf(Long.MAX_VALUE).length();
/**
* Serializes a BigDecimal value to a byte array that supports byte-to-byte
* comparison.
*
* First, we need to do the normalization, which means we normalize a
* given BigDecimal into two parts: exponent and mantissa.
*
* The decimal part contains 1 integer(non zero). For example,
* 1234.56 will be normalized to 1.23456E3;
* 123.4E100 will be normalized to 1.234E102;
* -1234.56E-100 will be normalized to -1.23456E-97.
*
* The byte format is:
* Byte 0 ~ m: the leading bytes that represents the combination of sign
* and exponent, m is from 1 to 5.
* Byte m ~ n: the mantissa part with sign, each byte represents every
* 2 digits, a terminator byte (-1) is appended at the end.
*/
public static byte[] serialize(BigDecimal value) {
if (value.compareTo(BigDecimal.ZERO) == 0) {
return BYTES_ZERO;
}
BigDecimal decimal = value.stripTrailingZeros();
int sign = decimal.signum();
String mantissa = decimal.unscaledValue().abs().toString();
int exponent = mantissa.length() - 1 - decimal.scale();
return writeBytes(sign, exponent, mantissa);
}
/**
* Serializes a long to a byte array that supports byte-to-byte comparison.
*
* The byte format is:
* Byte 0 ~ m: the leading bytes that represents the combination of
* sign and exponent, m is from 1 to 2.
* Byte m ~ n: the mantissa part with sign, each byte represents every
* 2 digits, a terminator byte (-1) is appended at the end.
*/
public static byte[] serialize(long value) {
if (value == 0) {
return BYTES_ZERO;
}
char[] digits = Long.toString(value).toCharArray();
int mlen = digits.length;
/* Remove tailing zeros */
while (mlen > 0 && digits[mlen - 1] == '0') {
mlen--;
}
int sign = value > 0 ? 1 : -1;
int start = sign > 0 ? 0 : 1;
int exponent = digits.length - 1 - start;
return writeBytes(sign, exponent, digits, start, mlen - start);
}
/**
* Serializes the exponent and mantissa to a byte array and return it
*/
private static byte[] writeBytes(int sign, int exponent, String digits) {
return writeBytes(sign, exponent, digits.toCharArray(),
0, digits.length());
}
private static byte[] writeBytes(int sign, int exponent,
char[] digits, int from, int len) {
int nLeadingBytes = getNumBytesExponent(sign, exponent);
/*
* Required byte # =
* leading byte # + mantissa byte # + 1 terminator byte
*/
int size = nLeadingBytes + (len + 1) / 2 + 1;
byte[] bytes = new byte[size];
writeExponent(bytes, 0, sign, exponent);
writeMantissa(bytes, nLeadingBytes, sign, digits, from, len);
return bytes;
}
/**
* Write the leading bytes that combines the sign and exponent to
* the given buffer, the leading bytes format as below:
*
* ------------- ------- ---------------------------------------------
* Leading bytes Sign Exponent
* ------------- ------- ---------------------------------------------
* 0xFF infinity
* 0xFC - 0xFE > 0 unused/reserved
* 0xFB > 0 reserved for up to Long.MAX_VALUE
* 0xF7 - 0xFA > 0 exponent from 4097 up to Integer.MAX_VALUE,
* 1 ~ 5 exponent bytes
* 0xE7 - 0xF6 > 0 exponent from 64 up to 4096, 1 exponent byte
* 0x97 - 0xE6 > 0 exponent from -16 up to 63, 0 exponent bytes
* 0x87 - 0x96 > 0 exponent from -4096 up to -17, 1 exponent byte
* 0x83 - 0x86 > 0 exponent from Integer.MIN_VALUE up to -4097,
* 1 ~ 5 exponent bytes
* 0x82 > 0 reserved for down to Long.MIN_VALUE
* 0x81 > 0 reserved for later expansion
* 0x80 = 0
* 0x7F < 0 reserved for later expansion
* 0x7E < 0 reserved for down to Long.MIN_VALUE
* 0x7A - 0x7D < 0 exponent from -4097 down to Integer.MIN_VALUE,
* 1 ~ 5 exponent bytes
* 0x6A - 0x79 < 0 exponent from -17 down to -4096, 1 exponent byte
* 0x1A - 0x69 < 0 exponent from 63 down to -16, 0 exponent bytes
* 0x0A - 0x19 < 0 exponent from 4096 down to 64, 1 exponent byte
* 0x06 - 0x09 < 0 exponent from Integer.MAX_VALUE down to 4097,
* 1 ~ 5 exponent bytes
* 0x05 < 0 reserved for up to Long.MAX_VALUE
* 0x01 - 0x04 < 0 unused/reserved
* 0x00 -infinity
*/
static void writeExponent(byte[] buffer, int offset, int sign, int value) {
if (sign == 0) {
buffer[offset] = (byte)0x80;
} else if (sign > 0) {
if (value >= 4097) {
writeExponentMultiBytes(buffer, offset, (byte)0xF7,
(value - 4097));
} else if (value >= 64) {
writeExponentTwoBytes(buffer, offset,
(byte)0xE7, (value - 64));
} else if (value >= -16) {
buffer[offset] = (byte)(0x97 + (value - (-16)));
} else if (value >= -4096) {
writeExponentTwoBytes(buffer, offset, (byte)0x87,
(value - (-4096)));
} else {
writeExponentMultiBytes(buffer, offset, (byte)0x83,
(value - Integer.MIN_VALUE));
}
} else {
/* Sign < 0 */
if (value <= -4097) {
writeExponentMultiBytes(buffer, offset, (byte)0x7A,
(-4097 - value));
} else if (value <= -17){
writeExponentTwoBytes(buffer, offset, (byte)0x6A,
(-17 - value));
} else if (value <= 63) {
buffer[offset] = (byte)(0x1A + (63 - value));
} else if (value <= 4096) {
writeExponentTwoBytes(buffer, offset, (byte)0x0A,
(4096 - value));
} else {
writeExponentMultiBytes(buffer, offset, (byte)0x06,
(Integer.MAX_VALUE - value));
}
}
}
/**
* Writes the leading bytes that represents the exponent with 2 bytes to
* buffer.
*/
private static void writeExponentTwoBytes(byte[] buffer,
int offset,
byte byte0,
int exponent) {
buffer[offset++] = (byte)(byte0 + (exponent >> 8 & 0xF));
buffer[offset] = (byte)(exponent & 0xFF);
}
/**
* Writes the leading bytes that represents the exponent with variable
* length bytes to the buffer.
*/
private static void writeExponentMultiBytes(byte[] buffer,
int offset,
byte byte0,
int exponent) {
int size = getNumBytesExponentVarLen(exponent);
buffer[offset++] = (byte)(byte0 + (size - 2));
if (size > 4) {
buffer[offset++] = (byte)(exponent >>> 24);
}
if (size > 3) {
buffer[offset++] = (byte)(exponent >>> 16);
}
if (size > 2) {
buffer[offset++] = (byte)(exponent >>> 8);
}
buffer[offset] = (byte)exponent;
}
/**
* Returns the number of bytes used to store the exponent value.
*/
static int getNumBytesExponent(int sign, int value) {
if (sign == 0) {
return 1;
} else if (sign > 0) {
if (value >= 4097) {
return getNumBytesExponentVarLen(value - 4097);
} else if (value >= 64) {
return 2;
} else if (value >= -16) {
return 1;
} else if (value >= -4096) {
return 2;
} else {
return getNumBytesExponentVarLen(value - Integer.MIN_VALUE);
}
} else {
if (value <= -4097) {
return getNumBytesExponentVarLen(-4097 - value);
} else if (value <= -17){
return 2;
} else if (value <= 63) {
return 1;
} else if (value <= 4096) {
return 2;
} else {
return getNumBytesExponentVarLen(Integer.MAX_VALUE - value);
}
}
}
/**
* Returns the number bytes that used to store the exponent value with
* variable length.
*/
private static int getNumBytesExponentVarLen(int exponent) {
if ((exponent & 0xFF000000) != 0) {
return 5;
} else if ((exponent & 0xFF0000) != 0) {
return 4;
} else if ((exponent & 0xFF00) != 0) {
return 3;
} else {
return 2;
}
}
/**
* Write mantissa digits to the given buffer.
*
* The digits are stored as a byte array with a terminator byte (-1):
* - All digits are stored with sign, 1 byte for every 2 digits.
* - Pad a zero if the number of digits is odd.
* - The byte is represented in excess-128.
* - The terminator used is -1. For negative value, subtract additional 2
* to leave -1 as a special byte.
*/
private static void writeMantissa(byte[] buffer, int offset, int sign,
char[] digits, int start, int len) {
for (int ind = 0; ind < len / 2; ind++) {
writeByte(buffer, offset++, sign, digits, start + ind * 2);
}
/* Pad a zero if left 1 digit only. */
if (len % 2 == 1) {
int last = start + len - 1;
writeByte(buffer, offset++, sign, new char[]{digits[last], '0'}, 0);
}
/* Writes terminator byte. */
buffer[offset] = excess128(TERMINATOR);
}
/**
* Parses 2 digits to a byte and write to the buffer on the given position.
*/
private static void writeByte(byte[] buffer, int offset, int sign,
char[] digits, int index) {
assert(digits.length > index + 1);
int value = (digits[index] - '0') * 10 + (digits[index + 1] - '0');
if (sign < 0) {
value = -1 * value;
}
buffer[offset] = toUnsignedByte(value);
}
/**
* Converts the value with sign to a unsigned byte. If the value is
* negative, subtract 2.
*/
private static byte toUnsignedByte(int value) {
if (value < 0) {
value -= 2;
}
return excess128(value);
}
/**
* Deserializes to a numeric object from a byte array, the numeric object
* can be a Integer, Long or BigDecimal based on its value.
*/
public static Object deserialize(byte[] bytes) {
return deserialize(bytes, false);
}
static void validateValue(byte[] bytes) {
deserialize(bytes, true);
}
private static Object deserialize(byte[] bytes, boolean validate) {
if (bytes == null || bytes.length == 0) {
throw new IllegalArgumentException(
"Invalid bytes for Number value, it must not be null or empty");
}
int offset = 0;
byte byte0 = bytes[offset++];
if (!isValidLeadingByte(byte0)) {
throw new IllegalArgumentException(
"Invalid leading byte: " + byte0);
}
int sign = readSign(byte0);
if (sign == 0) {
/* Sign of 0 represents ZERO, no more byte to read */
if (offset != bytes.length) {
throw new IllegalArgumentException(
"Invalid bytes for Number value");
}
return Integer.valueOf(0);
}
ReadBuffer in = new ReadBuffer(bytes, offset);
int exponent = readExponent(in, byte0, sign);
/* At least 2 more bytes left: mantissa part and TERMINATOR */
if (in.available() < 2) {
throw new IllegalArgumentException(
"Invalid bytes for Number value");
}
if (validate) {
return null;
}
return createNumericObject(in, sign, exponent);
}
/**
* Reads and returns the exponent value from the given TupleInput.
*/
static int readExponent(ReadBuffer in, byte byte0, int sign) {
if (sign == 0) {
return 0;
}
if (sign > 0) {
if (byte0 >= (byte)0xF7) {
return readExponentMultiBytes(in, byte0, (byte)0xF7) + 4097;
} else if (byte0 >= (byte)0xE7) {
return readExponentTwoBytes(in, byte0, (byte)0xE7) + 64;
} else if (byte0 >= (byte)0x97) {
return byte0 - (byte)0x97 + (-16);
} else if (byte0 >= (byte)0x87) {
return readExponentTwoBytes(in, byte0, (byte)0x87) + (-4096);
} else {
return readExponentMultiBytes(in, byte0, (byte)0x83) +
Integer.MIN_VALUE;
}
}
/* Sign < 0 */
if (byte0 >= (byte)0x7A) {
return -4097 - readExponentMultiBytes(in, byte0, (byte)0x7A);
} else if (byte0 >= (byte)0x6A) {
return -17 - readExponentTwoBytes(in, byte0, (byte)0x6A);
} else if (byte0 >= (byte)0x1A) {
return 63 - (byte0 - (byte)0x1A);
} else if (byte0 >= (byte)0x0A) {
return 4096 - readExponentTwoBytes(in, byte0, (byte)0x0A);
} else {
return Integer.MAX_VALUE -
readExponentMultiBytes(in, byte0, (byte)0x06);
}
}
/**
* Returns true if the leading byte is valid, false for unused.
*/
private static boolean isValidLeadingByte(byte byte0) {
return (byte0 >= (byte)0x83 && byte0 <= (byte)0xFA) ||
(byte0 >= (byte)0x06 && byte0 <= (byte)0x7D) ||
byte0 == (byte)0x80 || byte0 == (byte)0x00 ||
byte0 == (byte)0xFF;
}
/**
* Reads the exponent value represented with 2 bytes from the given
* TupleInput and returns it
*/
private static int readExponentTwoBytes(ReadBuffer in,
byte byte0,
byte base) {
if (in.available() == 0) {
throw new IllegalArgumentException(
"Invalid exponent value for Number");
}
return (byte0 - base) << 8 | (in.read() & 0xFF);
}
/**
* Reads the exponent value represented with multiple bytes from the
* given TupleInput and returns it
*/
private static int readExponentMultiBytes(ReadBuffer in,
byte byte0,
byte base){
int len = (byte0 - base) + 1;
int exp = 0;
if (in.available() < len) {
throw new IllegalArgumentException(
"Invalid exponent value for Number");
}
while (len-- > 0) {
exp = exp << 8 | (in.read() & 0xFF);
}
return exp;
}
/**
* Parses the digits string and constructs a numeric object, the numeric
* object can be any of Integer, Long or BigDecimal according to the value.
*/
private static Object createNumericObject(ReadBuffer in,
int sign,
int expo) {
int size = (in.available() - 1) * 2;
char[] buf = new char[size];
int ind = 0, val;
/* Read from byte array and store them into char array*/
while((val = excess128(in.read())) != TERMINATOR) {
if (val < 0) {
val = val + 2;
}
String group = Integer.toString(Math.abs(val));
if (group.length() == 1) {
buf[ind++] = '0';
buf[ind++] = group.charAt(0);
} else {
buf[ind++] = group.charAt(0);
buf[ind++] = group.charAt(1);
}
}
/* Remove the tailing zero in mantissa */
if (buf[ind - 1] == '0') {
ind--;
}
String digits = new String(buf, 0, ind);
/*
* If digits number is less than that of Long.MAX_VALUE, try to
* construct a Integer or Long.
*/
if ((expo >= 0 && expo < LONG_MAX_DIGITS_NUM) &&
digits.length() <= expo + 1) {
/* Append tailing zeros if number of digits < exponent + 1 */
int num = expo + 1;
if (digits.length() < num) {
digits = String.format("%-" + num + "s", digits)
.replace(' ', '0');
}
String signedDigits = (sign < 0) ? "-" + digits : digits;
/* Try to parse the string as long */
try {
long lval = Long.parseLong(signedDigits);
/* Returns as a int if the value is in range of int */
if (lval >= Integer.MIN_VALUE && lval <= Integer.MAX_VALUE) {
return Integer.valueOf((int)lval);
}
return Long.valueOf(lval);
} catch (NumberFormatException ignored) {
/*
* Out of Long's range, ignore the error and construct a
* BigDecimal object.
*/
}
}
/* Construct BigDecimal value */
BigInteger unscaled = new BigInteger(digits);
if (sign < 0) {
unscaled = unscaled.negate();
}
return new BigDecimal(unscaled, digits.length() - 1)
.scaleByPowerOfTen(expo);
}
/**
* Reads a tuple for a BigDecimal value from the given TupleInput.
*/
static byte[] readTuple(TupleInput in) {
byte byte0 = (byte)in.read();
int sign = readSign(byte0);
if (sign == 0) {
return new byte[]{byte0};
}
WriteBuffer buffer = new WriteBuffer();
/* Read the leading bytes */
readLeadingBytes(in, byte0, sign, buffer);
/* Read the bytes of mantissa */
int end = excess128(TERMINATOR);
byte b;
while ((b = (byte)in.read()) != end) {
buffer.write(b);
}
buffer.write((byte)end);
return buffer.toByteArray();
}
/**
* Read the leading bytes from the specified TupleInput.
*/
private static void readLeadingBytes(TupleInput in,
byte byte0,
int sign,
WriteBuffer buffer) {
buffer.write(byte0);
if (sign == 0) {
return;
}
if (in.available() == 0) {
throw new IllegalStateException("Failed to read leading bytes");
}
if (sign > 0) {
if (byte0 >= (byte)0xF7) {
readLeadingMultiBytes(in, byte0, (byte)0xF7, buffer);
} else if (byte0 >= (byte)0xE7) {
buffer.write((byte)in.read());
} else if (byte0 >= (byte)0x97) {
return;
} else if (byte0 >= (byte)0x87) {
buffer.write((byte)in.read());
} else {
readLeadingMultiBytes(in, byte0, (byte)0x83, buffer);
}
} else {
/* Sign < 0 */
if (byte0 >= (byte)0x7A) {
readLeadingMultiBytes(in, byte0, (byte)0x7A, buffer);
} else if (byte0 >= (byte)0x6A) {
buffer.write((byte)in.read());
} else if (byte0 >= (byte)0x1A) {
return;
} else if (byte0 >= (byte)0x0A) {
buffer.write((byte)in.read());
} else {
readLeadingMultiBytes(in, byte0, (byte)0x06, buffer);
}
}
}
/**
* Reads the leading bytes with variable length from the TupleInput
*/
private static void readLeadingMultiBytes(TupleInput in,
byte byte0,
byte base,
WriteBuffer buffer) {
int len = byte0 - base + 1;
if (in.available() < len) {
throw new IllegalStateException("Failed to read leading bytes");
}
for (int i = 0; i < len; i++) {
buffer.write((byte)in.read());
}
}
/**
* Returns the byte array that represents the next value of the BigDecimal
* value represented with the given byte array.
*
* It is used by NumberValueImpl.getNextValue(), the returning bytes is not
* tge actual next value of the given BigDeicimal value, but add a special
* byte to end of manitissa to make sure the bytes < nextUp() and there
* is no legal value such that bytes < cantHappen < nextUp().
*
* If sign == 0, return bytes of 0x81.
*
* If sign > 0, add 0x80 (unsigned byte representation for 0) to end of
* manitissa.
* e.g.
* 1234's byte[]: AA 8C A2 7F
* 1234's next value: AA 8C A2 80
*
* If sign < 0, increments 1 on the last byte of manitissa and pad 0x1A
* (unsigned byte representation for -100).
* e.g.
* -1234's byte[]: 56 72 5C 7F
* -1234's next value: 56 72 5C 80
*
* Note that this function is for internal use for bytes's comparison only,
* the deserializing of next value returned from nextUp() will causes the
* problem.
*/
static byte[] nextUp(byte[] bytes) {
if (readSign(bytes[0]) == 0) {
return new byte[]{(byte)(ZERO + 1)};
}
byte[] next = Arrays.copyOf(bytes, bytes.length);
next[next.length - 1] = ZERO;
return next;
}
/**
* Returns the sign number that the specified byte stand for.
*/
private static int readSign(byte byte0) {
return (byte0 == (byte)0x80) ? 0 : (((byte0 & 0x80) > 0) ? 1 : -1);
}
/**
* Returns the excess128 representation of a byte.
*/
private static byte excess128(int b) {
return (byte)(b ^ 0x80);
}
/**
* Returns the bytes that represents the negative infinity.
*/
static byte[] getNegativeInfinity() {
return new byte[] {NEG_INFINITY};
}
/**
* Returns the bytes that represents the positive infinity.
*/
static byte[] getPositiveInfinity() {
return new byte[] {POSI_INFINITY};
}
/**
* A simple read byte buffer.
*/
static class ReadBuffer {
private final byte[] bytes;
private int offset;
ReadBuffer(byte[] bytes, int offset) {
this.bytes = bytes;
this.offset = offset;
}
/**
* Reads the byte at this buffer's current position, and then
* increments the position. Return -1 if the position is out of range
* of the buffer.
*/
byte read() {
if (offset < bytes.length) {
return bytes[offset++];
}
return -1;
}
/**
* Returns the number of remaining bytes that can be read from this
* buffer.
*/
int available() {
return bytes.length - offset;
}
}
/**
* A simple write byte buffer.
*/
static class WriteBuffer {
private final static int INIT_SIZE = 32;
private final static int BUMP_SIZE = 32;
private byte[] bytes;
private int offset;
WriteBuffer() {
this(INIT_SIZE);
}
WriteBuffer(int size) {
bytes = new byte[size];
offset = 0;
}
/**
* Writes the given byte into this buffer at the current position,
* and then increments the position. If there is no space left,
* increase the buffer size.
*/
void write(byte val) {
if (offset == bytes.length) {
increaseBytes();
}
bytes[offset++] = val;
}
/**
* Creates a newly allocated byte array. Its size is the current number
* of bytes written and the valid contents of the buffer have been
* copied into it.
*/
byte[] toByteArray() {
return Arrays.copyOf(bytes, offset);
}
private void increaseBytes() {
offset = bytes.length;
bytes = Arrays.copyOf(bytes, bytes.length + BUMP_SIZE);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy