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

"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;
}
};
};