* Copyright 2023 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/base/SkQuads.h"
#include "include/core/SkSpan.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkFloatingPoint.h"
#include "src/pathops/SkPathOpsQuad.h"
#include "tests/Test.h"
#include <algorithm>
#include <cfloat>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <limits>
#include <string>
static void testQuadRootsReal(skiatest::Reporter* reporter, const std::string& name,
double A, double B, double C,
SkSpan<const double> expectedRoots) {
skiatest::ReporterContext subtest(reporter, name);
REPORTER_ASSERT(reporter, expectedRoots.size() <= 2,
"Invalid test case, up to 2 roots allowed");
for (size_t i = 0; i < expectedRoots.size(); i++) {
double x = expectedRoots[i];
double y = A * x * x + B * x + C;
REPORTER_ASSERT(reporter, sk_double_nearly_zero(y),
"Invalid test case root %zu. %.16f != 0", i, y);
if (i > 0) {
REPORTER_ASSERT(reporter, expectedRoots[i-1] <= expectedRoots[i],
"Invalid test case root %zu. Roots should be sorted in ascending order", i);
}
}
{
skiatest::ReporterContext subsubtest(reporter, "Pathops Implementation");
double roots[2] = {0, 0};
int rootCount = SkDQuad::RootsReal(A, B, C, roots);
REPORTER_ASSERT(reporter, expectedRoots.size() == size_t(rootCount),
"Wrong number of roots returned %zu != %d", expectedRoots.size(),
rootCount);
std::sort(std::begin(roots), std::begin(roots) + rootCount);
for (int i = 0; i < rootCount; i++) {
if (sk_double_nearly_zero(expectedRoots[i])) {
REPORTER_ASSERT(reporter, sk_double_nearly_zero(roots[i]),
"0 != %.16f at index %d", roots[i], i);
} else {
REPORTER_ASSERT(reporter,
sk_doubles_nearly_equal_ulps(expectedRoots[i], roots[i], 64),
"%.16f != %.16f at index %d", expectedRoots[i], roots[i], i);
}
}
}
{
skiatest::ReporterContext subsubtest(reporter, "SkQuads Implementation");
double roots[2] = {0, 0};
int rootCount = SkQuads::RootsReal(A, B, C, roots);
REPORTER_ASSERT(reporter, expectedRoots.size() == size_t(rootCount),
"Wrong number of roots returned %zu != %d", expectedRoots.size(),
rootCount);
std::sort(std::begin(roots), std::begin(roots) + rootCount);
for (int i = 0; i < rootCount; i++) {
if (sk_double_nearly_zero(expectedRoots[i])) {
REPORTER_ASSERT(reporter, sk_double_nearly_zero(roots[i]),
"0 != %.16f at index %d", roots[i], i);
} else {
REPORTER_ASSERT(reporter,
sk_doubles_nearly_equal_ulps(expectedRoots[i], roots[i], 64),
"%.16f != %.16f at index %d", expectedRoots[i], roots[i], i);
}
}
}
}
DEF_TEST(QuadRootsReal_ActualQuadratics, reporter) {
testQuadRootsReal(reporter, "two roots 3x^2 - 20x - 40",
3, -20, -40,
{-1.610798991397109,
8.277465658063775,
});
testQuadRootsReal(reporter, "two roots 2x^2 + 30x - 68",
2, 30, -68,
{-17, 2});
testQuadRootsReal(reporter, "two roots x^2 - 5",
1, 0, -5,
{-2.236067977499790,
2.236067977499790,
});
testQuadRootsReal(reporter, "one root x^2 - 2x + 1",
1, -2, 1,
{1});
testQuadRootsReal(reporter, "no roots 5x^2 + 6x + 7",
5, 6, 7,
{});
testQuadRootsReal(reporter, "no roots 4x^2 + 1",
4, 0, 1,
{});
testQuadRootsReal(reporter, "one root is zero, another is big",
14, -13, 0,
{0,
0.9285714285714286
});
testQuadRootsReal(reporter, "one root is zero, another is small",
0.2929016490705016, 0.0000030451558069, 0,
{-0.00001039651301576329, 0});
testQuadRootsReal(reporter, "b and c are zero, a is positive 4x^2",
4, 0, 0,
{0});
testQuadRootsReal(reporter, "b and c are zero, a is negative -4x^2",
-4, 0, 0,
{0});
testQuadRootsReal(reporter, "a and b are huge, c is zero",
4.3719914983870202e+291, 1.0269509510194551e+152, 0,
{0});
testQuadRootsReal(reporter, "Very small A B, very large C",
0x1p-1055, 0x1.3000006p-1044, -0x1.c000008p+1009,
{});
}
DEF_TEST(QuadRootsReal_Linear, reporter) {
testQuadRootsReal(reporter, "positive slope 5x + 6",
0, 5, 6,
{-1.2});
testQuadRootsReal(reporter, "negative slope -3x - 9",
0, -3, -9,
{-3.});
}
DEF_TEST(QuadRootsReal_Constant, reporter) {
testQuadRootsReal(reporter, "No intersections y = -10",
0, 0, -10,
{});
testQuadRootsReal(reporter, "Infinite solutions y = 0",
0, 0, 0,
{0.});
}
DEF_TEST(QuadRootsReal_NonFiniteNumbers, reporter) {
double roots[2];
REPORTER_ASSERT(reporter,
SkQuads::RootsReal(DBL_MAX, 0, DBL_MAX, roots) == 0,
"Discriminant is negative infinity"
);
REPORTER_ASSERT(reporter,
SkQuads::RootsReal(DBL_MAX, DBL_MAX, DBL_MAX, roots) == 0,
"Double Overflow"
);
REPORTER_ASSERT(reporter,
SkQuads::RootsReal(1, NAN, -3, roots) == 0,
"Nan quadratic"
);
REPORTER_ASSERT(reporter,
SkQuads::RootsReal(0, NAN, 3, roots) == 0,
"Nan linear"
);
REPORTER_ASSERT(reporter,
SkQuads::RootsReal(0, 0, NAN, roots) == 0,
"Nan constant"
);
}
DEF_TEST(QuadDiscriminant_Fibonacci, reporter) {
int64_t F[] = {1, 1, 0};
for (int i = 2; i < 79; ++i) {
F[0] = F[1] + F[2];
const int expectedDiscriminant = i % 2 == 0 ? 1 : -1;
REPORTER_ASSERT(reporter, SkQuads::Discriminant(F[0], F[1], F[2]) == expectedDiscriminant);
F[2] = F[1];
F[1] = F[0];
}
}
DEF_TEST(QuadRoots_Basic, reporter) {
{
auto [discriminant, r0, r1] = SkQuads::Roots(1, -0.5 * -2, 1);
REPORTER_ASSERT(reporter, discriminant == 0);
REPORTER_ASSERT(reporter, r0 == 1 && r1 == 1);
}
{
auto [discriminant, r0, r1] = SkQuads::Roots(1, -0.5 * 4, 4);
REPORTER_ASSERT(reporter, discriminant == 0);
REPORTER_ASSERT(reporter, r0 == -2 && r1 == -2);
}
}
DEF_TEST(QuadRoots_Fibonacci, reporter) {
int64_t F[] = {1, 1, 0};
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);
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];
}
}
struct TestCase {
const double A;
const double B;
const double C;
const double answerLo;
const double answerHi;
};
DEF_TEST(QuadRoots_Hard, reporter) {
const double nan = std::numeric_limits<double>::quiet_NaN();
const double infinity = std::numeric_limits<double>::infinity();
auto specialEqual = [] (double actual, double test) {
if (std::isnan(actual)) {
return std::isnan(test);
}
if (std::isinf(actual)) {
return std::isinf(test);
}
const double errorFactor = std::sqrt(std::numeric_limits<double>::epsilon());
return std::abs(test - actual) <= errorFactor * std::max(std::abs(test), std::abs(actual));
};
auto p2 = [](double a) {
return std::exp2(a);
};
TestCase cases[] = {
{2, 0, 3, nan, nan},
{1, 1, 1, nan, nan},
{2.0 * p2(600), 0, 2.0 * p2(600), nan, nan},
{-2.0 * p2(600), 0, -2.0 * p2(600), nan, nan},
{0, 0, 0, infinity, infinity},
{0, 1, 0, 0, 0},
{0, 1, 2, -2, -2},
{0, 3, 4, -4.0/3.0, -4.0/3.0},
{0, p2(600), -p2(600), 1, 1},
{0, p2(600), p2(600), -1, -1},
{0, p2(-600), p2(600), -infinity, -infinity},
{0, p2(600), p2(-600), 0, 0},
{0, 2, -1.0e-323, 5.0e-324, 5.0e-324},
{3, 0, 0, 0, 0},
{p2(600), 0, 0, 0, 0},
{2, 0, -3, -sqrt(3.0/2.0), sqrt(3.0/2.0)},
{3, 2, 0, -2.0/3.0, 0},
{p2(-600), p2(700), 0, -infinity, 0},
{p2(600), p2(-700), 0, 0, 0},
{1, -1, -1, -0.6180339887498948, 1.618033988749895},
{1, 1 + p2(-52), 0.25 + p2(-53), (-1 - p2(-51)) / 2.0, -0.5},
{1, p2(-511) + p2(-563), std::exp2(-1024), -7.458340888372987e-155,-7.458340574027429e-155},
{1, p2(27), 0.75, -134217728.0, -5.587935447692871e-09},
{1, -1e9, 1, 1e-09, 1000000000.0},
{p2(600), 0.5, -p2(-600), -3.086568504549085e-181, 1.8816085719976428e-181},
{1, p2(26), -0.125, -67108864.0, 1.862645149230957e-09},
{p2(600), -p2(-600), -p2(-600), -2.409919865102884e-181, 2.409919865102884e-181},
{-158114166017, 316227766017, -158113600000, 0.99999642020057874, 1},
{-312499999999.0, 707106781186.0, -400000000000.0, 1.131369396027, 1.131372303775},
{-67, 134, -65, 0.82722631488372798, 1.17277368511627202},
{0.247260273973, 0.994520547945, -0.138627953316, -4.157030027041105, 0.1348693622211607},
{1, -2300000, 2.0e11, 90518.994979145, 2209481.005020854},
{1.5*p2(-1026), 0, -p2(1022), -1.4678102981723264e308, 1.4678102981723264e308},
{1.5*p2(-1026), 0, -p2(1022), -1.4678102981723264e308, 1.4678102981723264e308},
};
for (auto testCase : cases) {
double A = testCase.A,
B = testCase.B,
C = testCase.C,
answerLo = testCase.answerLo,
answerHi = testCase.answerHi;
if (SkIsFinite(answerLo, answerHi)) {
SkASSERT(answerLo <= answerHi);
}
auto [discriminate, r0, r1] = SkQuads::Roots(A, -0.5*B, C);
double rLo = std::min(r0, r1),
rHi = std::max(r0, r1);
REPORTER_ASSERT(reporter, specialEqual(rLo, answerLo));
REPORTER_ASSERT(reporter, specialEqual(rHi, answerHi));
}
}