flo-poly
Version:
A practical, root-focused JavaScript polynomial utility library.
115 lines (98 loc) • 5.24 kB
text/typescript
import { EFTHorner as EFTHorner_ } from "./eft-horner.js";
import { hornerWithRunningError as hornerWithRunningError_ } from "./horner-with-running-error.js";
import { Horner as Horner_ } from "./horner.js";
import { AbsHorner as AbsHorner_ } from "./abs-horner.js";
import { γ as γ_ } from "../../error-analysis/gamma.js";
// We *have* to do the below❗ The assignee is a getter❗ The assigned is a pure function❗ Otherwise code is too slow❗
const γ = γ_;
const EFTHorner = EFTHorner_;
const hornerWithRunningError = hornerWithRunningError_;
const Horner = Horner_;
const AbsHorner = AbsHorner_;
const γ1 = γ(1);
const γ2 = γ(2);
/**
* Returns the result of evaluating the given polynomial (with specified
* coefficient-wise error bounds) at x such that the sign is correct when
* positive or negative and undecided when 0 - an additional `multiplier`
* parameter can enforce additional bits (beyond the sign) to be correct.
*
* * designed to be fast in 'easy' cases (say condition number < 2^53) and
* harder cases (condition number < 2^106) since nearly all typical
* calculations will have condition number < 2^106
* * a staggered approach is used - first double precision, then simulated
* double-double precision (i.e. once compensated Horner evluation) is tried
* before giving up and returning 0 - see point below
* * if zero is returned then the calculated result is too close to 0 to
* determine the sign; the caller of this function can then resort to a more
* accurate (possibly exact) evaluation
*
* @param p an array of 2 polynomials with coefficients given densely as an
* array of double precision floating point numbers from highest to
* lowest power, e.g. `[5,-3,0]` represents the polynomial `5x^2 - 3x`;
* the first polynomial's coefficients represent the 'high part' (a double) of a
* double-double precision value, while the second polynomial's coefficients
* represent the 'low part', i.e. designating `hp` for high part and `lp` for
* low part it must be that they are non-overlapping -> `twoSum(lp,hp)` will
* equal `[lp,hp]`; put another way, if the given polynomial is given as e.g. a
* linear polynomial with coefficients in double precision,
* e.g. `[[1.7053025658242404e-13, 2354.33721613], [-7.105427357601002e-15,284.5673337]]`
* then this parameter, `p`, should be `[[2354.33721613], 284.5673337], [1.7053025658242404e-13, -7.105427357601002e-15]]`
* which is simply the result of transposing the original polynomial if it is
* seen as a matrix
* @param pE defaults to `undefined`; an error polynomial that provides a
* coefficient-wise error bound on the input polynomial; all coefficients must
* be positive; if `undefined` then the input polynomial will be assumed exact
* @param x the value at which to evaluate the polynomial
* @param multiplier defaults to 1; the final calculation error needs to be a
* multiple of this number smaller than the evaluated value, otherwise zero is
* returned - useful if not only the sign is important but also some bits, e.g.
* if multiplier = 8 then 3 bits will have to be correct otherwise 0 is returned
*
* @doc
*/
function evalCertified(
p: number[][],
x: number,
pE: number[] | undefined = undefined,
multiplier = 1): number {
const absX = Math.abs(x);
const p0 = p[0];
// first do a fast evaluation
const [r,e1] = hornerWithRunningError(p0,x);
// inlined above line:
//const r = p0[0]; const e1 = Math.abs(r) / 2; for (const i=1; i<p0.length; i++) { r = r*x + p0[i]; e1 = Math.abs(x)*e1 + Math.abs(r); } e1 = Number.EPSILON * (2*e1 - Math.abs(r));
/** the error due to not considering p[1] */
// the line below was changed due to negative values of x now also allowed
const e2 = γ2*AbsHorner(p0,absX);
// inlined above line:
//const e2 = abs(p0[0]); for (const i=1; i<p0.length; i++) { e2 = e2*x + abs(p0[i]); }
/** error due to imprecision in coefficients */
// the line below was changed due to negative values of x now also allowed
const E = pE !== undefined ? Horner(pE,absX) : 0;
//const E = p0[0]; for (const i=1; i<p0.length; i++) {E = E*x + p0[i]; }
const ee = e1+e2+E; // in difficult cases E can be larger than e1+e2
if (ee*multiplier < Math.abs(r)) {
// we are within bounds
return r;
}
// error is too large - do a more precise evaluation (i.e. once compensated
// with K === 2)
const EFTHorner_ = EFTHorner(p0,x);
const { pπ, pσ } = EFTHorner_;
let { r̂ } = EFTHorner_;
const [C1,c1] = hornerWithRunningError(pπ, x);
const [C2,c2] = hornerWithRunningError(pσ, x);
const [C3,c3] = hornerWithRunningError(p[1], x);
// typically: c1,c2 < c3 < E
let e = (c1 + c2 + c3) + E;
// typically: C1,C2 < C3 < r̂ and (C1 + C2 + C3 < r̂)
r̂ = (C1 + C2 + C3) + r̂;
e += γ1*r̂;
if (e*multiplier < Math.abs(r̂)) {
return r̂;
}
// error is still too large to return the correct sign (if multiplier === 1)
return 0;
}
export { evalCertified }