You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
84 lines
2.2 KiB
JavaScript
84 lines
2.2 KiB
JavaScript
2 years ago
|
"use strict";
|
||
|
|
||
|
const assert = require("assert");
|
||
|
const bigintLog2 = require("@extra-bigint/log2");
|
||
|
|
||
|
function bitsNeeded(value) {
|
||
|
if (value === 0) {
|
||
|
return 1n;
|
||
|
} else {
|
||
|
return bigintLog2(value) + 1n;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function remainderDivide(number, divideBy) {
|
||
|
let remainder = number % divideBy;
|
||
|
let wholes = (number - remainder) / divideBy;
|
||
|
|
||
|
return [ wholes, remainder ];
|
||
|
}
|
||
|
|
||
|
module.exports = function createArithmeticCoder(fields) {
|
||
|
// NOTE: The fields are order-sensitive! You can *only* add a field to the definition afterwards without breaking decoding of existing values, if you put that new field at the *end*. Ranges of existing fields should never be changed, as this will break decoding.
|
||
|
// NOTE: Minimum is inclusive, maximum is exclusive
|
||
|
// NOTE: For binary sortability, the fields should be ordered from least to most significant
|
||
|
// second, ..., day, ... year, mask, timezone
|
||
|
|
||
|
let nextMultiplier = 1n;
|
||
|
|
||
|
let processedFields = fields.map((field) => {
|
||
|
let minimum = BigInt(field.minimum);
|
||
|
let maximum = BigInt(field.maximum);
|
||
|
let range = maximum - minimum;
|
||
|
|
||
|
let processed = {
|
||
|
offset: minimum,
|
||
|
range: range,
|
||
|
minimum: minimum,
|
||
|
maximum: maximum,
|
||
|
multiplier: nextMultiplier,
|
||
|
name: field.name
|
||
|
};
|
||
|
|
||
|
nextMultiplier = nextMultiplier * range;
|
||
|
|
||
|
return processed;
|
||
|
});
|
||
|
|
||
|
let maximumValue = nextMultiplier;
|
||
|
let reverseFields = processedFields.slice().reverse();
|
||
|
|
||
|
return {
|
||
|
bits: bitsNeeded(maximumValue - 1n),
|
||
|
encode: function (data) {
|
||
|
let number = processedFields.reduce((total, field) => {
|
||
|
let value = data[field.name];
|
||
|
|
||
|
if (value != null) {
|
||
|
let valueN = BigInt(value);
|
||
|
assert(valueN >= field.minimum && valueN < field.maximum);
|
||
|
let normalized = valueN - field.offset;
|
||
|
|
||
|
return total + (normalized * field.multiplier);
|
||
|
} else {
|
||
|
// Effectively store a 0, and assume that the calling code deals with any requiredness constraints and understands how to handle this case
|
||
|
return total;
|
||
|
}
|
||
|
}, 0n);
|
||
|
|
||
|
return number;
|
||
|
},
|
||
|
decode: function (number) {
|
||
|
let result = {};
|
||
|
|
||
|
for (let field of reverseFields) {
|
||
|
let [ wholes, remainder ] = remainderDivide(number, field.multiplier);
|
||
|
number = remainder;
|
||
|
result[field.name] = wholes + field.offset;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
};
|
||
|
};
|