package com.after_sunrise.oss.otdb.je.binding; import java.math.BigDecimal; import java.math.BigInteger; import com.sleepycat.bind.tuple.TupleInput; import com.sleepycat.bind.tuple.TupleOutput; /** * <p> * Utility to bind BigDecimal value to Tuple in/out with byte array size * optimization. * </p> * <p> * We assume that a typical tick value are <i>'relatively small'</i> positive * values with less than 15 digit of decimal scale, and its unscaled value is * less than {@code Long.MAX_VALUE} (=9223372036854775807). The default * implementation of the {@link TupleOutput} uses 3 bytes to store a * <i>'relatively small'</i> {@link BigDecimal} value at minimum, which is not * so size efficient for storing values such as 0, 1 or 1.1. This utility aims * to store these <i>'relatively small'</i> positive values in more size * efficient manner. If the value happens to fall outside of the <i>'relatively * small'</i> positive range, the implementation will be delegated to the * default {@link TupleOutput}'s implementation, with an additional cost of one * byte. * </p> * <p> * In this implementation, the first byte of the data defines how the following * data are written and should be read. * </p> * <ul> * <li>If the first byte is equal to -128 (b == -128), {@link TupleOutput}'s * default implementation is used.</li> * <li>If the first byte is greater than -8 and no greater than 128 (-8 < b <= * 128), the first byte itself represents the whole data.</li> * <li>Otherwise, the first byte indicates a combination of the scale and * unscaled value's byte length. Bytes following the first bytes represent the * unscaled value.</li> * </ul> * <table border="1"> * <caption>First Byte Combination</caption> * <tr> * <th>Fist byte</th> * <th>Scale</th> * <th>Byte Length</th> * <th>Example Value</th> * </tr> * <tr> * <td>-8</td> * <td>0</td> * <td>1</td> * <td>-8, -100, -128</td> * </tr> * <tr> * <td>-9</td> * <td>1</td> * <td>1</td> * <td>0.1, -1.2</td> * </tr> * <tr> * <td>...</td> * <td>...</td> * <td>...</td> * <td>...</td> * </tr> * <tr> * <td>-23</td> * <td>0</td> * <td>2</td> * <td>128, 32767</td> * </tr> * <tr> * <td>-24</td> * <td>1</td> * <td>2</td> * <td>12.8, 3276.7</td> * </tr> * <tr> * <td>...</td> * <td>...</td> * <td>...</td> * <td>...</td> * </tr> * <tr> * <td>-126</td> * <td>13</td> * <td>8</td> * <td>922337.2036854775807</td> * </tr> * <tr> * <td>-127</td> * <td>14</td> * <td>8</td> * <td>92233.72036854775807</td> * </tr> * </table> * * @author takanori.takase */ public final class TupleBindingUtils { private static final byte SCALE = 15; private static final byte LONG_BITS = 8; private static final byte MIN = Byte.MIN_VALUE + SCALE * LONG_BITS; private static final BigInteger MAX = BigInteger.valueOf(Long.MAX_VALUE); /** * Write BigDecimal value to TupleOutput with size optimization. * * @param out * Output to write value to. * @param value * Value to write to output. */ public static void write(TupleOutput out, BigDecimal value) { int scale = value.scale(); if (scale >= SCALE) { out.writeByte(Byte.MIN_VALUE); out.writeBigDecimal(value); return; } BigInteger bigInt = value.unscaledValue(); if (bigInt.abs().compareTo(MAX) > 0) { out.writeByte(Byte.MIN_VALUE); out.writeBigDecimal(value); return; } long unscaled = bigInt.longValue(); if (scale == 0 && MIN < unscaled && unscaled <= Byte.MAX_VALUE) { out.writeByte((byte) unscaled); return; } byte[] bytes = bigInt.toByteArray(); int first = MIN - (bytes.length - 1) * SCALE - scale; out.writeByte(first); out.write(bytes); } /** * Read BigDecimal value from TupleInput with size optimization. * * @param in * Input to read value from. * @return Value read from input. */ public static BigDecimal read(TupleInput in) { byte first = in.readByte(); if (MIN < first) { return BigDecimal.valueOf(first); } if (first == Byte.MIN_VALUE) { return in.readBigDecimal(); } int scale = (MIN - first) % SCALE; int length = (MIN - first) / SCALE + 1; byte[] bytes = new byte[length]; int read = in.read(bytes); if (read != length) { throw new RuntimeException("Invalid length : " + read); } BigInteger unscaled = new BigInteger(bytes); return new BigDecimal(unscaled, scale); } }