我找到了几个立方函数根的封闭形式公式,但它们都使用复数或大量的测角函数,我不喜欢它们(也不知道选择哪一个)。
我需要一些简单的东西;越快越好;而且我知道我最终需要求解更高阶的多项式,因此使用数值求解器也许会有所帮助。 我知道我可以用一些图书馆来为我做艰苦的工作,但是我想说我想做这个练习。
我在C编码,所以请不要import magic_poly_solver
。
奖金问题:如何在给定的时间间隔内仅查找根?
答案 0 :(得分:8)
对于三次多项式,有closed form solutions,但它们并不特别适合数值计算。
我会对立方体情况执行以下操作:任何三次多项式都至少有一个实根,您可以使用牛顿方法轻松找到它。然后,使用deflation得到剩余的二次多项式求解,请参阅我的答案there,了解如何正确执行后一步骤。
提醒一句:如果判别式接近于零,那么将存在数字上的多个实根,并且牛顿方法将失败。此外,由于到根的附近,多项式就像(x - x0)^ 2,你将失去有效数字的一半(因为P(x)将是< epsilon x - x0< sqrt (ε))。因此,您可能需要对此进行排除,并在此特定情况下使用闭合形式解,或求解导数多项式。
如果您想在给定的时间间隔内找到根,请检查Sturm's theorem。
通用多项式求解的更一般(复杂)算法是Jenkins-Traub algorithm。这在这里显然有些过分,但在立方体上效果很好。通常,您使用第三方实现。
由于你做C,使用GSL肯定是你最好的选择。
另一种通用方法是用例如companion matrix找到特征值。平衡的QR分解,或减少到Householder形式。这是GSL采取的方法。
答案 1 :(得分:2)
答案 2 :(得分:2)
见
答案 3 :(得分:1)
对于用简单的C代码求解三次方程组,我发现著名数字专家William Kahan教授的QBC
求解器足够健壮,相当快且相当准确:
威廉·卡汉(William Kahan),“求解一个实立方方程。” PAM-352,加州大学伯克利分校纯粹与应用数学中心。 1986年11月10日。(online,online)
这使用基于导数的迭代方法来找到实根,然后将其简化为二次方程,最后使用数值健壮的二次方程求解器来找到剩余的两个根。通常,迭代求解器需要大约五到十次迭代才能收敛到结果。通过明智地使用fma()
标准数学函数在ISO C99中使用的fused multiply-add(FMA)操作,可以提高两个求解器的准确性和性能。
对二次求解器的准确性至关重要的是判别式的计算。为此,我基于最近的研究使用以下代码:
/* Compute B*B - A*C, accurately
Claude-Pierre Jeannerod, Nicolas Louvet, and Jean-Michel Muller,
"Further Analysis of Kahan's Algorithm for the Accurate Computation
of 2x2 Determinants". Mathematics of Computation, Vol. 82, No. 284,
Oct. 2013, pp. 2245-2264
https://www.ams.org/journals/mcom/2013-82-284/S0025-5718-2013-02679-8/S0025-5718-2013-02679-8.pdf
*/
double DISC (double A, double B, double C)
{
double w = C * A;
double e = fma (-C, A, w);
double f = fma (B, B, -w);
double r = f + e;
return r;
}
使用双精度算法,Kahan的求解器无法始终产生精确到双精度的结果。 Kahan论文中提供的测试用例之一说明了这种情况的原因:
658x³-190125x²+ 18311811x-587898164
使用任意精度的数学库,我们发现该三次方程的根如下:
96.229639346592182182_18 ...
96.357064825184152_07 ...±i * 0.069749752043689625_43 ...
QBC
使用双精度算术将根计算为
96.2296393 50445893
96.35706482 3257289 ±i * 0.0697497 48521837268
这样做的原因是,围绕实根的函数求值会遭受计算出的函数值中高达60%的误差,从而阻止了迭代求解器更接近于根。通过将函数和导数评估更改为将双精度计算用于中间计算(以较高的计算成本),我们可以解决该问题。
/* Data type for double-double computation */
typedef struct {
double l; // low / tail
double h; // high / head
} dbldbl;
dbldbl make_dbldbl (double head, double tail);
double get_dbldbl_head (dbldbl a);
double get_dbldbl_tail (dbldbl a);
dbldbl add_dbldbl (dbldbl a, dbldbl b);
dbldbl mul_dbldbl (dbldbl a, dbldbl b);
void EVAL (double X, double A, double B, double C, double D,
double * restrict Q, double * restrict Qprime,
double * restrict B1, double * restrict C2)
{
#if USE_DBLDBL_EVAL
dbldbl AA, BB, CC, DD, XX, AX, TT, UU;
AA = make_dbldbl (A, 0);
BB = make_dbldbl (B, 0);
CC = make_dbldbl (C, 0);
DD = make_dbldbl (D, 0);
XX = make_dbldbl (X, 0);
AX = mul_dbldbl (AA, XX);
TT = add_dbldbl (AX, BB);
*B1 = get_dbldbl_head (TT) + get_dbldbl_tail(TT);
UU = add_dbldbl (mul_dbldbl (TT, XX), CC);
*C2 = get_dbldbl_head (UU) + get_dbldbl_tail(UU);
TT = add_dbldbl (mul_dbldbl (add_dbldbl (AX, TT), XX), UU);
*Qprime = get_dbldbl_head (TT) + get_dbldbl_tail(TT);
UU = add_dbldbl (mul_dbldbl (UU, XX), DD);
*Q = get_dbldbl_head (UU) + get_dbldbl_tail(UU);
#else // USE_DBLDBL_EVAL
*B1 = fma (A, X, B);
*C2 = fma (*B1, X, C);
*Qprime = fma (fma (A, X, *B1), X, *C2);
*Q = fma (*C2, X, D);
#endif // USE_DBLDBL_EVAL
}
/* Construct new dbldbl number. |tail| must be <= 0.5 ulp of |head| */
dbldbl make_dbldbl (double head, double tail)
{
dbldbl z;
z.l = tail;
z.h = head;
return z;
}
/* Return the head of a double-double number */
double get_dbldbl_head (dbldbl a)
{
return a.h;
}
/* Return the tail of a double-double number */
double get_dbldbl_tail (dbldbl a)
{
return a.l;
}
/* Add two dbldbl numbers */
dbldbl add_dbldbl (dbldbl a, dbldbl b)
{
dbldbl z;
double e, q, r, s, t, u;
/* Andrew Thall, "Extended-Precision Floating-Point Numbers for GPU
Computation." 2006. http://andrewthall.org/papers/df64_qf128.pdf
*/
q = a.h + b.h;
r = q - a.h;
t = (a.h + (r - q)) + (b.h - r);
s = a.l + b.l;
r = s - a.l;
u = (a.l + (r - s)) + (b.l - r);
t = t + s;
s = q + t;
t = (q - s) + t;
t = t + u;
z.h = e = s + t;
z.l = (s - e) + t;
/* For result of zero or infinity, ensure that tail equals head */
if (isinf (s)) {
z.h = s;
z.l = s;
}
if (z.h == 0) {
z.l = z.h;
}
return z;
}
/* Multiply two dbldbl numbers */
dbldbl mul_dbldbl (dbldbl a, dbldbl b)
{
dbldbl z;
double e, s, t;
s = a.h * b.h;
t = fma (a.h, b.h, -s);
t = fma (a.l, b.l, t);
t = fma (a.h, b.l, t);
t = fma (a.l, b.h, t);
z.h = e = s + t;
z.l = (s - e) + t;
/* For result of zero or infinity, ensure that tail equals head */
if (isinf (s)) {
z.h = s;
z.l = s;
}
if (z.h == 0) {
z.l = z.h;
}
return z;
}
使用更精确的函数和导数评估计算出的根为:
96.22963934659218 0
96.35706482518415 3 ±i * 0.06974975204 5672006
虽然实际零件现在的精度在双精度范围内,但虚部仍然不可用。原因是在这种情况下,二次方程对系数的微小差异敏感。它们中任何一个的一个ulp错误都可能导致假想部分之间的差异约为10 -11 。可以通过将系数表示为双精度以上并在二次求解器中使用高精度计算来解决此问题。
答案 4 :(得分:0)
/*******************************************************************************
* FindCubicRoots solves:
* coeff[3] * x^3 + coeff[2] * x^2 + coeff[1] * x + coeff[0] = 0
* returns:
* 3 - 3 real roots
* 1 - 1 real root (2 complex conjugate)
*******************************************************************************/
int FindCubicRoots(const FLOAT coeff[4], FLOAT x[3]);
http://www.realitypixels.com/turk/opensource/index.html#CubicRoots