Simplify Numerical.solveCubic() code by introducing evaluate() closure.

This commit is contained in:
Jürg Lehni 2016-07-09 13:54:02 +02:00
parent 9d6aab3802
commit da78e837a1

View file

@ -99,12 +99,14 @@ var Numerical = new function() {
} }
function getNormalizationFactor() { function getNormalizationFactor() {
var max = Math.max.apply(Math, arguments);
// Normalize coefficients à la Jenkins & Traub's RPOLY. // Normalize coefficients à la Jenkins & Traub's RPOLY.
// Normalization is done by scaling coefficients with a power of 2, so // Normalization is done by scaling coefficients with a power of 2, so
// that all the bits in the mantissa remain unchanged. // that all the bits in the mantissa remain unchanged.
return max && (max < 1e-8 || max > 1e8) // Use the infinity norm (max(sum(abs(a)…)) to determine the appropriate
? pow(2, -Math.round(log2(max))) // scale factor. See @hkrish in #1087#issuecomment-231526156
var norm = Math.max.apply(Math, arguments);
return norm && (norm < 1e-8 || norm > 1e8)
? pow(2, -Math.round(log2(norm)))
: 0; : 0;
} }
@ -340,13 +342,24 @@ var Numerical = new function() {
*/ */
solveCubic: function(a, b, c, d, roots, min, max) { solveCubic: function(a, b, c, d, roots, min, max) {
var f = getNormalizationFactor(abs(a), abs(b), abs(c), abs(d)), var f = getNormalizationFactor(abs(a), abs(b), abs(c), abs(d)),
x, b1, c2; x, b1, c2, qd, q;
if (f) { if (f) {
a *= f; a *= f;
b *= f; b *= f;
c *= f; c *= f;
d *= f; d *= f;
} }
function evaluate(x0) {
x = x0;
// Evaluate q, q', b1 and c2 at x
var tmp = a * x;
b1 = tmp + b;
c2 = b1 * x + c;
qd = (tmp + b1) * x + c2;
q = c2 * x + d;
}
// If a or d is zero, we only need to solve a quadratic, so we set // If a or d is zero, we only need to solve a quadratic, so we set
// the coefficients appropriately. // the coefficients appropriately.
if (abs(a) < EPSILON) { if (abs(a) < EPSILON) {
@ -359,38 +372,24 @@ var Numerical = new function() {
c2 = c; c2 = c;
x = 0; x = 0;
} else { } else {
var ec = 1 + MACHINE_EPSILON, // 1.000...002
x0, q, qd, t, r, s, tmp;
// Here onwards we iterate for the leftmost root. Proceed to // Here onwards we iterate for the leftmost root. Proceed to
// deflate the cubic into a quadratic (as a side effect to the // deflate the cubic into a quadratic (as a side effect to the
// iteration) and solve the quadratic. // iteration) and solve the quadratic.
x = -(b / a) / 3; evaluate(-(b / a) / 3);
// Evaluate q, q', b1 and c2 at x
tmp = a * x;
b1 = tmp + b;
c2 = b1 * x + c;
qd = (tmp + b1) * x + c2;
q = c2 * x + d;
// Get a good initial approximation. // Get a good initial approximation.
t = q / a; var t = q / a,
r = pow(abs(t), 1/3); r = pow(abs(t), 1/3),
s = t < 0 ? -1 : 1; s = t < 0 ? -1 : 1,
t = -qd / a; td = -qd / a,
// See Kahan's notes on why 1.324718*... works. // See Kahan's notes on why 1.324718*... works.
r = t > 0 ? 1.324717957244746 * Math.max(r, sqrt(t)) : r; rd = td > 0 ? 1.324717957244746 * Math.max(r, sqrt(td)) : r,
x0 = x - s * r; x0 = x - s * rd;
if (x0 !== x) { if (x0 !== x) {
do { do {
x = x0; evaluate(x0);
// Evaluate q, q', b1 and c2 at x // Newton's. Divide by 1 + MACHINE_EPSILON (1.000...002)
tmp = a * x; // to avoid x0 crossing over a root.
b1 = tmp + b; x0 = qd === 0 ? x : x - q / qd / (1 + MACHINE_EPSILON);
c2 = b1 * x + c;
qd = (tmp + b1) * x + c2;
q = c2 * x + d;
// Newton's. Divide by ec to avoid x0 crossing over a
// root.
x0 = qd === 0 ? x : x - q / qd / ec;
} while (s * x0 > s * x); } while (s * x0 > s * x);
// Adjust the coefficients for the quadratic. // Adjust the coefficients for the quadratic.
if (abs(a) * x * x > abs(d / x)) { if (abs(a) * x * x > abs(d / x)) {