/* See LICENSE file for copyright and license details. */ #include "common.h" #ifndef TEST CONST_FUNCTION static double refine_cubic_solution(double t, double a, double b, double c, double d) { size_t i; double x; for (i = 0; x = t*t*t*a + t*t*b + t*c + d, !iszeroish(x) && i < 10; i++) t -= x / (t*t*(a*3) + t*(b*2) + c); return t; } static size_t solve_cubic_with_1_known_root(double *ts, double a, double b, double t1) { /* * (t³ + t²a + tb + c) / (t - t₁) = t² + tp + q * t³ + t²a + tb + c = (t² + tp + q)(t - t₁) * t³ + t²a + tb + c = t²(t - t₁) + tp(t - t₁) + q(t - t₁) * t³ + t²a + tb + c = t³ - t²t₁ + t²p - tpt₁ + tq - qt₁ * t³ + t²a + tb + c = t³ + t²(p - t₁) + t(q - pt₁) - qt₁ * * ⎧a = p - t₁ * ⎨b = q - pt₁ * ⎩c = -qt₁ * * ⎧a + t₁ = p * ⎨b + pt₁ = q * ⎩c = -qt₁ * * ⎰a + t₁ = p * ⎱b + pt₁ = q * * ⎰p = a + t₁ * ⎱q = b + pt₁ */ double p = a + t1; double q = b + p * t1; return solve_quadratic(ts, 1, p, q); } static size_t solve_cubic_with_2_known_roots(double *ts, double a, double b, double c, double t1, double t2) { /* * (t - t₁)(t - t₂)(t - t₃) * (t - t₁)t(t - t₃) - (t - t₁)t₂(t - t₃) * (t - t₁)(t² - t₃t) - (t - t₁)(t₂t - t₂t₃) * (t - t₁)t² - (t - t₁)t₃t - (t - t₁)t₂t + (t - t₁)t₂t₃ * t³ - t₁t² - t₃t² + t₁t₃t - t₂t² + t₁t₂t + t₂t₃t - t₁t₂t₃ * t³ - t²(t₁ + t₂ + t₃) + t(t₁t₂ + t₁t₃ + t₂t₃) - t₁t₂t₃ * * ⎧a = -t₁ - t₂ - t₃ * ⎨b = t₁t₂ + t₁t₃ + t₂t₃ * ⎩c = -t₁t₂t₃ * * ⎧t₃ = -t₁ - t₂ - a * ⎨t₃ = (b - t₁t₂)/(t₁ + t₂) * ⎩t₃ = -c/t₁t₂ */ double t12sum = t1 + t2; double t12prod = t1 * t2; double t3a = -a - t12sum; double t3b = iszeroish(t12sum) ? t3a : (b - t12prod) / t12sum; double t3c = iszeroish(t12prod) ? t3a : -c / t12prod; if (iszeroish(t3a - t3b) && iszeroish(t3a - t3c)) { ts[0] = (t3a + t3b + t3c) / 3.0; return 0.0 <= ts[0] && ts[0] <= 1.0; } t1 = 0.5 * (t1 + t2); t1 = refine_cubic_solution(t1, 1.0, a, b, c); return solve_cubic_with_1_known_root(ts, a, b, t1); } size_t solve_cubic(double *ts, double a, double b, double c, double d) { double t1, t2, t3; unsigned int solutions = 0; size_t n = 0; double u = 1 / (a * -3); double D0 = b*b - 3*(a*c); double D1 = b*b*b - (4.5*b)*(a*c) + (13.5*a)*(a*d); double kk = D1*D1 - D0*D0*D0; double r, i; double Kr; if (kk >= 0) { Kr = D1 < 0 ? cbrt(D1 + sqrt(kk)) : cbrt(D1 - sqrt(kk)); } else { double ki = sqrt(-kk); double h = pow(D1*D1 + ki*ki, 1.0 / 6.0); double v = (0.1 / 0.3) * atan2(ki, D1); Kr = h * cos(v); if (1 /* || fabs(cos(v)) > 1.0 - TOLERANCE */) { double Ki = h * sin(v); double t = 1.0 / (Kr * Kr + Ki * Ki); double m = t * D0 + 1; if (iszeroish(Ki - D0 * Ki * t)) { ts[n] = u * (b + Kr + D0 * Kr * t); ts[n] = t1 = refine_cubic_solution(ts[n], a, b, c, d); solutions |= 1; n += 0.0 <= ts[n] && ts[n] <= 1.0; } if (iszeroish(Ki - sqrt(3.0) * Kr)) { ts[n] = u * (b - (0.5 * Kr + sqrt(0.75) * Ki) * m); ts[n] = t2 = refine_cubic_solution(ts[n], a, b, c, d); solutions |= 2; n += 0.0 <= ts[n] && ts[n] <= 1.0; } if (iszeroish(Ki + sqrt(3.0) * Kr)) { ts[n] = u * (b - (0.5 * Kr - sqrt(0.75) * Ki) * m); ts[n] = t3 = refine_cubic_solution(ts[n], a, b, c, d); solutions |= 4; n += 0.0 <= ts[n] && ts[n] <= 1.0; } /* Sometimes the maths doesn't work well enough */ if (solutions != 7) { #if defined(__GNUC__) && !defined(__clang__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #elif defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wconditional-uninitialized" #endif switch (solutions) { case 1: n += solve_cubic_with_1_known_root(&ts[n], b / a, c / a, t1); break; case 2: n += solve_cubic_with_1_known_root(&ts[n], b / a, c / a, t2); break; case 4: n += solve_cubic_with_1_known_root(&ts[n], b / a, c / a, t3); break; case 7 - 1: n += solve_cubic_with_2_known_roots(&ts[n], b / a, c / a, d / a, t2, t3); break; case 7 - 2: n += solve_cubic_with_2_known_roots(&ts[n], b / a, c / a, d / a, t1, t3); break; case 7 - 4: n += solve_cubic_with_2_known_roots(&ts[n], b / a, c / a, d / a, t1, t2); break; } #if defined(__GNUC__) && !defined(__clang__) # pragma GCC diagnostic pop #elif defined(__clang__) # pragma clang diagnostic pop #endif } return n; } } if (!iszeroish(Kr)) { r = Kr + D0 / Kr; i = u * sqrt(0.75) * (Kr - D0 / Kr); real_case: ts[n] = u * (b + r); ts[n] = t1 = refine_cubic_solution(ts[n], a, b, c, d); n += 0.0 <= ts[n] && ts[n] <= 1.0; if (iszeroish(i)) { ts[n] = u * (b - 0.5 * r); ts[n] = refine_cubic_solution(ts[n], a, b, c, d); n += 0.0 <= ts[n] && ts[n] <= 1.0; return n; } else { n += solve_cubic_with_1_known_root(&ts[n], b / a, c / a, t1); return n; } } else { double D0divKr = D0 / Kr; r = Kr + D0divKr; i = u * sqrt(0.75) * (Kr - D0divKr); if (isfinite(D0divKr)) /* NOT -ffast-math compatible */ goto real_case; ts[n] = u * b; ts[n] = t1 = refine_cubic_solution(ts[n], a, b, c, d); n += 0.0 <= ts[n] && ts[n] <= 1.0; n += solve_cubic_with_1_known_root(&ts[n], b / a, c / a, t1); return n; } } size_t solve_quadratic(double *ts, double a, double b, double c) { size_t n = 0; double negahalf_p = -0.5 * (b / a); double q = c / a; double D = negahalf_p * negahalf_p - q; if (D < 0) { if (D < -TOLERANCE) return 0; ts[n] = negahalf_p; n += (0.0 <= ts[n] && ts[n] <= 1.0); return n; } else { ts[n] = negahalf_p + sqrt(D); n += (0.0 <= ts[n] && ts[n] <= 1.0); ts[n] = negahalf_p - sqrt(D); n += (0.0 <= ts[n] && ts[n] <= 1.0); return n; } } size_t solve_linear(double *ts, double a, double b) { ts[0] = -b / a; return (0.0 <= ts[0] && ts[0] <= 1.0); } #else #define Re(X) X, 0 #define Im(X) 0, X #define Cx(R, I) R, I static void check_cubic(double r1, double i1, double r2, double i2, double r3, double i3, double k, int strict_real) { size_t nexpected = 0, ngot, i, j; double expected[3], got[3]; double error, maxerror = 0; double ra, rb, rc; double ia, ib, ic; int found[3], correct; if (iszeroish(i1)) expected[nexpected++] = r1; if (iszeroish(i2)) expected[nexpected++] = r2; if (iszeroish(i3)) expected[nexpected++] = r3; /* * k(t₁ - t)(t₂ - t)(t₃ - t) = 0 * k(t₁ - t)t₂(t₃ - t) - k(t₁ - t)t(t₃ - t) = 0 * kt₂(t₁ - t)(t₃ - t) - kt(t₁ - t)(t₃ - t) = 0 * kt₂(t₁(t₃ - t) - t(t₃ - t)) - kt(t₁(t₃ - t) - t(t₃ - t)) = 0 * kt₂(t₁t₃ - t₁t - tt₃ + t²) - kt(t₁t₃ - t₁t - tt₃ + t²) = 0 * kt₂(t₁t₃ - t₁t - tt₃ + t²) + kt(-t₁t₃ + t₁t + tt₃ - t²) = 0 * kt₂t₁t₃ - kt₂t₁t - kt₂tt₃ + kt₂t² - ktt₁t₃ + ktt₁t + kt²t₃ - kt³ = 0 * kt₂t₁t₃ - kt₂t₁t¹ - kt₂t¹t₃ + kt₂t² - kt¹t₁t₃ + kt²t₁ + kt²t₃ - kt³ = 0 * -kt³ + kt₂t² + kt²t₁ + kt²t₃ - kt₂t₁t¹ - kt₂t¹t₃ - kt¹t₁t₃ + kt₂t₁t₃ = 0 * kt³ - kt₂t² - kt²t₁ - kt²t₃ + kt₂t₁t¹ + kt₂t¹t₃ + kt¹t₁t₃ - kt₂t₁t₃ = 0 * t³k - t²k(t₁ + t₂ + t₃) + t¹k(t₂t₁ + t₂t₃ + t₁t₃) - kt₂t₁t₃ = 0 * ⎧a := t₁ + t₂ + t₃ ⎫ * ⎨b := t₁t₂ + t₁t₃ + t₂t₃⎬ * ⎩c := t₁t₂t₃ ⎭ * t³k - t²ka + t¹kb - kc = 0 */ ra = r1 + r2 + r3; ia = i1 + i2 + i3; rb = r1*r2 - i1*i2 + r1*r3 - i1*i3 + r2*r3 - i2*i3; ib = r1*i2 + i1*r2 + r1*i3 + i1*r3 + r2*i3 + i2*r3; rc = r1*r2*r3 - r1*i2*i3 - i1*r2*i3 - i1*i2*r3; ic = r1*r2*i3 + r1*i2*r3 + i1*r2*r3 - i1*i2*i3; if (strict_real) { ASSERT(iszeroish(ia)); ASSERT(iszeroish(ib)); ASSERT(iszeroish(ic)); } else { if (!iszeroish(ia) || !iszeroish(ib) || !iszeroish(ic)) return; } ngot = solve_cubic(got, k, -k*ra, k*rb, -k*rc); for (i = 0; i < sizeof(found) / sizeof(*found); i++) found[i] = (i >= nexpected || expected[i] < 0.0 + TOLERANCE || expected[i] > 1.0 - TOLERANCE); if (!nexpected) { ASSERT(!ngot); return; } for (i = 0; i < ngot; i++) { correct = (got[i] < 0.0 + TOLERANCE || got[i] > 1.0 - TOLERANCE); error = fabs(got[i] - expected[0]); for (j = 0; j < nexpected; j++) { error = fmin(error, fabs(got[i] - expected[j])); if (tolerant_eq(got[i], expected[j])) found[j] = correct = 1; } if (!correct) maxerror = fmax(maxerror, error); } if (!found[0] || !found[1] || !found[2] || !tolerant_eq(maxerror, 0)) { fprintf(stderr, "\n%lg, %lg, %lg, %lg, %lg, %lg, %lg\n", r1, i1, r2, i2, r3, i3, k); fprintf(stderr, "\t(%la, %la, %la, %la, %la, %la, %la)\n", r1, i1, r2, i2, r3, i3, k); fprintf(stderr, "\tmaxerror: %lf (%la)\n", maxerror, maxerror); fprintf(stderr, "\tfound: %i, %i, %i\n", found[0], found[1], found[2]); fprintf(stderr, "\tgot: %zu\n", ngot); for (i = 0; i < ngot; i++) fprintf(stderr, "\t\t%lf (%la)\n", got[i], got[i]); fprintf(stderr, "\texpected: %zu\n", nexpected); for (i = 0; i < nexpected; i++) fprintf(stderr, "\t\t%lf (%la)\n", expected[i], expected[i]); } ASSERT(found[0]); ASSERT(found[1]); ASSERT(found[2]); ASSERT(tolerant_eq(maxerror, 0)); } static void check_quadratic(double r1, double i1, double r2, double i2, double k, int strict_real) { size_t nexpected = 0, ngot, i, j; double expected[2], got[2]; double error, maxerror = 0; double ra, rb; double ia, ib; int found[2], correct; if (iszeroish(i1)) expected[nexpected++] = r1; if (iszeroish(i2)) expected[nexpected++] = r2; /* * k(t₁ - t)(t₂ - t) = 0 * k(t₁(t₂ - t) - t(t₂ - t)) = 0 * k(t₁t₂ - t¹t₁ - t¹t₂ + t²) = 0 * kt₁t₂ - t¹kt₁ - t¹kt₂ + kt² = 0 * t²k - t¹kt₁ - t¹kt₂ + kt₁t₂ = 0 * t²k - t¹k(t₁ + kt₂) + kt₁t₂ = 0 * ⎰a := t₁ + t₂⎱ * ⎱b := t₁t₂ ⎰ * t²k - t¹ka + kb = 0 */ ra = r1 + r2; ia = i1 + i2; rb = r1*r2 - i1*i2; ib = r1*i2 + i1*r2; if (strict_real) { ASSERT(iszeroish(ia)); ASSERT(iszeroish(ib)); } else { if (!iszeroish(ia) || !iszeroish(ib)) return; } ngot = solve_quadratic(got, k, -k*ra, k*rb); for (i = 0; i < sizeof(found) / sizeof(*found); i++) found[i] = (i >= nexpected || expected[i] < 0.0 + TOLERANCE || expected[i] > 1.0 - TOLERANCE); if (!nexpected) { ASSERT(!ngot); return; } for (i = 0; i < ngot; i++) { correct = (got[i] < 0.0 + TOLERANCE || got[i] > 1.0 - TOLERANCE); error = fabs(got[i] - expected[0]); for (j = 0; j < nexpected; j++) { error = fmin(error, fabs(got[i] - expected[j])); if (tolerant_eq(got[i], expected[j])) found[j] = correct = 1; } if (!correct) maxerror = fmax(maxerror, error); } if (!found[0] || !found[1] || !tolerant_eq(maxerror, 0)) { fprintf(stderr, "\n%lg, %lg, %lg, %lg, %lg\n", r1, i1, r2, i2, k); fprintf(stderr, "\t(%la, %la, %la, %la, %la)\n", r1, i1, r2, i2, k); fprintf(stderr, "\tmaxerror: %lf (%la)\n", maxerror, maxerror); fprintf(stderr, "\tfound: %i, %i\n", found[0], found[1]); fprintf(stderr, "\tgot: %zu\n", ngot); for (i = 0; i < ngot; i++) fprintf(stderr, "\t\t%lf (%la)\n", got[i], got[i]); fprintf(stderr, "\texpected: %zu\n", nexpected); for (i = 0; i < nexpected; i++) fprintf(stderr, "\t\t%lf (%la)\n", expected[i], expected[i]); } ASSERT(found[0]); ASSERT(found[1]); ASSERT(tolerant_eq(maxerror, 0)); } static void check_linear(double r1, double i1, double k, int strict_real) { size_t nexpected = 0, ngot, i; double expected[1] = {999999}, got[1]; double ra; double ia; if (iszeroish(i1) && 0.0 <= r1 && r1 <= 1.0) expected[nexpected++] = r1; /* * k(t - t₁) = 0 * kt - kt₁ = 0 */ ra = r1; ia = i1; if (strict_real) { ASSERT(iszeroish(ia)); } else { if (!iszeroish(ia)) return; } ngot = solve_linear(got, k, -k*ra); ASSERT(ngot == nexpected); for (i = 0; i < ngot; i++) ASSERT(eq(got[i], expected[i])); } static void check_cubic_randomly(void) { double r1 = rand() / (double)RAND_MAX; double i1 = rand() / (double)RAND_MAX; double r2 = rand() / (double)RAND_MAX; double i2 = rand() / (double)RAND_MAX; double r3 = rand() / (double)RAND_MAX; double i3 = rand() / (double)RAND_MAX; double k = rand() / (double)RAND_MAX; r1 = r1 * 3 - 1; i1 = i1 * 3 - 1; r2 = r2 * 3 - 1; i2 = i2 * 3 - 1; r3 = r3 * 3 - 1; i3 = i3 * 3 - 1; k = k * 3 - 1; k += iszeroish(k); check_cubic(Cx(r1, i1), Cx(r2, i2), Cx(r3, i3), k, 0); check_cubic(Im(i1), Cx(r2, i2), Cx(r3, i3), k, 0); check_cubic(Re(r1), Cx(r2, i2), Cx(r3, i3), k, 0); check_cubic(Cx(r1, i1), Im(i2), Cx(r3, i3), k, 0); check_cubic(Im(i1), Im(i2), Cx(r3, i3), k, 0); check_cubic(Re(r1), Im(i2), Cx(r3, i3), k, 0); check_cubic(Cx(r1, i1), Re(r2), Cx(r3, i3), k, 0); check_cubic(Im(i1), Re(r2), Cx(r3, i3), k, 0); check_cubic(Re(r1), Re(r2), Cx(r3, i3), k, 0); check_cubic(Cx(r1, i1), Cx(r1, i1), Cx(r3, i3), k, 0); check_cubic(Im(i1), Im(i1), Cx(r3, i3), k, 0); check_cubic(Re(r1), Re(r1), Cx(r3, i3), k, 0); check_cubic(Cx(r1, i1), Cx(r3, i3), Cx(r3, i3), k, 0); check_cubic(Im(i1), Cx(r3, i3), Cx(r3, i3), k, 0); check_cubic(Re(r1), Cx(r3, i3), Cx(r3, i3), k, 0); check_cubic(Cx(r1, i1), Cx(r2, i2), Im(i3), k, 0); check_cubic(Im(i1), Cx(r2, i2), Im(i3), k, 0); check_cubic(Re(r1), Cx(r2, i2), Im(i3), k, 0); check_cubic(Cx(r1, i1), Im(i2), Im(i3), k, 0); check_cubic(Im(i1), Im(i2), Im(i3), k, 0); check_cubic(Re(r1), Im(i2), Im(i3), k, 0); check_cubic(Cx(r1, i1), Re(r2), Im(i3), k, 0); check_cubic(Im(i1), Re(r2), Im(i3), k, 0); check_cubic(Re(r1), Re(r2), Im(i3), k, 0); check_cubic(Cx(r1, i1), Cx(r1, i1), Im(i3), k, 0); check_cubic(Im(i1), Im(i1), Im(i3), k, 0); check_cubic(Re(r1), Re(r1), Im(i3), k, 0); check_cubic(Cx(r1, i1), Im(i3), Im(i3), k, 0); check_cubic(Im(i1), Im(i3), Im(i3), k, 0); check_cubic(Re(r1), Im(i3), Im(i3), k, 0); check_cubic(Cx(r1, i1), Cx(r2, i2), Re(r3), k, 0); check_cubic(Im(i1), Cx(r2, i2), Re(r3), k, 0); check_cubic(Re(r1), Cx(r2, i2), Re(r3), k, 0); check_cubic(Cx(r1, i1), Im(i2), Re(r3), k, 0); check_cubic(Im(i1), Im(i2), Re(r3), k, 0); check_cubic(Re(r1), Im(i2), Re(r3), k, 0); check_cubic(Cx(r1, i1), Re(r2), Re(r3), k, 0); check_cubic(Im(i1), Re(r2), Re(r3), k, 0); check_cubic(Re(r1), Re(r2), Re(r3), k, 1); check_cubic(Cx(r1, i1), Cx(r1, i1), Re(r3), k, 0); check_cubic(Im(i1), Im(i1), Re(r3), k, 0); check_cubic(Re(r1), Re(r1), Re(r3), k, 1); check_cubic(Cx(r1, i1), Re(r3), Re(r3), k, 0); check_cubic(Im(i1), Re(r3), Re(r3), k, 0); check_cubic(Re(r1), Re(r3), Re(r3), k, 1); check_cubic(Cx(r1, i1), Cx(r1, i1), Cx(r1, i1), k, 0); check_cubic(Im(i1), Im(i1), Im(i1), k, 0); check_cubic(Re(r1), Re(r1), Re(r1), k, 1); } static void check_quadratic_randomly(void) { double r1 = rand() / (double)RAND_MAX; double i1 = rand() / (double)RAND_MAX; double r2 = rand() / (double)RAND_MAX; double i2 = rand() / (double)RAND_MAX; double k = rand() / (double)RAND_MAX; r1 = r1 * 3 - 1; i1 = i1 * 3 - 1; r2 = r2 * 3 - 1; i2 = i2 * 3 - 1; k = k * 3 - 1; k += iszeroish(k); check_quadratic(Cx(r1, i1), Cx(r2, i2), k, 0); check_quadratic(Im(i1), Cx(r2, i2), k, 0); check_quadratic(Re(r1), Cx(r2, i2), k, 0); check_quadratic(Cx(r1, i1), Im(i2), k, 0); check_quadratic(Im(i1), Im(i2), k, 0); check_quadratic(Re(r1), Im(i2), k, 0); check_quadratic(Cx(r1, i1), Re(r2), k, 0); check_quadratic(Im(i1), Re(r2), k, 0); check_quadratic(Re(r1), Re(r2), k, 1); check_quadratic(Cx(r1, i1), Cx(r1, i1), k, 0); check_quadratic(Im(i1), Im(i1), k, 0); check_quadratic(Re(r1), Re(r1), k, 1); } static void check_linear_randomly(void) { double r1 = rand() / (double)RAND_MAX; double i1 = rand() / (double)RAND_MAX; double k = rand() / (double)RAND_MAX; r1 = r1 * 3 - 1; i1 = i1 * 3 - 1; k = k * 3 - 1; k += iszeroish(k); check_linear(Cx(r1, i1), k, 0); check_linear(Im(i1), k, 0); check_linear(Re(r1), k, 0); } int main(void) { size_t i; void *r = malloc(1); srand((unsigned)(uintptr_t)r); free(r); check_linear(Re(0), 1.0, 1); check_linear(Re(0), 1.5, 1); check_linear(Re(0), 0.5, 1); check_linear(Re(0), -0.5, 1); check_linear(Re(0), -1.0, 1); check_linear(Re(0), -1.5, 1); for (i = 0; i < 100000UL; i++) check_linear_randomly(); check_quadratic(Re(0), Re(0), 1.0, 1); check_quadratic(Re(0), Re(0), 1.5, 1); check_quadratic(Re(0), Re(0), 0.5, 1); check_quadratic(Re(0), Re(0), -0.5, 1); check_quadratic(Re(0), Re(0), -1.0, 1); check_quadratic(Re(0), Re(0), -1.5, 1); for (i = 0; i < 10000000UL; i++) check_quadratic_randomly(); check_cubic(Re(0), Re(0), Re(0), 1.0, 1); check_cubic(Re(0), Re(0), Re(0), 1.5, 1); check_cubic(Re(0), Re(0), Re(0), 0.5, 1); check_cubic(Re(0), Re(0), Re(0), -0.5, 1); check_cubic(Re(0), Re(0), Re(0), -1.0, 1); check_cubic(Re(0), Re(0), Re(0), -1.5, 1); for (i = 0; i < 1000000UL; i++) check_cubic_randomly(); return 0; } #endif