agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
113 lines • 5.36 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.roundsTo = exports.scientificDecimalOf = exports.MAX_ROUNDING_SPELLING_CHARS = exports.decimalsOf = void 0;
/** The shared precision rule for existing figures and opt-in plain decimals,
* and the one exact reading of a number written in scientific notation. */
/** Decimal places the answer wrote the value with. */
function decimalsOf(value) {
const dot = value.indexOf('.');
return dot === -1 ? 0 : value.length - dot - 1;
}
exports.decimalsOf = decimalsOf;
/** A source spelling is kept only within this bound, never silently clipped. */
exports.MAX_ROUNDING_SPELLING_CHARS = 256;
/** Exact decimal parts, including the exponent a finite JS number can print. */
function decimalOf(spelling) {
if (spelling.length > exports.MAX_ROUNDING_SPELLING_CHARS)
return undefined;
const match = /^([-+]?)(\d+)(?:\.(\d+))?(?:e([-+]?\d+))?$/i.exec(spelling);
if (match?.[2] === undefined)
return undefined;
const exponent = Number(match[4] ?? 0);
// Native finite numbers print exponents from -324 to308. This also bounds
// every power below when this internal helper receives malformed input.
if (!Number.isInteger(exponent) || Math.abs(exponent) > 400)
return undefined;
const fractional = match[3] ?? '';
const magnitude = BigInt(match[2] + fractional);
return {
coefficient: match[1] === '-' ? -magnitude : magnitude,
scale: fractional.length - exponent,
};
}
/**
* One whole token in scientific notation: `1.12e-02`, `3.358E+03`, `-5e7`.
* Anchored, so a lookalike glued to letters or structure — a hash
* (`9f3e10ab`), a version (`1.2e3-rc`), an id (`host-1e5`) — never matches:
* the tokenizer's boundaries decide what stands alone.
*/
const SCIENTIFIC = /^[-+]?\d+(?:\.\d+)?e[-+]?\d+$/i;
/**
* Read one standalone scientific spelling as the decimal it states — exactly,
* through `decimalOf` (BigInt, no Number conversion) and within its bounds:
* the spelling at most `MAX_ROUNDING_SPELLING_CHARS` characters, its exponent
* within ±400, and the decimal it states within the same character bound.
* `undefined` for any other token; outside the bounds a token stays a
* spelling only.
*
* @example
* ```ts
* scientificDecimalOf('1.12e-02'); // { exact: '0.0112', written: '0.0112' }
* scientificDecimalOf('1.2E+21'); // { exact: '1200000000000000000000' }
* scientificDecimalOf('9f3e10ab'); // undefined — a hash, not a number
* ```
*/
function scientificDecimalOf(spelling) {
if (!SCIENTIFIC.test(spelling))
return undefined;
const decimal = decimalOf(spelling);
if (decimal === undefined)
return undefined;
const exact = plainOf(withoutTrailingZeros(decimal));
const written = decimal.scale > 0 ? plainOf(decimal) : undefined;
// `written` keeps every fractional digit `exact` drops, so it is the longer.
if ((written ?? exact).length > exports.MAX_ROUNDING_SPELLING_CHARS)
return undefined;
return written === undefined ? { exact } : { exact, written };
}
exports.scientificDecimalOf = scientificDecimalOf;
/** The same value with every trailing fractional zero dropped. */
function withoutTrailingZeros({ coefficient, scale }) {
let reduced = coefficient;
let places = scale;
while (places > 0 && reduced % 10n === 0n) {
reduced /= 10n;
places -= 1;
}
return { coefficient: reduced, scale: places };
}
/** `coefficient × 10^-scale` written out, with exactly `max(scale, 0)` fractional digits. */
function plainOf({ coefficient, scale }) {
const sign = coefficient < 0n ? '-' : '';
const digits = (coefficient < 0n ? -coefficient : coefficient).toString();
if (scale <= 0)
return digits === '0' ? '0' : `${sign}${digits}${'0'.repeat(-scale)}`;
const padded = digits.padStart(scale + 1, '0');
return `${sign}${padded.slice(0, -scale)}.${padded.slice(-scale)}`;
}
/** True inside the answer's inclusive half-unit rounding interval.
* Existing figures keep their exact historical numeric allowance. New plain
* decimals supply original spellings and use the same interval without slack
* or lossy Number subtraction. There is one rounding rule and one owner. */
function roundsTo(x, f, decimals, spellings) {
if (spellings === undefined) {
const half = 0.5 * 10 ** -decimals;
return Math.abs(x - f) <= half * (1 + 1e-9) + 1e-12;
}
if (!Number.isInteger(decimals) || decimals < 0 || decimals > exports.MAX_ROUNDING_SPELLING_CHARS) {
return false;
}
const source = decimalOf(spellings.source);
const answer = decimalOf(spellings.answer);
if (source === undefined || answer === undefined)
return false;
const scale = Math.max(source.scale, answer.scale, decimals + 1);
// With256-character spellings and exponents capped at400, powers are
// bounded at1056 decimal places before any BigInt exponentiation.
const difference = source.coefficient * 10n ** BigInt(scale - source.scale) -
answer.coefficient * 10n ** BigInt(scale - answer.scale);
const magnitude = difference < 0n ? -difference : difference;
return magnitude <= 5n * 10n ** BigInt(scale - decimals - 1);
}
exports.roundsTo = roundsTo;
//# sourceMappingURL=numberPrecision.js.map