问题
I am currently trying to solve a quartic equation using Ferrari's method from Wikipedia. I want to retrieve only the real roots, discarding the imaginary one. My implementation does not return the good value for real roots. I can't find the mistakes in the formula.
My CubicEquation
works as expected, and my bi-quadratic solving either. Now, I am only missing the Ferrari's method to be done but I can't make it work!
Here's my class:
public class QuarticFunction {
private final double a;
private final double b;
private final double c;
private final double d;
private final double e;
public QuarticFunction(double a, double b, double c, double d, double e) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
}
public final double[] findRealRoots() {
if (a == 0) {
return new CubicEquation(b, c, d, e).findRealRoots();
}
if (isBiquadratic()) { //This part works as expected
return solveUsingBiquadraticMethod();
}
return solveUsingFerrariMethodWikipedia();
}
private double[] solveUsingFerrariMethodWikipedia() {
// http://en.wikipedia.org/wiki/Quartic_function#Ferrari.27s_solution
// ERROR: Wrong numbers when Two Real Roots + Two Complex Roots
// ERROR: Wrong numbers when Four Real Roots
QuarticFunction depressedQuartic = toDepressed();
if (depressedQuartic.isBiquadratic()) {
return depressedQuartic.solveUsingBiquadraticMethod();
}
double y = findFerraryY(depressedQuartic);
double originalRootConversionPart = -b / (4 * a);
double firstPart = Math.sqrt(depressedQuartic.c + 2 * y);
double positiveSecondPart = Math.sqrt(-(3 * depressedQuartic.c + 2 * y + 2 * depressedQuartic.d / Math.sqrt(depressedQuartic.c + 2 * y)));
double negativeSecondPart = Math.sqrt(-(3 * depressedQuartic.c + 2 * y - 2 * depressedQuartic.d / Math.sqrt(depressedQuartic.c + 2 * y)));
double x1 = originalRootConversionPart + (firstPart + positiveSecondPart) / 2;
double x2 = originalRootConversionPart + (-firstPart + negativeSecondPart) / 2;
double x3 = originalRootConversionPart + (firstPart - positiveSecondPart) / 2;
double x4 = originalRootConversionPart + (-firstPart - negativeSecondPart) / 2;
Set<Double> realRoots = findAllRealRoots(x1, x2, x3, x4);
return toDoubleArray(realRoots);
}
private double findFerraryY(QuarticFunction depressedQuartic) {
double a3 = 1;
double a2 = 5 / 2 * depressedQuartic.c;
double a1 = 2 * Math.pow(depressedQuartic.c, 2) - depressedQuartic.e;
double a0 = Math.pow(depressedQuartic.c, 3) / 2 - depressedQuartic.c * depressedQuartic.e / 2
- Math.pow(depressedQuartic.d, 2) / 8;
//CubicEquation works as expected! No need to worry! It returns either 1 or 3 roots.
CubicEquation cubicEquation = new CubicEquation(a3, a2, a1, a0);
double[] roots = cubicEquation.findRealRoots();
for (double y : roots) {
if (depressedQuartic.c + 2 * y != 0) {
return y;
}
}
throw new IllegalStateException("Ferrari method should have at least one y");
}
public final boolean isBiquadratic() {
return Double.compare(b, 0) == 0 && Double.compare(d, 0) == 0;
}
private double[] solveUsingBiquadraticMethod() {
//It works as expected!
QuadraticLine quadraticEquation = new QuadraticLine(a, c, e);
if (!quadraticEquation.hasRoots()) {
return new double[] {};
}
double[] quadraticRoots = quadraticEquation.findRoots();
Set<Double> roots = new HashSet<>();
for (double quadraticRoot : quadraticRoots) {
if (quadraticRoot > 0) {
roots.add(Math.sqrt(quadraticRoot));
roots.add(-Math.sqrt(quadraticRoot));
} else if (quadraticRoot == 0.00) {
roots.add(0.00);
}
}
return toDoubleArray(roots);
}
public QuarticFunction toDepressed() {
// http://en.wikipedia.org/wiki/Quartic_function#Converting_to_a_depressed_quartic
double p = (8 * a * c - 3 * Math.pow(b, 2)) / (8 * Math.pow(a, 2));
double q = (Math.pow(b, 3) - 4 * a * b * c + 8 * d * Math.pow(a, 2)) / (8 * Math.pow(a, 3));
double r = (-3 * Math.pow(b, 4) + 256 * e * Math.pow(a, 3) - 64 * d * b * Math.pow(a, 2) + 16 * c * a
* Math.pow(b, 2)) / (256 * Math.pow(a, 4));
return new QuarticFunction(1, 0, p, q, r);
}
private Set<Double> findAllRealRoots(double... roots) {
Set<Double> realRoots = new HashSet<>();
for (double root : roots) {
if (!Double.isNaN(root)) {
realRoots.add(root);
}
}
return realRoots;
}
private double[] toDoubleArray(Collection<Double> values) {
double[] doubleArray = new double[values.size()];
int i = 0;
for (double value : values) {
doubleArray[i] = value;
++i;
}
return doubleArray;
}
}
I have tried a simpler implementation found here, but I had a new problem. Now, half of the roots are good, but the other half is wrong. Here is my tentative:
private double[] solveUsingFerrariMethodTheorem() {
// https://proofwiki.org/wiki/Ferrari's_Method
CubicEquation cubicEquation = getCubicEquationForFerrariMethodTheorem();
double[] cubicRoots = cubicEquation.findRealRoots();
double y1 = findFirstNonZero(cubicRoots);
double inRootOfP = Math.pow(b, 2) / Math.pow(a, 2) - 4 * c / a + 4 * y1;
double p1 = b / a + Math.sqrt(inRootOfP);
double p2 = b / a - Math.sqrt(inRootOfP);
double inRootOfQ = Math.pow(y1, 2) - 4 * e / a;
double q1 = y1 - Math.sqrt(inRootOfQ);
double q2 = y1 + Math.sqrt(inRootOfQ);
double x1 = (-p1 + Math.sqrt(Math.pow(p1, 2) - 8 * q1)) / 4;
double x2 = (-p2 - Math.sqrt(Math.pow(p2, 2) - 8 * q1)) / 4;
double x3 = (-p1 + Math.sqrt(Math.pow(p1, 2) - 8 * q2)) / 4;
double x4 = (-p2 - Math.sqrt(Math.pow(p2, 2) - 8 * q2)) / 4;
Set<Double> realRoots = findAllRealRoots(x1, x2, x3, x4);
return toDoubleArray(realRoots);
}
private CubicEquation getCubicEquationForFerrariMethodTheorem() {
double cubicA = 1;
double cubicB = -c / a;
double cubicC = b * d / Math.pow(a, 2) - 4 * e / a;
double cubicD = 4 * c * e / Math.pow(a, 2) - Math.pow(b, 2) * e / Math.pow(a, 3) - Math.pow(d, 2)
/ Math.pow(a, 2);
return new CubicEquation(cubicA, cubicB, cubicC, cubicD);
}
private double findFirstNonZero(double[] values) {
for (double value : values) {
if (Double.compare(value, 0) != 0) {
return value;
}
}
throw new IllegalArgumentException(values + " does not contain any non-zero value");
}
I don't know what I am missing. I have spent hours debugging trying to see the errors, but now I'm completely lost (plus some headaches). What am I doing wrong? I don't care which formulas to use, since I only want one of them to work.
回答1:
I had two mistakes.
- All my "integer constants" should have been in double.
- In the Wikipedia method, when the depressed quartic is biquadratic, we must reconvert the roots to the original quartic. I was returning the depressed roots.
Here is my resulting implementation
public class QuarticFunction {
private static final double NEAR_ZERO = 0.0000001;
private final double a;
private final double b;
private final double c;
private final double d;
private final double e;
public QuarticFunction(double a, double b, double c, double d, double e) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
}
public final double[] findRealRoots() {
if (Math.abs(a) < NEAR_ZERO) {
return new CubicFunction(b, c, d, e).findRealRoots();
}
if (isBiquadratic()) {
return solveUsingBiquadraticMethod();
}
return solveUsingFerrariMethodWikipedia();
}
private double[] solveUsingFerrariMethodWikipedia() {
// http://en.wikipedia.org/wiki/Quartic_function#Ferrari.27s_solution
QuarticFunction depressedQuartic = toDepressed();
if (depressedQuartic.isBiquadratic()) {
double[] depressedRoots = depressedQuartic.solveUsingBiquadraticMethod();
return reconvertToOriginalRoots(depressedRoots);
}
double y = findFerraryY(depressedQuartic);
double originalRootConversionPart = -b / (4.0 * a);
double firstPart = Math.sqrt(depressedQuartic.c + 2.0 * y);
double positiveSecondPart = Math.sqrt(-(3.0 * depressedQuartic.c + 2.0 * y + 2.0 * depressedQuartic.d
/ Math.sqrt(depressedQuartic.c + 2.0 * y)));
double negativeSecondPart = Math.sqrt(-(3.0 * depressedQuartic.c + 2.0 * y - 2.0 * depressedQuartic.d
/ Math.sqrt(depressedQuartic.c + 2.0 * y)));
double x1 = originalRootConversionPart + (firstPart + positiveSecondPart) / 2.0;
double x2 = originalRootConversionPart + (-firstPart + negativeSecondPart) / 2.0;
double x3 = originalRootConversionPart + (firstPart - positiveSecondPart) / 2.0;
double x4 = originalRootConversionPart + (-firstPart - negativeSecondPart) / 2.0;
Set<Double> realRoots = findOnlyRealRoots(x1, x2, x3, x4);
return toDoubleArray(realRoots);
}
private double[] reconvertToOriginalRoots(double[] depressedRoots) {
double[] originalRoots = new double[depressedRoots.length];
for (int i = 0; i < depressedRoots.length; ++i) {
originalRoots[i] = depressedRoots[i] - b / (4.0 * a);
}
return originalRoots;
}
private double findFerraryY(QuarticFunction depressedQuartic) {
double a3 = 1.0;
double a2 = 5.0 / 2.0 * depressedQuartic.c;
double a1 = 2.0 * Math.pow(depressedQuartic.c, 2.0) - depressedQuartic.e;
double a0 = Math.pow(depressedQuartic.c, 3.0) / 2.0 - depressedQuartic.c * depressedQuartic.e / 2.0
- Math.pow(depressedQuartic.d, 2.0) / 8.0;
CubicFunction cubicFunction = new CubicFunction(a3, a2, a1, a0);
double[] roots = cubicFunction.findRealRoots();
for (double y : roots) {
if (depressedQuartic.c + 2.0 * y != 0.0) {
return y;
}
}
throw new IllegalStateException("Ferrari method should have at least one y");
}
private double[] solveUsingBiquadraticMethod() {
QuadraticFunction quadraticFunction = new QuadraticFunction(a, c, e);
if (!quadraticFunction.hasRoots()) {
return new double[] {};
}
double[] quadraticRoots = quadraticFunction.findRoots();
Set<Double> roots = new HashSet<>();
for (double quadraticRoot : quadraticRoots) {
if (quadraticRoot > 0.0) {
roots.add(Math.sqrt(quadraticRoot));
roots.add(-Math.sqrt(quadraticRoot));
} else if (quadraticRoot == 0.00) {
roots.add(0.00);
}
}
return toDoubleArray(roots);
}
public QuarticFunction toDepressed() {
// http://en.wikipedia.org/wiki/Quartic_function#Converting_to_a_depressed_quartic
double p = (8.0 * a * c - 3.0 * Math.pow(b, 2.0)) / (8.0 * Math.pow(a, 2.0));
double q = (Math.pow(b, 3.0) - 4.0 * a * b * c + 8.0 * d * Math.pow(a, 2.0)) / (8.0 * Math.pow(a, 3.0));
double r = (-3.0 * Math.pow(b, 4.0) + 256.0 * e * Math.pow(a, 3.0) - 64.0 * d * b * Math.pow(a, 2.0) + 16.0 * c
* a * Math.pow(b, 2.0))
/ (256.0 * Math.pow(a, 4.0));
return new QuarticFunction(1.0, 0.0, p, q, r);
}
private Set<Double> findOnlyRealRoots(double... roots) {
Set<Double> realRoots = new HashSet<>();
for (double root : roots) {
if (Double.isFinite(root)) {
realRoots.add(root);
}
}
return realRoots;
}
private double[] toDoubleArray(Collection<Double> values) {
double[] doubleArray = new double[values.size()];
int i = 0;
for (double value : values) {
doubleArray[i] = value;
++i;
}
return doubleArray;
}
}
回答2:
The Wikipedia article you mention is correct. However, it is not so simple to just apply the formulas, due to limited floating-point arithmetic precision. That is especially true if comparing the values of determinants to zero, as in some cases, the insufficient precision can change the sign.
Here is my naive C++ implementation (work in progress), which as you can see, is full of thresholds. I plan to use adaptive multiple-precision arithmetics to get rid of the thresholds in the future. This somehow works for equations with 1 - 4 real roots in [-100, 100]
interval:
template <class T = double, bool b_sort_roots = true,
const int n_4th_order_coeff_log10_thresh = -6,
const int n_depressed_1st_order_coeff_log10_thresh = -6,
const int n_zero_discriminant_log10_thresh = -6,
const int n_cubic_3rd_order_coeff_log10_thresh = -6,
const int n_cubic_second_root_precision_abs_log10_thresh = -6,
const int n_cubic_second_root_precision_rel_log10_thresh = 1,
const int n_quad_zero_discriminant_log10_thresh = -5> // lower than above
class CQuarticEq {
protected:
T a; /**< @brief 4th order coefficient */
T b; /**< @brief 3rd order coefficient */
T c; /**< @brief the 2nd order coefficient */
T d; /**< @brief the 1st order coefficient */
T e; /**< @brief 0th order coefficient */
T p_real_root[4]; /**< @brief list of the roots (real parts) */
//T p_im_root[4]; // imaginary part of the roots
size_t n_real_root_num; /**< @brief number of real roots */
public:
/**
* @brief default constructor; solves for roots of \f$ax^4 + bx^3 + cx^2 + dx + e = 0\f$
*
* This finds roots of the given equation. It tends to find two identical roots instead of one, rather
* than missing one of two different roots - the number of roots found is therefore orientational,
* as the roots might have the same value.
*
* @param[in] _a is 4th order coefficient
* @param[in] _b is 3rd order coefficient
* @param[in] _c is the 2nd order coefficient
* @param[in] _d is the 1st order coefficient
* @param[in] _e is constant coefficient
*/
CQuarticEq(T _a, T _b, T _c, T _d, T _e)
:a(_a), b(_b), c(_c), d(_d), e(_e), n_real_root_num(0)
{
if(fabs(_a) < f_Power_Static(10, n_4th_order_coeff_log10_thresh)) { // otherwise division by a yields large numbers, this is then more precise
CCubicEq<T, b_sort_roots, n_cubic_3rd_order_coeff_log10_thresh,
n_cubic_second_root_precision_abs_log10_thresh,
n_cubic_second_root_precision_rel_log10_thresh,
n_quad_zero_discriminant_log10_thresh> eq3(_b, _c, _d, _e);
n_real_root_num = eq3.n_RealRoot_Num();
for(unsigned int i = 0; i < n_real_root_num; ++ i) {
p_real_root[i] = eq3.f_RealRoot(i);
//p_im_root[i] = eq3.f_ImagRoot(i);
}
return;
}
// the highest power is multiplied by 0, it is a cubic equation
if(fabs(_e) == 0) {
CCubicEq<T, b_sort_roots, n_cubic_3rd_order_coeff_log10_thresh,
n_cubic_second_root_precision_abs_log10_thresh,
n_cubic_second_root_precision_rel_log10_thresh,
n_quad_zero_discriminant_log10_thresh> eq3(_a, _b, _c, _d);
n_real_root_num = eq3.n_RealRoot_Num();
for(unsigned int i = 0; i < n_real_root_num; ++ i) {
p_real_root[i] = eq3.f_RealRoot(i);
//p_im_root[i] = eq3.f_ImagRoot(i);
}
return;
}
// the constant is 0, divide the whole equation by x to get a cubic equation
if(fabs(b) == 0 && fabs(d) == 0) { // this possibly needs to be done with a threshold
CQuadraticEq<T, b_sort_roots, -6, n_quad_zero_discriminant_log10_thresh> eq2(a, c, e);
for(int i = 0, n = eq2.n_RealRoot_Num(); i < n; ++ i) {
T y = eq2.f_RealRoot(i);
if(y < 0) {
if(y > -f_Power_Static(10, n_zero_discriminant_log10_thresh)) // only slightly negative
p_real_root[n_real_root_num ++] = 0;
continue; // this root would be imaginary
}
y = sqrt(y);
if(y > 0)
p_real_root[n_real_root_num ++] = -y; // generate the roots in approximately sorted order, avoid negative zeros
p_real_root[n_real_root_num ++] = y;
}
// solve a biquadratic equation a*x^4 + c*x^2 + e = 0
} else {
#if 0 // this first branch seems to be slightly less precise in the worst case
_b /= _a;
_c /= _a;
_d /= _a;
_e /= _a;
// normalize
T f_roots_offset = _b / 4;
T f_alpha = _c - 3.0 / 8 * _b * _b; // p // ok
T f_beta = _b * _b * _b / 8 - _b * _c / 2 + _d; // q // ok
T f_gamma = -3.0 / 256 * _b * _b * _b * _b + _e - 64.0 / 256 * _d * _b + 16.0 / 256 * _b * _b * _c; // r
#else // 0
const T a4 = _a, a3 = _b, a2 = _c, a1 = _d, a0 = _e; // just rename, no normalization
T f_roots_offset = a3 / (4 * a4);
T f_alpha = (8 * a2 * a4 - 3 * a3 * a3) / (8 * a4 * a4);
T f_beta = (a3 * a3 * a3 - 4 * a2 * a3 * a4 + 8 * a1 * a4 * a4) / (8 * a4 * a4 * a4);
T f_gamma = (-3 * a3 * a3 * a3 * a3 + 256 * a0 * a4 * a4 * a4 -
64 * a1 * a3 * a4 * a4 + 16 * a2 * a3 * a3 * a4) / (256 * a4 * a4 * a4 * a4);
#endif // 0
// convert to a depressed quartic u^4 + f_alpha*u^2 + f_beta*u + f_gamma = 0 (where x = u - f_roots_offset)
if(fabs(f_beta) < f_Power_Static(10, n_depressed_1st_order_coeff_log10_thresh)) {
CQuadraticEq<T, b_sort_roots, -6, n_quad_zero_discriminant_log10_thresh> eq2(1, f_alpha, f_gamma);
for(int i = 0, n = eq2.n_RealRoot_Num(); i < n; ++ i) {
T y = eq2.f_RealRoot(i);
if(y < 0) {
if(y > -f_Power_Static(10, n_zero_discriminant_log10_thresh)) // only slightly negative
p_real_root[n_real_root_num ++] = 0 - f_roots_offset;
continue; // this root would be imaginary
}
y = sqrt(y);
p_real_root[n_real_root_num ++] = -y - f_roots_offset;
if(y > 0)
p_real_root[n_real_root_num ++] = y - f_roots_offset; // generate the roots in approximately sorted order
}
// solve a depressed quartic u^4 + f_alpha*u^2 + f_gamma = 0
} else {
T f_cub_a = 1, f_cub_b = 5.0 / 2 * f_alpha, f_cub_c = 2 * f_alpha * f_alpha - f_gamma,
f_cub_d = (f_alpha * f_alpha * f_alpha - f_alpha * f_gamma) / 2 - f_beta * f_beta / 8;
// form a cubic, which gets the parameter y for Ferrari's method
CCubicEq<T, b_sort_roots, n_cubic_3rd_order_coeff_log10_thresh,
n_cubic_second_root_precision_abs_log10_thresh,
n_cubic_second_root_precision_rel_log10_thresh,
n_quad_zero_discriminant_log10_thresh> eq3(f_cub_a, f_cub_b, f_cub_c, f_cub_d);
for(int i = 0, n = eq3.n_RealRoot_Num(); i < n; ++ i) {
const T y = eq3.f_RealRoot(i);
_ASSERTE(fabs(f_alpha + 2 * y) > 0); // if this triggers, we have apparently missed to detect a biquadratic equation above (would cause a division by zero later on)
// get a root of the cubic equation
T f_a2y = f_alpha + 2 * y;
if(f_a2y < 0)
continue; // the root would be an imaginary one
f_a2y = (T)sqrt(f_a2y);
// get square root of alpha + 2y
{
T f_det0 = -(3 * f_alpha + 2 * y + 2 * f_beta / f_a2y);
if(f_det0 >= 0) {
double f_det_sqrt = sqrt(f_det0);
p_real_root[n_real_root_num ++] = (f_a2y + f_det_sqrt) / 2 - f_roots_offset;
if(f_det_sqrt > 0) // otherwise they are the same
p_real_root[n_real_root_num ++] = (f_a2y - f_det_sqrt) / 2 - f_roots_offset;
} else if(f_det0 > -f_Power_Static(10, n_zero_discriminant_log10_thresh)) { // only slightly negative
double f_det_sqrt = 0;
p_real_root[n_real_root_num ++] = (f_a2y + f_det_sqrt) / 2 - f_roots_offset;
}
}
{
T f_det1 = -(3 * f_alpha + 2 * y - 2 * f_beta / f_a2y);
if(f_det1 >= 0) {
double f_det_sqrt = sqrt(f_det1);
p_real_root[n_real_root_num ++] = (-f_a2y + f_det_sqrt) / 2 - f_roots_offset;
if(f_det_sqrt > 0) // otherwise they are the same
p_real_root[n_real_root_num ++] = (-f_a2y - f_det_sqrt) / 2 - f_roots_offset;
} else if(f_det1 > -f_Power_Static(10, n_zero_discriminant_log10_thresh)) { // only slightly negative
double f_det_sqrt = 0;
p_real_root[n_real_root_num ++] = (-f_a2y + f_det_sqrt) / 2 - f_roots_offset;
}
}
break; // the same results are obtained for any value of y
}
// find roots using Ferrari's method
}
}
if(b_sort_roots)
std::sort(p_real_root, p_real_root + n_real_root_num);
// sort the roots explicitly (we could use an ordered merge above,
// but that would convolute the code somewhat)
}
/**
* @brief gets number of real roots
* @return Returns number of real roots (0 to 3).
*/
size_t n_RealRoot_Num() const
{
_ASSERTE(n_real_root_num >= 0);
return n_real_root_num;
}
/**
* @brief gets value of a real root
* @param[in] n_index is zero-based index of the root
* @return Returns value of the specified root.
*/
T f_RealRoot(size_t n_index) const
{
_ASSERTE(n_index < 4 && n_index < n_real_root_num);
return p_real_root[n_index];
}
/**
* @brief evaluates the equation for a given argument
* @param[in] f_x is value of the argument \f$x\f$
* @return Returns value of \f$ax^4 + bx^3 + cx^2 + dx + e\f$.
*/
T operator ()(T f_x) const
{
T f_x2 = f_x * f_x;
T f_x3 = f_x2 * f_x;
T f_x4 = f_x2 * f_x2;
return f_x4 * a + f_x3 * b + f_x2 * c + f_x * d + e;
}
};
You can use it as a starting point for debugging your implementation. This gives the following results:
root response precision 1e-2147483648: 1774587 cases
root response precision 1e-24: 5 cases
root response precision 1e-23: 1 cases
root response precision 1e-22: 10 cases
root response precision 1e-21: 9 cases
root response precision 1e-20: 16 cases
root response precision 1e-19: 43 cases
root response precision 1e-18: 84 cases
root response precision 1e-17: 136 cases
root response precision 1e-16: 447 cases
root response precision 1e-15: 97499 cases
root response precision 1e-14: 394130 cases
root response precision 1e-13: 483321 cases
root response precision 1e-12: 435378 cases
root response precision 1e-11: 203487 cases
root response precision 1e-10: 69011 cases
root response precision 1e-9: 22429 cases
root response precision 1e-8: 6961 cases
root response precision 1e-7: 2368 cases
root response precision 1e-6: 744 cases
root response precision 1e-5: 208 cases
root response precision 1e-4: 24 cases
4-root solution precision 1e-2147483648: 73928 cases
4-root solution precision 1e-23: 1876 cases
4-root solution precision 1e-22: 81123 cases
4-root solution precision 1e-21: 293684 cases
4-root solution precision 1e-20: 745332 cases
4-root solution precision 1e-19: 919578 cases
4-root solution precision 1e-18: 597248 cases
4-root solution precision 1e-17: 268523 cases
4-root solution precision 1e-16: 99841 cases
4-root solution precision 1e-15: 33995 cases
4-root solution precision 1e-14: 11962 cases
4-root solution precision 1e-13: 4366 cases
4-root solution precision 1e-12: 1609 cases
4-root solution precision 1e-11: 547 cases
4-root solution precision 1e-10: 194 cases
4-root solution precision 1e-9: 64 cases
4-root solution precision 1e-8: 4 cases
4-root solution precision 1e-7: 2 cases
3-root solution precision 1e-2147483648: 75013 cases
3-root solution precision 1e-23: 321 cases
3-root solution precision 1e-22: 3281 cases
3-root solution precision 1e-21: 4961 cases
3-root solution precision 1e-20: 5543 cases
3-root solution precision 1e-19: 3248 cases
3-root solution precision 1e-18: 1424 cases
3-root solution precision 1e-17: 654 cases
3-root solution precision 1e-16: 177 cases
3-root solution precision 1e-15: 96 cases
3-root solution precision 1e-14: 372 cases
3-root solution precision 1e-13: 874 cases
3-root solution precision 1e-12: 7575 cases
3-root solution precision 1e-11: 14236 cases
3-root solution precision 1e-10: 11076 cases
3-root solution precision 1e-9: 5020 cases
3-root solution precision 1e-8: 2202 cases
3-root solution precision 1e-7: 1044 cases
3-root solution precision 1e-6: 326 cases
3-root solution precision 1e-5: 39 cases
3-root solution precision 1e-4: 4 cases
3-root solution precision 1e-3: 1 cases
2-root solution precision 1e-2147483648: 35077 cases
2-root solution precision 1e-23: 399 cases
2-root solution precision 1e-22: 2054 cases
2-root solution precision 1e-21: 3016 cases
2-root solution precision 1e-20: 3236 cases
2-root solution precision 1e-19: 2340 cases
2-root solution precision 1e-18: 1337 cases
2-root solution precision 1e-17: 687 cases
2-root solution precision 1e-16: 356 cases
2-root solution precision 1e-15: 3596 cases
2-root solution precision 1e-14: 12063 cases
2-root solution precision 1e-13: 8287 cases
2-root solution precision 1e-12: 7902 cases
2-root solution precision 1e-11: 11479 cases
2-root solution precision 1e-10: 4458 cases
2-root solution precision 1e-9: 1212 cases
2-root solution precision 1e-8: 142 cases
2-root solution precision 1e-7: 21 cases
2-root solution precision 1e-6: 2 cases
2-root solution precision 1e-4: 1 cases
2-root solution precision 1e-3: 1 cases
1-root solution precision 1e-2147483648: 111525 cases
1-root solution precision 1e-23: 867 cases
1-root solution precision 1e-22: 3505 cases
1-root solution precision 1e-21: 2735 cases
1-root solution precision 1e-20: 1888 cases
1-root solution precision 1e-19: 686 cases
1-root solution precision 1e-18: 497 cases
1-root solution precision 1e-17: 138 cases
1-root solution precision 1e-16: 26 cases
1-root solution precision 1e-14: 2 cases
had 0 quartic equations with 0 roots
had 121869 quartic equations with 1 roots
had 48833 quartic equations with 2 roots
had 45829 quartic equations with 3 roots
had 783469 quartic equations with 4 roots
Where "root response precision" means the absolute value of the function in the found root (error in y
), and "root solution precision" is distance of the found root to the ground truth root (error in x
).
Note that I do not give CCubicEq
and CQuadraticEq
, since you already have those.
来源:https://stackoverflow.com/questions/27217113/finding-real-roots-of-quartic-equation-using-ferraris-method