Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
188 lines
6.3 KiB
JavaScript
Executable file
188 lines
6.3 KiB
JavaScript
Executable file
// asuth.
|
|
|
|
/**
|
|
* ASCII-encoding tricks, particularly ordered-base64 encoding for
|
|
* lexicographically ordered things like IndexedDB or 64-bit number support that
|
|
* we can't use JS numbers for.
|
|
*
|
|
* The math logic is by me (asuth); hopefully it's not too embarassing.
|
|
**/
|
|
|
|
define(
|
|
[
|
|
'exports'
|
|
],
|
|
function(
|
|
exports
|
|
) {
|
|
|
|
/**
|
|
* A lexicographically ordered base64 encoding. Our two extra characters are {
|
|
* and } because they are at the top of the ordering space and have a clear (to
|
|
* JS coders) ordering which makes it tractable to eyeball an encoded value and
|
|
* not be completely confused/misled.
|
|
*/
|
|
var ORDERED_ARBITRARY_BASE64_CHARS = [
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
|
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
|
|
'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
|
|
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
|
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
|
|
'y', 'z', '{', '}'
|
|
];
|
|
/**
|
|
* Zero padding to get us up to the maximum encoding length of a 64-bit value in
|
|
* our encoding (11) or for decimal re-conversion (16).
|
|
*/
|
|
var ZERO_PADDING = '0000000000000000';
|
|
|
|
/**
|
|
* Encode a JS int in our base64 encoding.
|
|
*/
|
|
function encodeInt(v, padTo) {
|
|
var sbits = [];
|
|
do {
|
|
// note: bitwise ops are 32-bit only.
|
|
// so, this is fine:
|
|
sbits.push(ORDERED_ARBITRARY_BASE64_CHARS[v & 0x3f]);
|
|
// but this can't be >>> 6 and has to be a divide.
|
|
v = Math.floor(v / 64);
|
|
} while (v > 0);
|
|
sbits.reverse();
|
|
var estr = sbits.join('');
|
|
if (padTo && estr.length < padTo)
|
|
return ZERO_PADDING.substring(0, padTo - estr.length) + estr;
|
|
return estr;
|
|
}
|
|
exports.encodeInt = encodeInt;
|
|
|
|
/**
|
|
* 10^14 >> 14 so that its 'lowest' binary 1 ends up in the one's place. It
|
|
* is encoded in 33 bits itself.
|
|
*/
|
|
var E10_14_RSH_14 = Math.pow(10, 14) / Math.pow(2, 14),
|
|
P2_14 = Math.pow(2, 14),
|
|
P2_22 = Math.pow(2, 22),
|
|
P2_32 = Math.pow(2, 32),
|
|
P2_36 = Math.pow(2, 36),
|
|
MASK32 = 0xffffffff;
|
|
|
|
/**
|
|
* Convert a decimal uint64 string to a compact string representation that can
|
|
* be compared using our helper method `cmpUI64`. We could do direct straight
|
|
* string comparison if we were willing to pad all strings out to 11 characters,
|
|
* but that's a lot of overhead considering that we expect a lot of our values
|
|
* to be muuuuch smaller. (Appropriate padding can be requested for cases
|
|
* where the ordering is explicitly desired, like IndexedDB keys. Just only
|
|
* request as many bits as you really need!)
|
|
*
|
|
* JS can handle up to 2^53 reliably which means that for numbers larger than
|
|
* that we will have to do a second parse. For that to work (easily), we need
|
|
* to pick a power of 10 to cut at where the smallest '1' in its binary encoding
|
|
* is at least in the 14th bit so we can pre-shift off 13 bits so when we
|
|
* multiply by 10 we don't go floating point, as it were. (We also need to add
|
|
* in the relevant bits from the lower parse appropriately shifted.)
|
|
*/
|
|
exports.parseUI64 = function p(s, padTo) {
|
|
// 2^53 is 16 digits long, so any string shorter than that can be handled
|
|
// by the built-in logic.
|
|
if (s.length < 16) {
|
|
return encodeInt(parseInt(s, 10));
|
|
}
|
|
|
|
var lowParse = parseInt(s.substring(s.length - 14), 10),
|
|
highParse = parseInt(s.substring(0, s.length - 14), 10),
|
|
// multiply the high parse by our scaled power of 10
|
|
rawHighBits = highParse * E10_14_RSH_14;
|
|
|
|
// Now lowParse's low 14 bits are valid, but everything above that needs to
|
|
// be mixed (by addition) with rawHighBits. We'll mix in 22 bits from
|
|
// rawHighBits to get lowBits to 36 useful bits. The main thing is to lop off
|
|
// the higher bits in rawHighBits that we don't want so they don't go float.
|
|
// We do want the 37rd bit if there was addition overflow to carry to the
|
|
// upper calculation.
|
|
var lowBitsAdded = (((rawHighBits % P2_36) * P2_14) % P2_36 +
|
|
lowParse % P2_36),
|
|
lowBits = lowBitsAdded % P2_36,
|
|
overflow = Math.floor(lowBitsAdded / P2_36) % 2;
|
|
|
|
// We can lop off the low 22-bits of the high bits (since lowBits is taking
|
|
// care of that) and combine that with the bits of low above 36.
|
|
var highBits = Math.floor(rawHighBits / P2_22) +
|
|
Math.floor(lowParse / P2_36) + overflow;
|
|
|
|
var outStr = encodeInt(highBits) + encodeInt(lowBits, 6);
|
|
if (padTo && outStr.length < padTo)
|
|
return ZERO_PADDING.substring(0, padTo - outStr.length) + outStr;
|
|
return outStr;
|
|
};
|
|
|
|
exports.cmpUI64 = function(a, b) {
|
|
// longer equals bigger!
|
|
var c = a.length - b.length;
|
|
if (c !== 0)
|
|
return c;
|
|
|
|
if (a < b)
|
|
return -1;
|
|
else if (a > b)
|
|
return 1;
|
|
return 0;
|
|
};
|
|
|
|
/**
|
|
* Convert the output of `parseUI64` back into a decimal string.
|
|
*/
|
|
exports.decodeUI64 = function d(es) {
|
|
var iNonZero = 0;
|
|
for (;es.charCodeAt(iNonZero) === 48; iNonZero++) {
|
|
}
|
|
if (iNonZero)
|
|
es = es.substring(iNonZero);
|
|
|
|
var v, i;
|
|
// 8 characters is 48 bits, JS can do that internally.
|
|
if (es.length <= 8) {
|
|
v = 0;
|
|
for (i = 0; i < es.length; i++) {
|
|
v = v * 64 + ORDERED_ARBITRARY_BASE64_CHARS.indexOf(es[i]);
|
|
}
|
|
return v.toString(10);
|
|
}
|
|
|
|
// upper-string gets 28 bits (that could hold 30), lower-string gets 36 bits.
|
|
// This is how we did things in encoding is why.
|
|
var ues = es.substring(0, es.length - 6), uv = 0,
|
|
les = es.substring(es.length - 6), lv = 0;
|
|
|
|
for (i = 0; i < ues.length; i++) {
|
|
uv = uv * 64 + ORDERED_ARBITRARY_BASE64_CHARS.indexOf(ues[i]);
|
|
}
|
|
for (i = 0; i < les.length; i++) {
|
|
lv = lv * 64 + ORDERED_ARBITRARY_BASE64_CHARS.indexOf(les[i]);
|
|
}
|
|
|
|
// Do the division to figure out the "high" string from our encoding (a whole
|
|
// number.) Then subtract that whole number off our effective number, leaving
|
|
// us dealing with <53 bits so we can just hand it off to the JS engine.
|
|
|
|
var rsh14val = (uv * P2_22 + Math.floor(lv / P2_14)),
|
|
uraw = rsh14val / E10_14_RSH_14,
|
|
udv = Math.floor(uraw),
|
|
uds = udv.toString();
|
|
|
|
var rsh14Leftover = rsh14val - udv * E10_14_RSH_14,
|
|
lowBitsRemoved = rsh14Leftover * P2_14 + lv % P2_14;
|
|
|
|
var lds = lowBitsRemoved.toString();
|
|
if (lds.length < 14)
|
|
lds = ZERO_PADDING.substring(0, 14 - lds.length) + lds;
|
|
|
|
return uds + lds;
|
|
};
|
|
//d(p('10000000000000000'));
|
|
//d(p('18014398509481984'));
|
|
//d(p('1171221845949812801'));
|
|
|
|
}); // end define
|