
oracle.nosql.driver.util.NumberUtil Maven / Gradle / Ivy
/*-
* Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl/
*/
package oracle.nosql.driver.util;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* This class encapsulates methods to serialize/deserialize BigDecimal value
* to/from a byte array.
*
* The code are directly from KV's oracle.kv.impl.api.table.NumberUtils.
*/
public class NumberUtil {
/* The terminator byte */
private static final byte TERMINATOR = (byte)-1;
/* 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 };
/**
* 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);
}
/**
* Deserializes to a BigDecimal object from a byte array.
*/
public static BigDecimal deserialize(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
throw new IllegalStateException("Leading bytes should be null " +
"or empty");
}
int offset = 0;
byte byte0 = bytes[offset++];
if (!isValidLeadingByte(byte0)) {
throw new IllegalStateException("Invalid leading byte: " + byte0);
}
int sign = readSign(byte0);
if (sign == 0) {
return BigDecimal.ZERO;
}
ReadBuffer in = new ReadBuffer(bytes, offset);
int exponent = readExponent(in, byte0, sign);
return createNumericObject(in, sign, exponent);
}
/**
* 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
*/
private 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.
*/
private 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) {
if (digits.length <= index + 1) {
throw new IllegalArgumentException(
"Invalid digits buffer with length " + digits.length +
", it should be greater than " + (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);
}
/**
* Reads and returns the exponent value from the given TupleInput.
*/
private 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) {
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;
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 BigDecimal 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);
/* Construct BigDecimal value */
BigInteger unscaled = new BigInteger(digits);
if (sign < 0) {
unscaled = unscaled.negate();
}
return new BigDecimal(unscaled, digits.length() - 1)
.scaleByPowerOfTen(expo);
}
/**
* 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);
}
/**
* A simple read byte buffer.
*/
private 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;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy