
software.amazon.event.ruler.ComparableNumber Maven / Gradle / Ivy
package software.amazon.event.ruler;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
/**
* Represents a number, turned into a comparable string
* Numbers are allowed in the range -50**9 .. +50**9, inclusive
* Comparisons are precise to 6 digits to the right of the decimal point
* They are all treated as floating point
* They are turned into strings by:
* 1. Add 10**9 (so no negatives), then multiply by 10**6 to remove the decimal point
* 2. Format to a 14 char string left padded with 0 because hex string converted from 5e9*1e6=10e15 has 14 characters.
* Note: We use Hex because of a) it can save 3 bytes memory per number than decimal b) it aligned IP address radix.
* If needed, we can consider to use 32 or 64 radix description to save more memory,e.g. the string length will be 10
* for 32 radix, and 9 for 64 radix.
*
* Note:
* The number is parsed to be java double to support number with decimal fraction, the max range supported is from
* -5e9 to 5e9 with precision of 6 digits to the right of decimal point.
* There is well known issue that double number will lose the precision while calculation among double and other data
* types, the higher the number, the lower the accuracy that can be maintained.
* For example: 0.30d - 0.10d = 0.19999999999999998 instead of 0.2d, and if extend to 1e10, the test result shows only
* 5 digits of precision from right of decimal point can be guaranteed with existing implementation.
* The current max number 5e9 is selected with a balance between keeping the committed 6 digits of precision from right
* of decimal point and the memory cost (each number is parsed into a 14 characters HEX string).
*
* CAVEAT:
* When there is need to further enlarging the max number, PLEASE BE VERY CAREFUL TO RESERVE THE NUMBER PRECISION AND
* TAKEN THE MEMORY COST INTO CONSIDERATION, BigDecimal shall be used to ensure the precision of double calculation ...
*/
class ComparableNumber {
private static final double TEN_E_SIX = 1E6;
static final int MAX_LENGTH_IN_BYTES = 16;
private static final String HEXES = new String(Constants.HEX_DIGITS, StandardCharsets.US_ASCII);
public static final int NIBBLE_SIZE = 4;
// 1111 0000
private static final int UPPER_NIBBLE_MASK = 0xF0;
// 0000 1111
private static final int LOWER_NIBBLE_MASK = 0x0F;
private ComparableNumber() {
}
static String generate(final double f) {
if (f < -Constants.FIVE_BILLION || f > Constants.FIVE_BILLION) {
throw new IllegalArgumentException("Value must be between " + -Constants.FIVE_BILLION +
" and " + Constants.FIVE_BILLION + ", inclusive");
}
return toHexStringSkippingFirstByte((long) (TEN_E_SIX * (Constants.FIVE_BILLION + f)));
}
/**
* converts a single byte to its two hexadecimal character representation
* @param value the byte we want to convert to hex string
* @return a 2 digit char array with the equivalent hex representation
*/
static char[] byteToHexChars(byte value) {
char[] result = new char[2];
int upperNibbleIndex = (value & UPPER_NIBBLE_MASK) >> NIBBLE_SIZE;
int lowerNibbleIndex = value & LOWER_NIBBLE_MASK;
result[0] = HEXES.charAt(upperNibbleIndex);
result[1] = HEXES.charAt(lowerNibbleIndex);
return result;
}
private static byte[] longToByteBuffer(long value) {
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.putLong(value);
return buffer.array();
}
static String toHexStringSkippingFirstByte(long value) {
byte[] raw = longToByteBuffer(value);
char[] outputChars = new char[14];
for (int i = 1; i < raw.length; i++) {
int pos = (i - 1) * 2;
char[] currentByteChars = byteToHexChars(raw[i]);
outputChars[pos] = currentByteChars[0];
outputChars[pos + 1] = currentByteChars[1];
}
return new String(outputChars);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy