Math 四次函数的根

Math 四次函数的根,math,polynomial-math,Math,Polynomial Math,我在做高级碰撞检测时遇到了一种情况,我需要计算四次函数的根 我使用法拉利的一般解决方案编写了一个似乎运行良好的函数,如下所示: 以下是我的功能: 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:A

我在做高级碰撞检测时遇到了一种情况,我需要计算四次函数的根

我使用法拉利的一般解决方案编写了一个似乎运行良好的函数,如下所示:

以下是我的功能:

    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.3262664455924333X+5.474214401412618

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

如果我画出这个特殊的方程,我可以看到实际的根更接近1.2和2.9(大约)。我不能将四个不正确的根视为随机根,因为它们实际上是方程一阶导数的两个根:

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

请记住,我并不是在寻找我发布的等式的具体根。我的问题是,是否有某种特殊情况我没有考虑


有什么想法吗?

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

这种方法不适合心脏虚弱的人。构造多项式的后,运行查找该矩阵的特征值。这些是多项式的零点


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

对于寻找次数>=3的多项式的根,我使用Jenkins Traub()的结果总是比显式公式更好。

你可以看到。我支持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
的系数是一,如果多项式中不是这样,则必须将整件事除以系数,然后继续

祝你好运


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

其他答案都是好的建议。然而,回顾我在福斯实施法拉利方法的经验,我认为你的错误结果可能是由1造成的。必要且相当棘手的符号组合的错误实现,2。还没有意识到浮点中的“..==beta”应该变成“abs(..-beta) 对于此特定问题,诊断模式下的第四个代码返回:

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 

我还实现了这个函数,得到了与您相同的结果。
经过一段时间的调试后,是条件检查“\beta上述方法的一个很好的替代方法是基于Terence R.F.Nonweiler CACM(1968年4月)的论文“低阶多项式的根”

这是一个三阶和四阶多项式的代数解,它相当紧凑、快速且相当精确。它比詹金斯·特劳布简单得多

但是,请注意,TOMS代码并没有那么好地工作


这是一个四次/三次方根查找器的代码,它更加精确。它还有一个Jenkins Traub类型的根查找器。

我们能看看你写的函数吗?我不知道你为什么不在这个PlanetMath站点上实现漂亮的方程::)你对复杂的解也感兴趣吗?如果你想找到真正的解,任何标准的数值寻根方法都需要足够的种子。不要使用这种方法!这是一个漂亮的数学,但是一个糟糕的算法。你可能只想要真正的根。计算代码中的实际操作数,包括展开复杂的算术运算。与迭代算法相比,它是昂贵的。另外,你能说在所有这些操作之后,最终的结果有多准确吗?事实上,迭代方法可能更精确。其他人已经列出了这些方法。@Justin Peel-谢谢,我今天需要一个好的笑声=)你能推荐一个学习如何使用这种方法的资源吗?有趣的是,我假设我可以使用一个函数来计算不同程度的多项式的根吗?刚刚实现了这种方法,它似乎工作得很好!非常感谢@Scott Langendyk,你应该接受答案,然后点击进入