四次函数的根

时间:2010-06-04 15:07:22

标签: math polynomial-math

我遇到了一些进行高级碰撞检测的情况,我需要计算一个四次函数的根。

我使用法拉利的一般解决方案编写了一个似乎工作正常的函数,如下所示:http://en.wikipedia.org/wiki/Quartic_function#Ferrari.27s_solution

这是我的功能:

    private function solveQuartic(A:Number, B:Number, C:Number, D:Number, E:Number):Array{          
        // For paramters: Ax^4 + Bx^3 + Cx^2 + Dx + E
        var solution:Array = new Array(4);

        // Using Ferrari's formula: http://en.wikipedia.org/wiki/Quartic_function#Ferrari.27s_solution
        var Alpha:Number = ((-3 * (B * B)) / (8 * (A * A))) + (C / A);
        var Beta:Number = ((B * B * B) / (8 * A * A * A)) - ((B * C) / (2 * A * A)) + (D / A);          
        var Gamma:Number = ((-3 * B * B * B * B) / (256 * A * A * A * A)) + ((C * B * B) / (16 * A * A * A)) - ((B * D) / (4 * A * A)) + (E / A);

        var P:Number = ((-1 * Alpha * Alpha) / 12) - Gamma; 
        var Q:Number = ((-1 * Alpha * Alpha * Alpha) / 108) + ((Alpha * Gamma) / 3) - ((Beta * Beta) / 8);

        var PreRoot1:Number = ((Q * Q) / 4) + ((P * P * P) / 27);
        var R:ComplexNumber = ComplexNumber.add(new ComplexNumber((-1 * Q) / 2), ComplexNumber.sqrt(new ComplexNumber(PreRoot1)));

        var U:ComplexNumber = ComplexNumber.pow(R, 1/3);

        var preY1:Number = (-5 / 6) * Alpha;
        var RedundantY:ComplexNumber = ComplexNumber.add(new ComplexNumber(preY1), U);

        var Y:ComplexNumber;

        if(U.isZero()){
            var preY2:ComplexNumber = ComplexNumber.pow(new ComplexNumber(Q), 1/3);

            Y = ComplexNumber.subtract(RedundantY, preY2);
        } else{
            var preY3:ComplexNumber = ComplexNumber.multiply(new ComplexNumber(3), U);
            var preY4:ComplexNumber = ComplexNumber.divide(new ComplexNumber(P), preY3);

            Y = ComplexNumber.subtract(RedundantY, preY4);
        }

        var W:ComplexNumber = ComplexNumber.sqrt(ComplexNumber.add(new ComplexNumber(Alpha), ComplexNumber.multiply(new ComplexNumber(2), Y)));

        var Two:ComplexNumber = new ComplexNumber(2);
        var NegativeOne:ComplexNumber = new ComplexNumber(-1);

        var NegativeBOverFourA:ComplexNumber = new ComplexNumber((-1 * B) / (4 * A));
        var NegativeW:ComplexNumber = ComplexNumber.multiply(W, NegativeOne);

        var ThreeAlphaPlusTwoY:ComplexNumber = ComplexNumber.add(new ComplexNumber(3 * Alpha), ComplexNumber.multiply(new ComplexNumber(2), Y));
        var TwoBetaOverW:ComplexNumber = ComplexNumber.divide(new ComplexNumber(2 * Beta), W);

        solution["root1"] = ComplexNumber.add(NegativeBOverFourA, ComplexNumber.divide(ComplexNumber.add(W, ComplexNumber.sqrt(ComplexNumber.multiply(NegativeOne, ComplexNumber.add(ThreeAlphaPlusTwoY, TwoBetaOverW)))), Two));
        solution["root2"] = ComplexNumber.add(NegativeBOverFourA, ComplexNumber.divide(ComplexNumber.subtract(NegativeW, ComplexNumber.sqrt(ComplexNumber.multiply(NegativeOne, ComplexNumber.subtract(ThreeAlphaPlusTwoY, TwoBetaOverW)))), Two));
        solution["root3"] = ComplexNumber.add(NegativeBOverFourA, ComplexNumber.divide(ComplexNumber.subtract(W, ComplexNumber.sqrt(ComplexNumber.multiply(NegativeOne, ComplexNumber.add(ThreeAlphaPlusTwoY, TwoBetaOverW)))), Two));
        solution["root4"] = ComplexNumber.add(NegativeBOverFourA, ComplexNumber.divide(ComplexNumber.add(NegativeW, ComplexNumber.sqrt(ComplexNumber.multiply(NegativeOne, ComplexNumber.subtract(ThreeAlphaPlusTwoY, TwoBetaOverW)))), Two));

        return solution;
    }

唯一的问题是我似乎得到了一些例外。最值得注意的是,当我有两个真正的根,两个想象的根。

例如,这个等式: y = 0.9604000000000001x ^ 4 - 5.997600000000001x ^ 3 + 13.951750054511718x ^ 2 - 14.326264455924333x + 5.474214401412618

返回根: 1.7820304835380467 + 0i 1.34041662585388 + 0i 1.3404185025061823 + 0i 1.7820323472855648 + 0i

