Clean-up code from PR #1087

Closes #1085
This commit is contained in:
Jürg Lehni 2016-07-09 01:01:19 +02:00
parent 1914e64e4b
commit 02658c9e74
3 changed files with 96 additions and 44 deletions

View file

@ -60,6 +60,7 @@ var Numerical = new function() {
var abs = Math.abs, var abs = Math.abs,
sqrt = Math.sqrt, sqrt = Math.sqrt,
pow = Math.pow, pow = Math.pow,
// Constants
EPSILON = 1e-12, EPSILON = 1e-12,
MACHINE_EPSILON = 1.12e-16; MACHINE_EPSILON = 1.12e-16;
@ -67,23 +68,37 @@ var Numerical = new function() {
return value < min ? min : value > max ? max : value; return value < min ? min : value > max ? max : value;
} }
function splitDouble(X) { function getDiscriminant(a, b, c) {
var bigX = X * 134217729, // X*(2^27 + 1) // Ported from @hkrish's polysolve.c
Y = X - bigX, function split(a) {
Xh = Y + bigX; // Don't optimize Y away! var x = a * 134217729,
return [Xh, X - Xh]; y = a - x,
hi = y + x, // Don't optimize y away!
lo = a - hi;
return [hi, lo];
}
var D = b * b - a * c,
E = b * b + a * c;
if (abs(D) * 3 < E) {
var ad = split(a),
bd = split(b),
cd = split(c),
p = b * b,
dp = (bd[0] * bd[0] - p + 2 * bd[0] * bd[1]) + bd[1] * bd[1],
q = a * c,
dq = (ad[0] * cd[0] - q + ad[0] * cd[1] + ad[1] * cd[0])
+ ad[1] * cd[1];
D = (p - q) + (dp - dq); // Dont omit parentheses!
}
return D;
} }
function higherPrecisionDiscriminant(a, b, c) { function getNormalizationFactor(x) {
var ad = splitDouble(a), // Normalization is done by scaling coefficients with a power of 2, so
bd = splitDouble(b), // that all the bits in the mantissa remain unchanged.
cd = splitDouble(c), return pow(2, -Math.floor(
p = b * b, Math.log(x || MACHINE_EPSILON) * Math.LOG2E + 0.5));
dp = (bd[0] * bd[0] - p + 2 * bd[0] * bd[1]) + bd[1] * bd[1],
q = a * c,
dq = (ad[0] * cd[0] - q + ad[0] * cd[1] + ad[1] * cd[0])
+ ad[1] * cd[1];
return (p - q) + (dp - dq);
} }
return /** @lends Numerical */{ return /** @lends Numerical */{
@ -241,31 +256,21 @@ var Numerical = new function() {
eMax = max + EPSILON, eMax = max + EPSILON,
x1, x2 = Infinity, x1, x2 = Infinity,
B = b, B = b,
D, E, pi = 3; D, E;
// a, b, c are expected to be the coefficients of the equation: // a, b, c are expected to be the coefficients of the equation:
// Ax² - 2Bx + C == 0, so we take b = -B/2: // Ax² - 2Bx + C == 0, so we take b = -B/2:
b /= -2; b *= -0.5;
D = b * b - a * c; // Discriminant D = getDiscriminant(a, b, c);
E = b * b + a * c;
if (pi * abs(D) < E) {
D = higherPrecisionDiscriminant(a, b, c);
}
// If the discriminant is very small, we can try to pre-condition // If the discriminant is very small, we can try to pre-condition
// the coefficients, so that we may get better accuracy // the coefficients, so that we may get better accuracy
if (D !== 0 && abs(D) < MACHINE_EPSILON) { if (D && abs(D) < MACHINE_EPSILON) {
// If the geometric mean of the coefficients is small enough // Normalize coefficients.
var sc = (abs(a) + abs(b) + abs(c)) || MACHINE_EPSILON; var f = getNormalizationFactor(abs(a) + abs(b) + abs(c));
sc = pow(2, -Math.floor(Math.log(sc) * Math.LOG2E + 0.5)); a *= f;
a *= sc; b *= f;
b *= sc; c *= f;
c *= sc; B *= f;
// Recalculate the discriminant D = getDiscriminant(a, b, c);
D = b * b - a * c;
E = b * b + a * c;
B = - 2.0 * b;
if (pi * abs(D) < E) {
D = higherPrecisionDiscriminant(a, b, c);
}
} }
if (abs(a) < EPSILON) { if (abs(a) < EPSILON) {
// This could just be a linear equation // This could just be a linear equation
@ -326,18 +331,18 @@ var Numerical = new function() {
* @author Harikrishnan Gopalakrishnan <hari.exeption@gmail.com> * @author Harikrishnan Gopalakrishnan <hari.exeption@gmail.com>
*/ */
solveCubic: function(a, b, c, d, roots, min, max) { solveCubic: function(a, b, c, d, roots, min, max) {
var count = 0, x, b1, c2, var x, b1, c2,
s = Math.max(abs(a), abs(b), abs(c), abs(d)); s = Math.max(abs(a), abs(b), abs(c), abs(d));
// Normalise coefficients a la Jenkins & Traub's RPOLY // Normalize coefficients à la Jenkins & Traub's RPOLY
if ((s < 1e-7 && s > 0) || s > 1e7) { if (s < 1e-8 || s > 1e8) {
// Scale the coefficients by a multiple of the exponent of // Scale the coefficients by a multiple of the exponent of
// coefficients so that all the bits in the mantissa are // coefficients so that all the bits in the mantissa are
// preserved. // preserved.
var p = pow(2, -Math.floor(Math.log(s) * Math.LOG2E)); var f = getNormalizationFactor(s);
a *= p; a *= f;
b *= p; b *= f;
c *= p; c *= f;
d *= p; d *= f;
} }
// 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.

45
test/tests/Numerical.js Normal file
View file

@ -0,0 +1,45 @@
/*
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
* http://paperjs.org/
*
* Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
* http://scratchdisk.com/ & http://jonathanpuckey.com/
*
* Distributed under the MIT license. See LICENSE file for details.
*
* All rights reserved.
*/
QUnit.module('Numerical');
test('Numerical.solveQuadratic()', function() {
function solve(s) {
var roots = [],
count = Numerical.solveQuadratic(s, 0, -s, roots);
return roots;
}
var expected = [1, -1];
equals(solve(1), expected,
'Numerical.solveQuadratic().');
equals(solve(Numerical.EPSILON), expected,
'Numerical.solveQuadratic() with an identical set of' +
'coefficients at different scale.');
});
test('Numerical.solveCubic()', function() {
function solve(s) {
var roots = [],
count = Numerical.solveCubic(0.5 * s, -s, -s, -s, roots);
return roots;
}
var expected = [2.919639565839418];
equals(solve(1), expected,
'Numerical.solveCubic().');
equals(solve(Numerical.EPSILON), expected,
'Numerical.solveCubic() with an identical set of' +
'coefficients at different scale.');
});

View file

@ -58,3 +58,5 @@
/*#*/ include('SvgImport.js'); /*#*/ include('SvgImport.js');
/*#*/ include('SvgExport.js'); /*#*/ include('SvgExport.js');
/*#*/ include('Numerical.js');