2,249 bytes · the TypeScript implementation · view raw
const POW10 = [1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12];
// 2^52: at or above this every double is a whole number, so a scaled value// this large has no digits left to round.const INTEGRAL = 4503599627370496;
// Veltkamp's splitting constant, 2^27 + 1.const SPLIT = 134217729;
/** * The rounding error of a * b: a * b - fl(a * b), exactly (Dekker's * TwoProduct). Only * and - are used, so it needs no fused multiply-add, * which JavaScript does not have. */function productError(a: number, b: number, p: number): number {
const ca = SPLIT * a;
const ah = ca - (ca - a);
const al = a - ah;
const cb = SPLIT * b;
const bh = cb - (cb - b);
const bl = b - bh;
return ((ah * bh - p) + ah * bl + al * bh) + al * bl;
}
/** * Round half away from zero to `decimals` places, deciding on the exact value * of the double. * * 2.675 is stored as 2.67499999999999982236431605997495353221893310546875, * so it rounds to 2.67, as JavaScript's toFixed(2) says. The shortcut * `Math.round(x * 100) / 100` says 2.68, because 2.675 * 100 rounds to exactly * 267.5 before Math.round sees it; it also rounds -2.5 to -2. Here the * product's rounding error is recovered exactly (TwoProduct), so the tie test * is on the true value, using only +, -, * and floor, which IEEE 754 defines * exactly in every language. */exportfunction roundFloat(value: number, decimals: number): number {
if (typeof value !== "number" || !Number.isFinite(value)) {
thrownew TypeError(`value must be a finite number, received ${value}`);
}
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 12) {
thrownew RangeError(`decimals must be a whole number from 0 to 12, received ${decimals}`);
}
const scale = POW10[decimals];
const a = Math.abs(value);
const y = a * scale;
if (y >= INTEGRAL) return value + 0;
let r = Math.floor(y);
// y - r is exact, and so is subtracting a half from it; adding the product's// error cannot change the sign of the answer, only settle a tie.const above = (y - r - 0.5) + productError(a, scale, y);
if (above >= 0) r += 1;
const out = r / scale;
// + 0 turns -0 into 0, so -0.001 rounded to 2 places is 0.return (value < 0 ? -out : out) + 0;
}