@amazon-dax-sdk/client-dax
Version:
Amazon DAX Client for JavaScript
296 lines (255 loc) • 8.47 kB
JavaScript
/*
* Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
* use this file except in compliance with the License. A copy of the License
* is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
;
const BigInteger = require('bignumber.js');
const BigDecimal = require('./BigDecimal');
/** Byte to use for undefined, low ordering */
const NULL_BYTE_LOW = 0;
/** Byte to use for undefined, high ordering */
const NULL_BYTE_HIGH = 0xff;
class LexDecimal {
static compareUnsigned(a, b) {
return LexDecimal._compareUnsigned(a, 0, a.length, b, 0, b.length);
}
static _compareUnsigned(a, aoff, alen, b, boff, blen) {
let minLen = Math.min(alen, blen);
for(let i=0; i<minLen; i++) {
let ab = a[aoff + i];
let bb = b[boff + i];
if(ab != bb) {
return (ab & 0xff) - (bb & 0xff);
}
}
return alen - blen;
}
static encode(value, xorMask) {
if(xorMask === undefined) {
xorMask = 0;
}
if(value === undefined) {
return Buffer.from([NULL_BYTE_HIGH ^ xorMask]);
}
// throw new Error('NULL big decimal is not valid key value');
let encoded;
if(value.unscaledValue.isZero()) {
// Value is zero.
encoded = Buffer.from([0x80 ^ xorMask]);
return encoded;
}
encoded = Buffer.alloc(LexDecimal._estimateLength(value));
let offset = LexDecimal._encode(value, encoded, 0, xorMask);
if(offset == encoded.length) {
return encoded;
}
return encoded.slice(0, offset);
}
static _estimateLength(value) {
// Exactly predicting encoding length is hard, so overestimate to be safe.
// Calculate number of base-1000 digits, rounded up. Add two more digits to account for
// the terminator and a rare extra digit.
// const unscaledValue = value.mul(new BigNumber(10).pow(value.decimalPlaces()));
// let digits = 2 + Math.ceil(Math.log(unscaledValue.abs()) / Math.log(1000));
let digits = 2 + Math.ceil((value.unscaledValue.e + 1) / 3);
// Calculate the number of bytes required to encode the base-1000 digits, at 10 bits
// per digit. Add 5 for the maximum header length, and add 7 for round-up behavior when
// dividing by 8.
return (5 + (((digits * 10) + 7) >> 3));
}
static _encode(value, dst, dstOffset, xorMask) {
// Do the header.
if(xorMask === undefined) {
xorMask = 0;
}
let unscaledValue = value.unscaledValue;
let precision = unscaledValue.e + 1;
let exponent = precision - value.scale;
if(unscaledValue.isNeg()) {
if(exponent >= -0x3e && exponent < 0x3e) {
dst[dstOffset++] = ((0x3f - exponent) ^ xorMask);
} else {
if(exponent < 0) {
dst[dstOffset] = (0x7e ^ xorMask);
} else {
dst[dstOffset] = (1 ^ xorMask);
}
dst.writeInt32BE(exponent ^ xorMask ^ 0x7fffffff, dstOffset + 1);
dstOffset += 5;
}
} else {
if(exponent >= -0x3e && exponent < 0x3e) {
dst[dstOffset++] = ((exponent + 0xc0) ^ xorMask);
} else {
if(exponent < 0) {
dst[dstOffset] = (0x81 ^ xorMask);
} else {
dst[dstOffset] = (0xfe ^ xorMask);
}
dst.writeInt32BE(exponent ^ xorMask ^ 0x80000000, dstOffset + 1);
dstOffset += 5;
}
}
// Ensure a non-fractional amount of base-1000 digits.
let terminator;
switch(precision % 3) {
case 0: default:
terminator = 2;
break;
case 1:
terminator = 0;
unscaledValue = unscaledValue.mul(100);
break;
case 2:
terminator = 1;
unscaledValue = unscaledValue.mul(10);
break;
}
// 10 bits per base-1000 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.
let digitAdjust;
if(!unscaledValue.isNeg()) {
digitAdjust = 12;
} else {
digitAdjust = 999 + 12;
terminator = 1023 - terminator;
}
let pos = Math.floor(Math.ceil((unscaledValue.e + 1) / Math.log(2) / 3 * Math.log(1000) + 9) / 10) + 1;
let digits = [];
digits[--pos] = terminator;
while(!unscaledValue.isZero()) {
let divrem = [unscaledValue.divToInt(1000), unscaledValue.mod(1000)];
if(--pos < 0) {
// Handle rare case when an extra digit is required.
digits.unshift(0);
pos = 0;
}
digits[pos] = divrem[1].toNumber() + 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.
let accum = 0;
let bits = 0;
for(let i=0; i<digits.length; i++) {
accum = (accum << 10) | digits[i];
bits += 10;
do {
dst[dstOffset++] = ((accum >> (bits -= 8)) ^ xorMask);
} while(bits >= 8);
}
if(bits != 0) {
dst[dstOffset++] = ((accum << (8 - bits)) ^ xorMask);
}
return dstOffset;
}
static decode(src, srcOffset, valueRef, xorMask) {
if(xorMask === undefined) {
xorMask = 0;
}
const originalOffset = srcOffset;
let unscaledValue;
let scale;
let header = (src[srcOffset] ^ xorMask) & 0xff;
let digitAdjust;
let exponent;
switch(header) {
case (NULL_BYTE_HIGH & 0xff):
case (NULL_BYTE_LOW & 0xff):
valueRef[0] = undefined;
return 1;
case 0x7f: case 0x80:
valueRef[0] = new BigDecimal('0');
return 1;
case 1: case 0x7e:
digitAdjust = 999 + 12;
exponent = src.readInt32BE(srcOffset + 1) ^ xorMask ^ 0x7fffffff;
srcOffset += 5;
break;
case 0x81: case 0xfe:
digitAdjust = 12;
exponent = src.readInt32BE(srcOffset + 1) ^ xorMask ^ 0x80000000;
srcOffset += 5;
break;
default:
exponent = (src[srcOffset++] ^ xorMask) & 0xff;
if(exponent >= 0x82) {
digitAdjust = 12;
exponent -= 0xc0;
} else {
digitAdjust = 999 + 12;
exponent = 0x3f - exponent;
}
break;
}
// Significand is base 1000 encoded, 10 bits per digit.
unscaledValue = undefined;
let precision = 0;
let accum = 0;
let bits = 0;
let lastDigit = undefined;
loop: while(true) {
accum = (accum << 8) | ((src[srcOffset++] ^ xorMask) & 0xff);
bits += 8;
if(bits >= 10) {
let digit = (accum >> (bits - 10)) & 0x3ff;
switch(digit) {
case 0:
case 1023:
lastDigit = lastDigit.divToInt(100);
if(unscaledValue === undefined) {
unscaledValue = lastDigit;
} else {
unscaledValue = unscaledValue.mul(10).add(lastDigit);
}
precision += 1;
break loop;
case 1:
case 1022:
lastDigit = lastDigit.divToInt(10);
if(unscaledValue === undefined) {
unscaledValue = lastDigit;
} else {
unscaledValue = unscaledValue.mul(100).add(lastDigit);
}
precision += 2;
break loop;
case 2:
case 1021:
if(unscaledValue === undefined) {
unscaledValue = lastDigit;
} else {
unscaledValue = unscaledValue.mul(1000).add(lastDigit);
}
precision += 3;
break loop;
default:
if(unscaledValue === undefined) {
if((unscaledValue = lastDigit) !== undefined) {
precision += 3;
}
} else {
unscaledValue = unscaledValue.mul(1000).add(lastDigit);
precision += 3;
}
bits -= 10;
lastDigit = new BigInteger(digit - digitAdjust);
}
}
}
scale = precision - exponent;
valueRef[0] = new BigDecimal(unscaledValue, scale);
return srcOffset - originalOffset;
}
}
module.exports = LexDecimal;