如果我绘制特定方程的图形,我可以看到实际的根更接近1.2和2.9(大约)。我不能忽略这四个不正确的根是随机的,因为它们实际上是等式的一阶导数的两个根:

y = 3.8416x ^ 3 - 17.9928x ^ 2 + 27.9035001x - 14.326264455924333

请记住,我实际上并没有找到我发布的等式的具体根源。我的问题是,是否存在一些我没有考虑的特殊情况。

有什么想法吗?

6 个答案:

答案 0 :(得分:4)

我不知道为什么法拉利的解决方案不起作用,但我尝试使用标准数值方法(创建伴随矩阵并计算其特征值),并获得正确的解,即1.2和1.9处的两个实根

这种方法不适合胆小的人。在构造多项式的companion matrix之后,运行QR algorithm以找到该矩阵的特征值。那些是多项式的零。

我建议您使用QR算法的现有实现,因为它的大部分都比算法更接近厨房配方。但我相信,它是最广泛使用的算法,用于计算特征值,从而计算多项式的根。

答案 1 :(得分:4)

为了找到程度> = 3的多项式的根,我总是使用Jenkins-Traub(http://en.wikipedia.org/wiki/Jenkins-Traub_algorithm)得到比显式公式更好的结果。

答案 2 :(得分:2)

其他答案都是好的和合理的建议。但是,回想一下我在Forth中实施法拉利方法的经验,我认为你的错误结果可能是由于1.错误实施必要且相当棘手的标志组合,2。没有意识到“.. == beta”in浮点应该变成“abs(... - beta)< eps,3。还没有发现代码中还有其他平方根可能会返回复杂的解决方案。

对于这个特殊问题,诊断模式中的Forth代码返回:

x1 =  1.5612244897959360787072371026316680470492e+0000 -1.6542769593216835969789894020584464029664e-0001 i
 --> -4.2123274051525879873007970023884313331788e-0054  3.4544674220377778501545407451201598284464e-0077 i
x2 =  1.5612244897959360787072371026316680470492e+0000  1.6542769593216835969789894020584464029664e-0001 i
 --> -4.2123274051525879873007970023884313331788e-0054 -3.4544674220377778501545407451201598284464e-0077 i
x3 =  1.2078440724224197532447709413299479764843e+0000  0.0000000000000000000000000000000000000000e-0001 i
 --> -4.2123274051525879873010733597821943554068e-0054  0.0000000000000000000000000000000000000000e-0001 i
x4 =  1.9146049071693819497220585618954851525216e+0000 -0.0000000000000000000000000000000000000000e-0001 i
 --> -4.2123274051525879873013497171759573776348e-0054  0.0000000000000000000000000000000000000000e-0001 i

“ - >”之后的文字从后面的根取代到原始的等式。

供参考,Mathematica / Alpha的结果是我设法设置的最高精度:

Mathematica:
x1 = 1.20784407242
x2 = 1.91460490717
x3 = 1.56122449 - 0.16542770 i
x4 = 1.56122449 + 0.16542770 i 

答案 3 :(得分:1)

您可以看到my answer to a related question。我支持Olivier的观点:要走的路可能只是伴随矩阵/特征值方法(非常稳定,简单,可靠,快速)。

修改

我想如果我在这里重现答案并不会有害,为方便起见:

以可靠,稳定的方式多次这样做的数值解包括:(1)形成伴随矩阵,(2)找到伴随矩阵的特征值。

您可能认为这是一个比原始问题更难解决的问题,但这就是大多数生产代码(例如,Matlab)中解决方案的实现方式。

对于多项式:

p(t) = c0 + c1 * t + c2 * t^2 + t^3

伴随矩阵是:

[[0 0 -c0],[1 0 -c1],[0 1 -c2]]

找出这种矩阵的特征值;它们对应于原始多项式的根。

为了快速完成这项工作,请从LAPACK下载奇异值子程序,编译它们,并将它们链接到您的代码。如果您有太多(例如,大约一百万)个系数集,请同时执行此操作。您可以使用QR分解或任何其他稳定的方法来计算特征值(请参阅维基百科关于“矩阵特征值”的条目)。

请注意,t^3的系数是1,如果在多项式中不是这种情况,则必须将整个系数除以系数,然后继续。

祝你好运。

编辑:Numpy和octave也依赖于这种方法来计算多项式的根。例如,请参阅this link

答案 4 :(得分:0)

已经提到的方法的一个很好的替代方法是TOMS Algorithm 326,它基于论文"低阶多项式的根源"作者:Terence R.F.Nonweiler CACM(1968年4月)。

这是对3阶和4阶多项式的代数解,它相当紧凑,快速且非常准确。它比Jenkins Traub简单得多。

但请注意,TOMS代码并不能很好地发挥作用。

这个Iowa Hills Root Solver page有一个Quartic / Cubic root查找器的代码,它有点精炼。它还有一个Jenkins Traub类型的根查找器。

答案 5 :(得分:-1)

我也实现了这个功能,并像你一样得到了理智的结果。 经过一段时间的调试后,应该通过条件检查“\ beta< 0”,但不是。