xref: /aosp_15_r20/external/skia/tests/QuadRootsTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2023 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkQuads.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSpan.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTypes.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkFloatingPoint.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "src/pathops/SkPathOpsQuad.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "tests/Test.h"
15*c8dee2aaSAndroid Build Coastguard Worker 
16*c8dee2aaSAndroid Build Coastguard Worker #include <algorithm>
17*c8dee2aaSAndroid Build Coastguard Worker #include <cfloat>
18*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
19*c8dee2aaSAndroid Build Coastguard Worker #include <cstddef>
20*c8dee2aaSAndroid Build Coastguard Worker #include <cstdint>
21*c8dee2aaSAndroid Build Coastguard Worker #include <iterator>
22*c8dee2aaSAndroid Build Coastguard Worker #include <limits>
23*c8dee2aaSAndroid Build Coastguard Worker #include <string>
24*c8dee2aaSAndroid Build Coastguard Worker 
testQuadRootsReal(skiatest::Reporter * reporter,const std::string & name,double A,double B,double C,SkSpan<const double> expectedRoots)25*c8dee2aaSAndroid Build Coastguard Worker static void testQuadRootsReal(skiatest::Reporter* reporter, const std::string& name,
26*c8dee2aaSAndroid Build Coastguard Worker                                double A, double B, double C,
27*c8dee2aaSAndroid Build Coastguard Worker                                SkSpan<const double> expectedRoots) {
28*c8dee2aaSAndroid Build Coastguard Worker     skiatest::ReporterContext subtest(reporter, name);
29*c8dee2aaSAndroid Build Coastguard Worker     // Validate test case
30*c8dee2aaSAndroid Build Coastguard Worker     REPORTER_ASSERT(reporter, expectedRoots.size() <= 2,
31*c8dee2aaSAndroid Build Coastguard Worker                     "Invalid test case, up to 2 roots allowed");
32*c8dee2aaSAndroid Build Coastguard Worker 
33*c8dee2aaSAndroid Build Coastguard Worker     for (size_t i = 0; i < expectedRoots.size(); i++) {
34*c8dee2aaSAndroid Build Coastguard Worker         double x = expectedRoots[i];
35*c8dee2aaSAndroid Build Coastguard Worker         // A*x^2 + B*x + C should equal 0
36*c8dee2aaSAndroid Build Coastguard Worker         double y = A * x * x + B * x + C;
37*c8dee2aaSAndroid Build Coastguard Worker         REPORTER_ASSERT(reporter, sk_double_nearly_zero(y),
38*c8dee2aaSAndroid Build Coastguard Worker                     "Invalid test case root %zu. %.16f != 0", i, y);
39*c8dee2aaSAndroid Build Coastguard Worker 
40*c8dee2aaSAndroid Build Coastguard Worker         if (i > 0) {
41*c8dee2aaSAndroid Build Coastguard Worker             REPORTER_ASSERT(reporter, expectedRoots[i-1] <= expectedRoots[i],
42*c8dee2aaSAndroid Build Coastguard Worker                     "Invalid test case root %zu. Roots should be sorted in ascending order", i);
43*c8dee2aaSAndroid Build Coastguard Worker         }
44*c8dee2aaSAndroid Build Coastguard Worker     }
45*c8dee2aaSAndroid Build Coastguard Worker 
46*c8dee2aaSAndroid Build Coastguard Worker     {
47*c8dee2aaSAndroid Build Coastguard Worker         skiatest::ReporterContext subsubtest(reporter, "Pathops Implementation");
48*c8dee2aaSAndroid Build Coastguard Worker         double roots[2] = {0, 0};
49*c8dee2aaSAndroid Build Coastguard Worker         int rootCount = SkDQuad::RootsReal(A, B, C, roots);
50*c8dee2aaSAndroid Build Coastguard Worker         REPORTER_ASSERT(reporter, expectedRoots.size() == size_t(rootCount),
51*c8dee2aaSAndroid Build Coastguard Worker                         "Wrong number of roots returned %zu != %d", expectedRoots.size(),
52*c8dee2aaSAndroid Build Coastguard Worker                         rootCount);
53*c8dee2aaSAndroid Build Coastguard Worker 
54*c8dee2aaSAndroid Build Coastguard Worker         // We don't care which order the roots are returned from the algorithm.
55*c8dee2aaSAndroid Build Coastguard Worker         // For determinism, we will sort them (and ensure the provided solutions are also sorted).
56*c8dee2aaSAndroid Build Coastguard Worker         std::sort(std::begin(roots), std::begin(roots) + rootCount);
57*c8dee2aaSAndroid Build Coastguard Worker         for (int i = 0; i < rootCount; i++) {
58*c8dee2aaSAndroid Build Coastguard Worker             if (sk_double_nearly_zero(expectedRoots[i])) {
59*c8dee2aaSAndroid Build Coastguard Worker                 REPORTER_ASSERT(reporter, sk_double_nearly_zero(roots[i]),
60*c8dee2aaSAndroid Build Coastguard Worker                                 "0 != %.16f at index %d", roots[i], i);
61*c8dee2aaSAndroid Build Coastguard Worker             } else {
62*c8dee2aaSAndroid Build Coastguard Worker                 REPORTER_ASSERT(reporter,
63*c8dee2aaSAndroid Build Coastguard Worker                                 sk_doubles_nearly_equal_ulps(expectedRoots[i], roots[i], 64),
64*c8dee2aaSAndroid Build Coastguard Worker                                 "%.16f != %.16f at index %d", expectedRoots[i], roots[i], i);
65*c8dee2aaSAndroid Build Coastguard Worker             }
66*c8dee2aaSAndroid Build Coastguard Worker         }
67*c8dee2aaSAndroid Build Coastguard Worker     }
68*c8dee2aaSAndroid Build Coastguard Worker     {
69*c8dee2aaSAndroid Build Coastguard Worker         skiatest::ReporterContext subsubtest(reporter, "SkQuads Implementation");
70*c8dee2aaSAndroid Build Coastguard Worker         double roots[2] = {0, 0};
71*c8dee2aaSAndroid Build Coastguard Worker         int rootCount = SkQuads::RootsReal(A, B, C, roots);
72*c8dee2aaSAndroid Build Coastguard Worker         REPORTER_ASSERT(reporter, expectedRoots.size() == size_t(rootCount),
73*c8dee2aaSAndroid Build Coastguard Worker                         "Wrong number of roots returned %zu != %d", expectedRoots.size(),
74*c8dee2aaSAndroid Build Coastguard Worker                         rootCount);
75*c8dee2aaSAndroid Build Coastguard Worker 
76*c8dee2aaSAndroid Build Coastguard Worker         // We don't care which order the roots are returned from the algorithm.
77*c8dee2aaSAndroid Build Coastguard Worker         // For determinism, we will sort them (and ensure the provided solutions are also sorted).
78*c8dee2aaSAndroid Build Coastguard Worker         std::sort(std::begin(roots), std::begin(roots) + rootCount);
79*c8dee2aaSAndroid Build Coastguard Worker         for (int i = 0; i < rootCount; i++) {
80*c8dee2aaSAndroid Build Coastguard Worker             if (sk_double_nearly_zero(expectedRoots[i])) {
81*c8dee2aaSAndroid Build Coastguard Worker                 REPORTER_ASSERT(reporter, sk_double_nearly_zero(roots[i]),
82*c8dee2aaSAndroid Build Coastguard Worker                                 "0 != %.16f at index %d", roots[i], i);
83*c8dee2aaSAndroid Build Coastguard Worker             } else {
84*c8dee2aaSAndroid Build Coastguard Worker                 REPORTER_ASSERT(reporter,
85*c8dee2aaSAndroid Build Coastguard Worker                                 sk_doubles_nearly_equal_ulps(expectedRoots[i], roots[i], 64),
86*c8dee2aaSAndroid Build Coastguard Worker                                 "%.16f != %.16f at index %d", expectedRoots[i], roots[i], i);
87*c8dee2aaSAndroid Build Coastguard Worker             }
88*c8dee2aaSAndroid Build Coastguard Worker         }
89*c8dee2aaSAndroid Build Coastguard Worker     }
90*c8dee2aaSAndroid Build Coastguard Worker }
91*c8dee2aaSAndroid Build Coastguard Worker 
DEF_TEST(QuadRootsReal_ActualQuadratics,reporter)92*c8dee2aaSAndroid Build Coastguard Worker DEF_TEST(QuadRootsReal_ActualQuadratics, reporter) {
93*c8dee2aaSAndroid Build Coastguard Worker     // All answers are given with 16 significant digits (max for a double) or as an integer
94*c8dee2aaSAndroid Build Coastguard Worker     // when the answer is exact.
95*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "two roots 3x^2 - 20x - 40",
96*c8dee2aaSAndroid Build Coastguard Worker                        3, -20, -40,
97*c8dee2aaSAndroid Build Coastguard Worker                        {-1.610798991397109,
98*c8dee2aaSAndroid Build Coastguard Worker                       //-1.610798991397108632474265 from Wolfram Alpha
99*c8dee2aaSAndroid Build Coastguard Worker                          8.277465658063775,
100*c8dee2aaSAndroid Build Coastguard Worker                       // 8.277465658063775299140932 from Wolfram Alpha
101*c8dee2aaSAndroid Build Coastguard Worker                        });
102*c8dee2aaSAndroid Build Coastguard Worker 
103*c8dee2aaSAndroid Build Coastguard Worker     // (2x - 4)(x + 17)
104*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "two roots 2x^2 + 30x - 68",
105*c8dee2aaSAndroid Build Coastguard Worker                        2, 30, -68,
106*c8dee2aaSAndroid Build Coastguard Worker                        {-17, 2});
107*c8dee2aaSAndroid Build Coastguard Worker 
108*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "two roots x^2 - 5",
109*c8dee2aaSAndroid Build Coastguard Worker                        1, 0, -5,
110*c8dee2aaSAndroid Build Coastguard Worker                        {-2.236067977499790,
111*c8dee2aaSAndroid Build Coastguard Worker                       //-2.236067977499789696409174 from Wolfram Alpha
112*c8dee2aaSAndroid Build Coastguard Worker                          2.236067977499790,
113*c8dee2aaSAndroid Build Coastguard Worker                       // 2.236067977499789696409174 from Wolfram Alpha
114*c8dee2aaSAndroid Build Coastguard Worker                        });
115*c8dee2aaSAndroid Build Coastguard Worker 
116*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "one root x^2 - 2x + 1",
117*c8dee2aaSAndroid Build Coastguard Worker                        1, -2, 1,
118*c8dee2aaSAndroid Build Coastguard Worker                        {1});
119*c8dee2aaSAndroid Build Coastguard Worker 
120*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "no roots 5x^2 + 6x + 7",
121*c8dee2aaSAndroid Build Coastguard Worker                        5, 6, 7,
122*c8dee2aaSAndroid Build Coastguard Worker                        {});
123*c8dee2aaSAndroid Build Coastguard Worker 
124*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "no roots 4x^2 + 1",
125*c8dee2aaSAndroid Build Coastguard Worker                        4, 0, 1,
126*c8dee2aaSAndroid Build Coastguard Worker                        {});
127*c8dee2aaSAndroid Build Coastguard Worker 
128*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "one root is zero, another is big",
129*c8dee2aaSAndroid Build Coastguard Worker                        14, -13, 0,
130*c8dee2aaSAndroid Build Coastguard Worker                        {0,
131*c8dee2aaSAndroid Build Coastguard Worker                         0.9285714285714286
132*c8dee2aaSAndroid Build Coastguard Worker                       //0.9285714285714285714285714 from Wolfram Alpha
133*c8dee2aaSAndroid Build Coastguard Worker                         });
134*c8dee2aaSAndroid Build Coastguard Worker 
135*c8dee2aaSAndroid Build Coastguard Worker     // Values from a failing test case observed during testing.
136*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "one root is zero, another is small",
137*c8dee2aaSAndroid Build Coastguard Worker                        0.2929016490705016, 0.0000030451558069, 0,
138*c8dee2aaSAndroid Build Coastguard Worker                        {-0.00001039651301576329, 0});
139*c8dee2aaSAndroid Build Coastguard Worker 
140*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "b and c are zero, a is positive 4x^2",
141*c8dee2aaSAndroid Build Coastguard Worker                        4, 0, 0,
142*c8dee2aaSAndroid Build Coastguard Worker                        {0});
143*c8dee2aaSAndroid Build Coastguard Worker 
144*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "b and c are zero, a is negative -4x^2",
145*c8dee2aaSAndroid Build Coastguard Worker                        -4, 0, 0,
146*c8dee2aaSAndroid Build Coastguard Worker                        {0});
147*c8dee2aaSAndroid Build Coastguard Worker 
148*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "a and b are huge, c is zero",
149*c8dee2aaSAndroid Build Coastguard Worker                        4.3719914983870202e+291, 1.0269509510194551e+152, 0,
150*c8dee2aaSAndroid Build Coastguard Worker                        // One solution is 0, the other is so close to zero it returns
151*c8dee2aaSAndroid Build Coastguard Worker                        // true for sk_double_nearly_zero, so it is collapsed into one.
152*c8dee2aaSAndroid Build Coastguard Worker                        {0});
153*c8dee2aaSAndroid Build Coastguard Worker 
154*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "Very small A B, very large C",
155*c8dee2aaSAndroid Build Coastguard Worker                       0x1p-1055, 0x1.3000006p-1044, -0x1.c000008p+1009,
156*c8dee2aaSAndroid Build Coastguard Worker                       // The roots are not in the range of doubles.
157*c8dee2aaSAndroid Build Coastguard Worker                       {});
158*c8dee2aaSAndroid Build Coastguard Worker }
159*c8dee2aaSAndroid Build Coastguard Worker 
DEF_TEST(QuadRootsReal_Linear,reporter)160*c8dee2aaSAndroid Build Coastguard Worker DEF_TEST(QuadRootsReal_Linear, reporter) {
161*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "positive slope 5x + 6",
162*c8dee2aaSAndroid Build Coastguard Worker                        0, 5, 6,
163*c8dee2aaSAndroid Build Coastguard Worker                        {-1.2});
164*c8dee2aaSAndroid Build Coastguard Worker 
165*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "negative slope -3x - 9",
166*c8dee2aaSAndroid Build Coastguard Worker                        0, -3, -9,
167*c8dee2aaSAndroid Build Coastguard Worker                        {-3.});
168*c8dee2aaSAndroid Build Coastguard Worker }
169*c8dee2aaSAndroid Build Coastguard Worker 
DEF_TEST(QuadRootsReal_Constant,reporter)170*c8dee2aaSAndroid Build Coastguard Worker DEF_TEST(QuadRootsReal_Constant, reporter) {
171*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "No intersections y = -10",
172*c8dee2aaSAndroid Build Coastguard Worker                        0, 0, -10,
173*c8dee2aaSAndroid Build Coastguard Worker                        {});
174*c8dee2aaSAndroid Build Coastguard Worker 
175*c8dee2aaSAndroid Build Coastguard Worker     testQuadRootsReal(reporter, "Infinite solutions y = 0",
176*c8dee2aaSAndroid Build Coastguard Worker                        0, 0, 0,
177*c8dee2aaSAndroid Build Coastguard Worker                        {0.});
178*c8dee2aaSAndroid Build Coastguard Worker }
179*c8dee2aaSAndroid Build Coastguard Worker 
DEF_TEST(QuadRootsReal_NonFiniteNumbers,reporter)180*c8dee2aaSAndroid Build Coastguard Worker DEF_TEST(QuadRootsReal_NonFiniteNumbers, reporter) {
181*c8dee2aaSAndroid Build Coastguard Worker     // The Pathops implementation does not check for infinities nor nans in all cases.
182*c8dee2aaSAndroid Build Coastguard Worker     double roots[2];
183*c8dee2aaSAndroid Build Coastguard Worker     REPORTER_ASSERT(reporter,
184*c8dee2aaSAndroid Build Coastguard Worker         SkQuads::RootsReal(DBL_MAX, 0, DBL_MAX, roots) == 0,
185*c8dee2aaSAndroid Build Coastguard Worker         "Discriminant is negative infinity"
186*c8dee2aaSAndroid Build Coastguard Worker     );
187*c8dee2aaSAndroid Build Coastguard Worker     REPORTER_ASSERT(reporter,
188*c8dee2aaSAndroid Build Coastguard Worker         SkQuads::RootsReal(DBL_MAX, DBL_MAX, DBL_MAX, roots) == 0,
189*c8dee2aaSAndroid Build Coastguard Worker         "Double Overflow"
190*c8dee2aaSAndroid Build Coastguard Worker     );
191*c8dee2aaSAndroid Build Coastguard Worker 
192*c8dee2aaSAndroid Build Coastguard Worker     REPORTER_ASSERT(reporter,
193*c8dee2aaSAndroid Build Coastguard Worker         SkQuads::RootsReal(1, NAN, -3, roots) == 0,
194*c8dee2aaSAndroid Build Coastguard Worker         "Nan quadratic"
195*c8dee2aaSAndroid Build Coastguard Worker     );
196*c8dee2aaSAndroid Build Coastguard Worker     REPORTER_ASSERT(reporter,
197*c8dee2aaSAndroid Build Coastguard Worker         SkQuads::RootsReal(0, NAN, 3, roots) == 0,
198*c8dee2aaSAndroid Build Coastguard Worker         "Nan linear"
199*c8dee2aaSAndroid Build Coastguard Worker     );
200*c8dee2aaSAndroid Build Coastguard Worker     REPORTER_ASSERT(reporter,
201*c8dee2aaSAndroid Build Coastguard Worker         SkQuads::RootsReal(0, 0, NAN, roots) == 0,
202*c8dee2aaSAndroid Build Coastguard Worker         "Nan constant"
203*c8dee2aaSAndroid Build Coastguard Worker     );
204*c8dee2aaSAndroid Build Coastguard Worker }
205*c8dee2aaSAndroid Build Coastguard Worker 
206*c8dee2aaSAndroid Build Coastguard Worker // Test the discriminant using
207*c8dee2aaSAndroid Build Coastguard Worker // Use quadratics of the form F_n * x^2 - 2 * F_(n-1) * x + F_(n-2).
208*c8dee2aaSAndroid Build Coastguard Worker //   This has a discriminant of F_(n-1)^2 - F_n * F_(n-2) = 1 if n is even else -1.
DEF_TEST(QuadDiscriminant_Fibonacci,reporter)209*c8dee2aaSAndroid Build Coastguard Worker DEF_TEST(QuadDiscriminant_Fibonacci, reporter) {
210*c8dee2aaSAndroid Build Coastguard Worker     //            n,  n-1, n-2
211*c8dee2aaSAndroid Build Coastguard Worker     int64_t F[] = {1,   1,   0};
212*c8dee2aaSAndroid Build Coastguard Worker     // F_79 just fits in the 53 significant bits of a double.
213*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 2; i < 79; ++i) {
214*c8dee2aaSAndroid Build Coastguard Worker         F[0] = F[1] + F[2];
215*c8dee2aaSAndroid Build Coastguard Worker 
216*c8dee2aaSAndroid Build Coastguard Worker         const int expectedDiscriminant = i % 2 == 0 ? 1 : -1;
217*c8dee2aaSAndroid Build Coastguard Worker         REPORTER_ASSERT(reporter, SkQuads::Discriminant(F[0], F[1], F[2]) == expectedDiscriminant);
218*c8dee2aaSAndroid Build Coastguard Worker 
219*c8dee2aaSAndroid Build Coastguard Worker         F[2] = F[1];
220*c8dee2aaSAndroid Build Coastguard Worker         F[1] = F[0];
221*c8dee2aaSAndroid Build Coastguard Worker     }
222*c8dee2aaSAndroid Build Coastguard Worker }
223*c8dee2aaSAndroid Build Coastguard Worker 
DEF_TEST(QuadRoots_Basic,reporter)224*c8dee2aaSAndroid Build Coastguard Worker DEF_TEST(QuadRoots_Basic, reporter) {
225*c8dee2aaSAndroid Build Coastguard Worker     {
226*c8dee2aaSAndroid Build Coastguard Worker         // (x - 1) (x - 1) normal quadratic form A = 1, B = 2, C =1.
227*c8dee2aaSAndroid Build Coastguard Worker         auto [discriminant, r0, r1] = SkQuads::Roots(1, -0.5 * -2, 1);
228*c8dee2aaSAndroid Build Coastguard Worker         REPORTER_ASSERT(reporter, discriminant == 0);
229*c8dee2aaSAndroid Build Coastguard Worker         REPORTER_ASSERT(reporter, r0 == 1 && r1 == 1);
230*c8dee2aaSAndroid Build Coastguard Worker     }
231*c8dee2aaSAndroid Build Coastguard Worker 
232*c8dee2aaSAndroid Build Coastguard Worker     {
233*c8dee2aaSAndroid Build Coastguard Worker         // (x + 2) (x + 2) normal quadratic form A = 1, B = 4, C = 4.
234*c8dee2aaSAndroid Build Coastguard Worker         auto [discriminant, r0, r1] = SkQuads::Roots(1, -0.5 * 4, 4);
235*c8dee2aaSAndroid Build Coastguard Worker         REPORTER_ASSERT(reporter, discriminant == 0);
236*c8dee2aaSAndroid Build Coastguard Worker         REPORTER_ASSERT(reporter, r0 == -2 && r1 == -2);
237*c8dee2aaSAndroid Build Coastguard Worker     }
238*c8dee2aaSAndroid Build Coastguard Worker }
239*c8dee2aaSAndroid Build Coastguard Worker 
240*c8dee2aaSAndroid Build Coastguard Worker // Test the roots using
241*c8dee2aaSAndroid Build Coastguard Worker // Use quadratics of the form F_n * x^2 - 2 * F_(n-1) * x + F_(n-2).
242*c8dee2aaSAndroid Build Coastguard Worker // The roots are (F_(n–1) ± 1)/F_n if n is even otherwise there are no roots.
DEF_TEST(QuadRoots_Fibonacci,reporter)243*c8dee2aaSAndroid Build Coastguard Worker DEF_TEST(QuadRoots_Fibonacci, reporter) {
244*c8dee2aaSAndroid Build Coastguard Worker     //            n,  n-1, n-2
245*c8dee2aaSAndroid Build Coastguard Worker     int64_t F[] = {1,   1,   0};
246*c8dee2aaSAndroid Build Coastguard Worker     // F_79 just fits in the 53 significant bits of a double.
247*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 2; i < 79; ++i) {
248*c8dee2aaSAndroid Build Coastguard Worker         F[0] = F[1] + F[2];
249*c8dee2aaSAndroid Build Coastguard Worker 
250*c8dee2aaSAndroid Build Coastguard Worker         const int expectedDiscriminant = i % 2 == 0 ? 1 : -1;
251*c8dee2aaSAndroid Build Coastguard Worker         auto [discriminant, r0, r1] = SkQuads::Roots(F[0], F[1], F[2]);
252*c8dee2aaSAndroid Build Coastguard Worker         REPORTER_ASSERT(reporter, discriminant == expectedDiscriminant);
253*c8dee2aaSAndroid Build Coastguard Worker 
254*c8dee2aaSAndroid Build Coastguard Worker         // There are only real roots when i is even.
255*c8dee2aaSAndroid Build Coastguard Worker         if (i % 2 == 0) {
256*c8dee2aaSAndroid Build Coastguard Worker         const double expectedLittle = ((double)F[1] - 1) / F[0];
257*c8dee2aaSAndroid Build Coastguard Worker         const double expectedBig = ((double)F[1] + 1) / F[0];
258*c8dee2aaSAndroid Build Coastguard Worker             if (r0 <= r1) {
259*c8dee2aaSAndroid Build Coastguard Worker                 REPORTER_ASSERT(reporter, r0 == expectedLittle);
260*c8dee2aaSAndroid Build Coastguard Worker                 REPORTER_ASSERT(reporter, r1 == expectedBig);
261*c8dee2aaSAndroid Build Coastguard Worker             } else {
262*c8dee2aaSAndroid Build Coastguard Worker                 REPORTER_ASSERT(reporter, r1 == expectedLittle);
263*c8dee2aaSAndroid Build Coastguard Worker                 REPORTER_ASSERT(reporter, r0 == expectedBig);
264*c8dee2aaSAndroid Build Coastguard Worker             }
265*c8dee2aaSAndroid Build Coastguard Worker         } else {
266*c8dee2aaSAndroid Build Coastguard Worker             REPORTER_ASSERT(reporter, std::isnan(r0));
267*c8dee2aaSAndroid Build Coastguard Worker             REPORTER_ASSERT(reporter, std::isnan(r1));
268*c8dee2aaSAndroid Build Coastguard Worker         }
269*c8dee2aaSAndroid Build Coastguard Worker 
270*c8dee2aaSAndroid Build Coastguard Worker         F[2] = F[1];
271*c8dee2aaSAndroid Build Coastguard Worker         F[1] = F[0];
272*c8dee2aaSAndroid Build Coastguard Worker     }
273*c8dee2aaSAndroid Build Coastguard Worker }
274*c8dee2aaSAndroid Build Coastguard Worker 
275*c8dee2aaSAndroid Build Coastguard Worker // These are test cases used in the paper "The Ins and Outs of Solving Quadratic Equations with
276*c8dee2aaSAndroid Build Coastguard Worker // Floating-Point Arithmetic" located at
277*c8dee2aaSAndroid Build Coastguard Worker // https://github.com/goualard-f/QuadraticEquation.jl/blob/main/test/tests.jl
278*c8dee2aaSAndroid Build Coastguard Worker 
279*c8dee2aaSAndroid Build Coastguard Worker struct TestCase {
280*c8dee2aaSAndroid Build Coastguard Worker     const double A;
281*c8dee2aaSAndroid Build Coastguard Worker     const double B;
282*c8dee2aaSAndroid Build Coastguard Worker     const double C;
283*c8dee2aaSAndroid Build Coastguard Worker     const double answerLo;
284*c8dee2aaSAndroid Build Coastguard Worker     const double answerHi;
285*c8dee2aaSAndroid Build Coastguard Worker };
286*c8dee2aaSAndroid Build Coastguard Worker 
DEF_TEST(QuadRoots_Hard,reporter)287*c8dee2aaSAndroid Build Coastguard Worker DEF_TEST(QuadRoots_Hard, reporter) {
288*c8dee2aaSAndroid Build Coastguard Worker     const double nan = std::numeric_limits<double>::quiet_NaN();
289*c8dee2aaSAndroid Build Coastguard Worker     const double infinity = std::numeric_limits<double>::infinity();
290*c8dee2aaSAndroid Build Coastguard Worker 
291*c8dee2aaSAndroid Build Coastguard Worker     auto specialEqual = [] (double actual, double test) {
292*c8dee2aaSAndroid Build Coastguard Worker         if (std::isnan(actual)) {
293*c8dee2aaSAndroid Build Coastguard Worker             return std::isnan(test);
294*c8dee2aaSAndroid Build Coastguard Worker         }
295*c8dee2aaSAndroid Build Coastguard Worker 
296*c8dee2aaSAndroid Build Coastguard Worker         if (std::isinf(actual)) {
297*c8dee2aaSAndroid Build Coastguard Worker             return std::isinf(test);
298*c8dee2aaSAndroid Build Coastguard Worker         }
299*c8dee2aaSAndroid Build Coastguard Worker 
300*c8dee2aaSAndroid Build Coastguard Worker         // Comparison function from the paper "The Ins and Outs ...."
301*c8dee2aaSAndroid Build Coastguard Worker         const double errorFactor = std::sqrt(std::numeric_limits<double>::epsilon());
302*c8dee2aaSAndroid Build Coastguard Worker         return std::abs(test - actual) <= errorFactor * std::max(std::abs(test), std::abs(actual));
303*c8dee2aaSAndroid Build Coastguard Worker     };
304*c8dee2aaSAndroid Build Coastguard Worker 
305*c8dee2aaSAndroid Build Coastguard Worker     auto p2 = [](double a) {
306*c8dee2aaSAndroid Build Coastguard Worker         return std::exp2(a);
307*c8dee2aaSAndroid Build Coastguard Worker     };
308*c8dee2aaSAndroid Build Coastguard Worker 
309*c8dee2aaSAndroid Build Coastguard Worker     TestCase cases[] = {
310*c8dee2aaSAndroid Build Coastguard Worker         // no real solutions
311*c8dee2aaSAndroid Build Coastguard Worker         {2, 0, 3, nan, nan},
312*c8dee2aaSAndroid Build Coastguard Worker         {1, 1, 1, nan, nan},
313*c8dee2aaSAndroid Build Coastguard Worker         {2.0 * p2(600), 0, 2.0 * p2(600), nan, nan},
314*c8dee2aaSAndroid Build Coastguard Worker         {-2.0 * p2(600), 0, -2.0 * p2(600), nan, nan},
315*c8dee2aaSAndroid Build Coastguard Worker 
316*c8dee2aaSAndroid Build Coastguard Worker         // degenerate cases
317*c8dee2aaSAndroid Build Coastguard Worker         {0, 0, 0, infinity, infinity},
318*c8dee2aaSAndroid Build Coastguard Worker         {0, 1, 0, 0, 0},
319*c8dee2aaSAndroid Build Coastguard Worker         {0, 1, 2, -2, -2},
320*c8dee2aaSAndroid Build Coastguard Worker         {0, 3, 4, -4.0/3.0, -4.0/3.0},
321*c8dee2aaSAndroid Build Coastguard Worker         {0, p2(600), -p2(600), 1, 1},
322*c8dee2aaSAndroid Build Coastguard Worker         {0, p2(600), p2(600), -1, -1},
323*c8dee2aaSAndroid Build Coastguard Worker         {0, p2(-600), p2(600), -infinity, -infinity},
324*c8dee2aaSAndroid Build Coastguard Worker         {0, p2(600), p2(-600), 0, 0},
325*c8dee2aaSAndroid Build Coastguard Worker         {0, 2, -1.0e-323, 5.0e-324, 5.0e-324},
326*c8dee2aaSAndroid Build Coastguard Worker         {3, 0, 0, 0, 0},
327*c8dee2aaSAndroid Build Coastguard Worker         {p2(600), 0, 0, 0, 0},
328*c8dee2aaSAndroid Build Coastguard Worker         {2, 0, -3, -sqrt(3.0/2.0), sqrt(3.0/2.0)},
329*c8dee2aaSAndroid Build Coastguard Worker         // {p2(600), 0, -p2(600), -1, 1}, determinant is infinity
330*c8dee2aaSAndroid Build Coastguard Worker         {3, 2, 0, -2.0/3.0, 0},
331*c8dee2aaSAndroid Build Coastguard Worker         // {p2(600), p2(700), 0, -p2(100), 0},
332*c8dee2aaSAndroid Build Coastguard Worker         {p2(-600), p2(700), 0, -infinity, 0},
333*c8dee2aaSAndroid Build Coastguard Worker         {p2(600), p2(-700), 0, 0, 0},
334*c8dee2aaSAndroid Build Coastguard Worker 
335*c8dee2aaSAndroid Build Coastguard Worker         // two solutions tests
336*c8dee2aaSAndroid Build Coastguard Worker         {1, -1, -1, -0.6180339887498948, 1.618033988749895},
337*c8dee2aaSAndroid Build Coastguard Worker         {1, 1 + p2(-52), 0.25 + p2(-53), (-1 - p2(-51)) / 2.0, -0.5},
338*c8dee2aaSAndroid Build Coastguard Worker         {1, p2(-511) + p2(-563), std::exp2(-1024), -7.458340888372987e-155,-7.458340574027429e-155},
339*c8dee2aaSAndroid Build Coastguard Worker         {1, p2(27), 0.75, -134217728.0, -5.587935447692871e-09},
340*c8dee2aaSAndroid Build Coastguard Worker         {1, -1e9, 1, 1e-09, 1000000000.0},
341*c8dee2aaSAndroid Build Coastguard Worker         // {1.3407807929942596e154, -1.3407807929942596e154, -1.3407807929942596e154, -0.6180339887498948, 1.618033988749895},
342*c8dee2aaSAndroid Build Coastguard Worker         {p2(600), 0.5, -p2(-600), -3.086568504549085e-181, 1.8816085719976428e-181},
343*c8dee2aaSAndroid Build Coastguard Worker         // {p2(600), 0.5, -p2(600), -1.0, 1.0},
344*c8dee2aaSAndroid Build Coastguard Worker         // {8.0, p2(800),-p2(500), -8.335018041099818e+239, 4.909093465297727e-91},
345*c8dee2aaSAndroid Build Coastguard Worker         {1, p2(26), -0.125, -67108864.0, 1.862645149230957e-09},
346*c8dee2aaSAndroid Build Coastguard Worker         // {p2(-1073), -p2(-1073), -p2(-1073), -0.6180339887498948,1.618033988749895},
347*c8dee2aaSAndroid Build Coastguard Worker         {p2(600), -p2(-600), -p2(-600), -2.409919865102884e-181, 2.409919865102884e-181},
348*c8dee2aaSAndroid Build Coastguard Worker 
349*c8dee2aaSAndroid Build Coastguard Worker         // Tests in Nivergelt paper
350*c8dee2aaSAndroid Build Coastguard Worker         {-158114166017, 316227766017, -158113600000, 0.99999642020057874, 1},
351*c8dee2aaSAndroid Build Coastguard Worker         {-312499999999.0, 707106781186.0, -400000000000.0, 1.131369396027, 1.131372303775},
352*c8dee2aaSAndroid Build Coastguard Worker         {-67, 134, -65, 0.82722631488372798, 1.17277368511627202},
353*c8dee2aaSAndroid Build Coastguard Worker         {0.247260273973, 0.994520547945, -0.138627953316, -4.157030027041105, 0.1348693622211607},
354*c8dee2aaSAndroid Build Coastguard Worker         {1, -2300000, 2.0e11, 90518.994979145, 2209481.005020854},
355*c8dee2aaSAndroid Build Coastguard Worker         {1.5*p2(-1026), 0, -p2(1022), -1.4678102981723264e308, 1.4678102981723264e308},
356*c8dee2aaSAndroid Build Coastguard Worker 
357*c8dee2aaSAndroid Build Coastguard Worker         // one solution tests
358*c8dee2aaSAndroid Build Coastguard Worker         {1.5*p2(-1026), 0, -p2(1022), -1.4678102981723264e308, 1.4678102981723264e308},
359*c8dee2aaSAndroid Build Coastguard Worker     };
360*c8dee2aaSAndroid Build Coastguard Worker 
361*c8dee2aaSAndroid Build Coastguard Worker     for (auto testCase : cases) {
362*c8dee2aaSAndroid Build Coastguard Worker         double A = testCase.A,
363*c8dee2aaSAndroid Build Coastguard Worker                B = testCase.B,
364*c8dee2aaSAndroid Build Coastguard Worker                C = testCase.C,
365*c8dee2aaSAndroid Build Coastguard Worker                answerLo = testCase.answerLo,
366*c8dee2aaSAndroid Build Coastguard Worker                answerHi = testCase.answerHi;
367*c8dee2aaSAndroid Build Coastguard Worker         if (SkIsFinite(answerLo, answerHi)) {
368*c8dee2aaSAndroid Build Coastguard Worker             SkASSERT(answerLo <= answerHi);
369*c8dee2aaSAndroid Build Coastguard Worker         }
370*c8dee2aaSAndroid Build Coastguard Worker         auto [discriminate, r0, r1] = SkQuads::Roots(A, -0.5*B, C);
371*c8dee2aaSAndroid Build Coastguard Worker         double rLo = std::min(r0, r1),
372*c8dee2aaSAndroid Build Coastguard Worker                rHi = std::max(r0, r1);
373*c8dee2aaSAndroid Build Coastguard Worker         REPORTER_ASSERT(reporter, specialEqual(rLo, answerLo));
374*c8dee2aaSAndroid Build Coastguard Worker         REPORTER_ASSERT(reporter, specialEqual(rHi, answerHi));
375*c8dee2aaSAndroid Build Coastguard Worker     }
376*c8dee2aaSAndroid Build Coastguard Worker }
377*c8dee2aaSAndroid Build Coastguard Worker 
378