用法拉利法找到四次方程的真根

时间:2014-11-30 19:12:51

标签: java math trigonometry polynomial-math equation-solving

我目前正在尝试使用Ferrari's method from Wikipedia来解决四次方程式。我想只检索真正的根,丢弃想象的根。我的实现并没有为真正的根源带来好的价值。我无法找到公式中的错误。

我的CubicEquation按预期工作,我的双二次解决也是如此。现在,我只是错过了法拉利的方法,但我无法让它发挥作用!

这是我的班级:

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;
    }
}

我尝试过更简单的实现found here,但我遇到了一个新问题。现在,一半的根是好的,但另一半是错的。这是我的尝试:

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");
}

我不知道自己错过了什么。我花了好几个小时调试试图查看错误,但现在我完全丢失了(加上一些令人头疼的事)。我究竟做错了什么?我不关心使用哪些公式,因为我只想让其中一个公式起作用。

2 个答案:

答案 0 :(得分:3)

我有两个错误。

  1. 我所有的&#34;整数常数&#34;应该是双倍的。
  2. 在维基百科方法中,当凹陷的四次方是双二次时,我们必须将根再转换为原始的四次方。我正在回归沮丧的根源。
  3. 这是我最终的实现

    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;
        }
    }
    

答案 1 :(得分:1)

你提到的Wikipedia article是正确的。但是,由于有限的浮点算术精度,仅应用公式并不是那么简单。如果将决定因素的值与零进行比较,尤其如此,因为在某些情况下,精度不足会改变符号。

这是我天真的C ++实现(正在进行中),正如您所看到的,它充满了阈值。我打算使用自适应多精度算术来摆脱未来的门槛。这在某种程度上适用于[-100, 100]区间内具有1-4个实根的方程:

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;
    }
};

您可以将其用作调试实施的起点。这给出了以下结果:

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

其中“根响应精度”表示找到的根中的函数的绝对值(y中的错误),“根解精度”是找到的根到地面真实根的距离(错误x)。

请注意,由于您已经拥有CCubicEqCQuadraticEq,因此我不会这样做。