cubic_roots.hpp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. // (C) Copyright Nick Thompson 2021.
  2. // Use, modification and distribution are subject to the
  3. // Boost Software License, Version 1.0. (See accompanying file
  4. // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  5. #ifndef BOOST_MATH_TOOLS_CUBIC_ROOTS_HPP
  6. #define BOOST_MATH_TOOLS_CUBIC_ROOTS_HPP
  7. #include <algorithm>
  8. #include <array>
  9. #include <boost/math/special_functions/sign.hpp>
  10. #include <boost/math/tools/roots.hpp>
  11. namespace boost::math::tools {
  12. // Solves ax^3 + bx^2 + cx + d = 0.
  13. // Only returns the real roots, as types get weird for real coefficients and
  14. // complex roots. Follows Numerical Recipes, Chapter 5, section 6. NB: A better
  15. // algorithm apparently exists: Algorithm 954: An Accurate and Efficient Cubic
  16. // and Quartic Equation Solver for Physical Applications However, I don't have
  17. // access to that paper!
  18. template <typename Real>
  19. std::array<Real, 3> cubic_roots(Real a, Real b, Real c, Real d) {
  20. using std::abs;
  21. using std::acos;
  22. using std::cbrt;
  23. using std::cos;
  24. using std::fma;
  25. using std::sqrt;
  26. std::array<Real, 3> roots = {std::numeric_limits<Real>::quiet_NaN(),
  27. std::numeric_limits<Real>::quiet_NaN(),
  28. std::numeric_limits<Real>::quiet_NaN()};
  29. if (a == 0) {
  30. // bx^2 + cx + d = 0:
  31. if (b == 0) {
  32. // cx + d = 0:
  33. if (c == 0) {
  34. if (d != 0) {
  35. // No solutions:
  36. return roots;
  37. }
  38. roots[0] = 0;
  39. roots[1] = 0;
  40. roots[2] = 0;
  41. return roots;
  42. }
  43. roots[0] = -d / c;
  44. return roots;
  45. }
  46. auto [x0, x1] = quadratic_roots(b, c, d);
  47. roots[0] = x0;
  48. roots[1] = x1;
  49. return roots;
  50. }
  51. if (d == 0) {
  52. auto [x0, x1] = quadratic_roots(a, b, c);
  53. roots[0] = x0;
  54. roots[1] = x1;
  55. roots[2] = 0;
  56. std::sort(roots.begin(), roots.end());
  57. return roots;
  58. }
  59. Real p = b / a;
  60. Real q = c / a;
  61. Real r = d / a;
  62. Real Q = (p * p - 3 * q) / 9;
  63. Real R = (2 * p * p * p - 9 * p * q + 27 * r) / 54;
  64. if (R * R < Q * Q * Q) {
  65. Real rtQ = sqrt(Q);
  66. Real theta = acos(R / (Q * rtQ)) / 3;
  67. Real st = sin(theta);
  68. Real ct = cos(theta);
  69. roots[0] = -2 * rtQ * ct - p / 3;
  70. roots[1] = -rtQ * (-ct + sqrt(Real(3)) * st) - p / 3;
  71. roots[2] = rtQ * (ct + sqrt(Real(3)) * st) - p / 3;
  72. } else {
  73. // In Numerical Recipes, Chapter 5, Section 6, it is claimed that we
  74. // only have one real root if R^2 >= Q^3. But this isn't true; we can
  75. // even see this from equation 5.6.18. The condition for having three
  76. // real roots is that A = B. It *is* the case that if we're in this
  77. // branch, and we have 3 real roots, two are a double root. Take
  78. // (x+1)^2(x-2) = x^3 - 3x -2 as an example. This clearly has a double
  79. // root at x = -1, and it gets sent into this branch.
  80. Real arg = R * R - Q * Q * Q;
  81. Real A = (R >= 0 ? -1 : 1) * cbrt(abs(R) + sqrt(arg));
  82. Real B = 0;
  83. if (A != 0) {
  84. B = Q / A;
  85. }
  86. roots[0] = A + B - p / 3;
  87. // Yes, we're comparing floats for equality:
  88. // Any perturbation pushes the roots into the complex plane; out of the
  89. // bailiwick of this routine.
  90. if (A == B || arg == 0) {
  91. roots[1] = -A - p / 3;
  92. roots[2] = -A - p / 3;
  93. }
  94. }
  95. // Root polishing:
  96. for (auto &r : roots) {
  97. // Horner's method.
  98. // Here I'll take John Gustaffson's opinion that the fma is a *distinct*
  99. // operation from a*x +b: Make sure to compile these fmas into a single
  100. // instruction and not a function call! (I'm looking at you Windows.)
  101. Real f = fma(a, r, b);
  102. f = fma(f, r, c);
  103. f = fma(f, r, d);
  104. Real df = fma(3 * a, r, 2 * b);
  105. df = fma(df, r, c);
  106. if (df != 0) {
  107. Real d2f = fma(6 * a, r, 2 * b);
  108. Real denom = 2 * df * df - f * d2f;
  109. if (denom != 0) {
  110. r -= 2 * f * df / denom;
  111. } else {
  112. r -= f / df;
  113. }
  114. }
  115. }
  116. std::sort(roots.begin(), roots.end());
  117. return roots;
  118. }
  119. // Computes the empirical residual p(r) (first element) and expected residual
  120. // eps*|rp'(r)| (second element) for a root. Recall that for a numerically
  121. // computed root r satisfying r = r_0(1+eps) of a function p, |p(r)| <=
  122. // eps|rp'(r)|.
  123. template <typename Real>
  124. std::array<Real, 2> cubic_root_residual(Real a, Real b, Real c, Real d,
  125. Real root) {
  126. using std::abs;
  127. using std::fma;
  128. std::array<Real, 2> out;
  129. Real residual = fma(a, root, b);
  130. residual = fma(residual, root, c);
  131. residual = fma(residual, root, d);
  132. out[0] = residual;
  133. // The expected residual is:
  134. // eps*[4|ar^3| + 3|br^2| + 2|cr| + |d|]
  135. // This can be demonstrated by assuming the coefficients and the root are
  136. // perturbed according to the rounding model of floating point arithmetic,
  137. // and then working through the inequalities.
  138. root = abs(root);
  139. Real expected_residual = fma(4 * abs(a), root, 3 * abs(b));
  140. expected_residual = fma(expected_residual, root, 2 * abs(c));
  141. expected_residual = fma(expected_residual, root, abs(d));
  142. out[1] = expected_residual * std::numeric_limits<Real>::epsilon();
  143. return out;
  144. }
  145. // Computes the condition number of rootfinding. This is defined in Corless, A
  146. // Graduate Introduction to Numerical Methods, Section 3.2.1.
  147. template <typename Real>
  148. Real cubic_root_condition_number(Real a, Real b, Real c, Real d, Real root) {
  149. using std::abs;
  150. using std::fma;
  151. // There are *absolute* condition numbers that can be defined when r = 0;
  152. // but they basically reduce to the residual computed above.
  153. if (root == static_cast<Real>(0)) {
  154. return std::numeric_limits<Real>::infinity();
  155. }
  156. Real numerator = fma(abs(a), abs(root), abs(b));
  157. numerator = fma(numerator, abs(root), abs(c));
  158. numerator = fma(numerator, abs(root), abs(d));
  159. Real denominator = fma(3 * a, root, 2 * b);
  160. denominator = fma(denominator, root, c);
  161. if (denominator == static_cast<Real>(0)) {
  162. return std::numeric_limits<Real>::infinity();
  163. }
  164. denominator *= root;
  165. return numerator / abs(denominator);
  166. }
  167. } // namespace boost::math::tools
  168. #endif