Add a low level quadratic root solver
This is a solver based on W. Kahan's solver. It will be
used as part of Skia's multiple existing solvers.
Change-Id: Ib95c5516f1260e5394a8cc072987c14559bc84b6
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/705225
Commit-Queue: Herb Derby <herb@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/src/base/SkQuads.cpp b/src/base/SkQuads.cpp
index 0997033..8152c77 100644
--- a/src/base/SkQuads.cpp
+++ b/src/base/SkQuads.cpp
@@ -6,6 +6,7 @@
*/
#include "src/base/SkQuads.h"
+#include "include/private/base/SkAssert.h"
#include "include/private/base/SkFloatingPoint.h"
#include <cmath>
@@ -92,6 +93,26 @@
return discriminant;
}
+SkQuads::RootResult SkQuads::Roots(double A, double B, double C) {
+ SkASSERT(A != 0);
+
+ const double discriminant = Discriminant(A, B, C);
+
+ if (discriminant == 0) {
+ const double root = B / A;
+ return {discriminant, root, root};
+ }
+
+ if (discriminant > 0) {
+ const double D = sqrt(discriminant);
+ const double R = B > 0 ? B + D : B - D;
+ return {discriminant, R / A, C / R};
+ }
+
+ // The discriminant is negative or is not finite.
+ return {discriminant, NAN, NAN};
+}
+
int SkQuads::RootsReal(const double A, const double B, const double C, double solution[2]) {
if (close_to_linear(A, B)) {
return solve_linear(B, C, solution);
diff --git a/src/base/SkQuads.h b/src/base/SkQuads.h
index 2cdc4ac..ec9918f 100644
--- a/src/base/SkQuads.h
+++ b/src/base/SkQuads.h
@@ -29,6 +29,32 @@
*/
static double Discriminant(double A, double B, double C);
+ struct RootResult {
+ double discriminant;
+ double root0;
+ double root1;
+ };
+
+ /**
+ * Calculate the roots of a quadratic.
+ * Given
+ * A*t^2 -2*B*t + C = 0,
+ * calculate the roots.
+ *
+ * This does not try to detect a linear configuration of the equation, or detect if the two
+ * roots are the same. It returns the discriminant and the two roots.
+ *
+ * Not this uses a different form the quadratic equation to reduce rounding error. Give
+ * standard A, B, C. You can call this root finder with:
+ * Roots(A, -0.5*B, C)
+ * to find the roots of A*x^2 + B*x + C.
+ *
+ * The method used to calculate the roots is from
+ * "On the Cost of Floating-Point Computation Without Extra-Precise Arithmetic"
+ * by W. Kahan.
+ */
+ static RootResult Roots(double A, double B, double C);
+
/**
* Puts up to 2 real solutions to the equation
* A*t^2 + B*t + C = 0
diff --git a/tests/QuadRootsTest.cpp b/tests/QuadRootsTest.cpp
index 34eac57..3397d59 100644
--- a/tests/QuadRootsTest.cpp
+++ b/tests/QuadRootsTest.cpp
@@ -213,3 +213,55 @@
F[1] = F[0];
}
}
+
+DEF_TEST(QuadRoots_Basic, reporter) {
+ {
+ // (x - 1) (x - 1) normal quadratic form A = 1, B = 2, C =1.
+ auto [discriminant, r0, r1] = SkQuads::Roots(1, -0.5 * -2, 1);
+ REPORTER_ASSERT(reporter, discriminant == 0);
+ REPORTER_ASSERT(reporter, r0 == 1 && r1 == 1);
+ }
+
+ {
+ // (x + 2) (x + 2) normal quadratic form A = 1, B = 4, C = 4.
+ auto [discriminant, r0, r1] = SkQuads::Roots(1, -0.5 * 4, 4);
+ REPORTER_ASSERT(reporter, discriminant == 0);
+ REPORTER_ASSERT(reporter, r0 == -2 && r1 == -2);
+ }
+}
+
+// Test the roots using
+// Use quadratics of the form F_n * x^2 - 2 * F_(n-1) * x + F_(n-2).
+// The roots are (F_(n–1) ± 1)/F_n if n is even otherwise there are no roots.
+DEF_TEST(QuadRoots_Fibonacci, reporter) {
+ // n, n-1, n-2
+ int64_t F[] = {1, 1, 0};
+ // F_79 just fits in the 53 significant bits of a double.
+ for (int i = 2; i < 79; ++i) {
+ F[0] = F[1] + F[2];
+
+ const int expectedDiscriminant = i % 2 == 0 ? 1 : -1;
+ auto [discriminant, r0, r1] = SkQuads::Roots(F[0], F[1], F[2]);
+ REPORTER_ASSERT(reporter, discriminant == expectedDiscriminant);
+
+ // There are only real roots when i is even.
+ if (i % 2 == 0) {
+ const double expectedLittle = ((double)F[1] - 1) / F[0];
+ const double expectedBig = ((double)F[1] + 1) / F[0];
+ if (r0 <= r1) {
+ REPORTER_ASSERT(reporter, r0 == expectedLittle);
+ REPORTER_ASSERT(reporter, r1 == expectedBig);
+ } else {
+ REPORTER_ASSERT(reporter, r1 == expectedLittle);
+ REPORTER_ASSERT(reporter, r0 == expectedBig);
+ }
+ } else {
+ REPORTER_ASSERT(reporter, std::isnan(r0));
+ REPORTER_ASSERT(reporter, std::isnan(r1));
+ }
+
+ F[2] = F[1];
+ F[1] = F[0];
+ }
+}
+