卡尔达诺公式是解三次方程的一种方法,由意大利数学家卡尔达诺首次提出。虽然维基百科上有关于这个方法的描述,但细节并不完整,尤其是德文维基百科上的描述更是缺失了最后一部分,没有这部分内容,无法将其实现为算法。本文将从维基百科描述结束的地方继续,详细解释卡尔达诺公式的实现细节。
卡尔达诺公式的描述始于三次方程:
x^3 + ax^2 + bx + c = 0
虽然有些细节缺失,但维基百科的描述在以下部分之前是可以接受的:
...
现在事情变得有趣,大多数描述都变得模糊。存在三种可能性:
D >= 0
,
D = 0
,
D < 0
。
如果D > 0
,那么D
的根是实数,可以得到一个实数解和两个复数解:
u
和v
:
实数解是:
u0 = ...
v0 = ...
两个复数解的长度与u0
和v0
相同,每个解的复数角度分别为120°和240°。这意味着:
u1 = ...
v1 = ...
对于复数角度得到:
u2 = ...
v2 = ...
因此得到u
的三个可能解和v
的三个可能解,如果将它们组合起来,得到九个可能的解。但这并不是全部,因为并非所有解都是有效的。此外,之前有条款u*v = -p/3
,并非所有九种组合都满足这个条件。它只对组合u0 * v0
、u1 * v2
和u2 * v1
有效。让看看u1 * v2
。那是:
...
如果分离:
...
那么只有ε1 * ε2
有效。如果组合ε1 * ε1
,会剩下一个虚部,这不能给出-p/3
。这是卡尔达诺公式描述中最常见的遗漏部分:
因此,给定D > 0
,有三种解。但只有第一种是实数。
y1 = u0 + v0
y2 = u1 + v2
y3 = u2 + v1
在得到这些解的过程中,进行了替换:
x = y - a/3 = u + v - a/3
这不应该被忘记,因为最终要找的是x
。
如果将其转化为代码,它看起来像这样:
if (d > 0) {
u = Xroot(-q / 2.0 + Math.sqrt(d), 3.0);
v = Xroot(-q / 2.0 - Math.sqrt(d), 3.0);
x1.real = u + v - a / 3.0;
x2.real = -(u + v) / 2.0 - a / 3.0;
x2.imag = Math.sqrt(3.0) / 2.0 * (u - v);
x3.real = x2.real;
x3.imag = -x2.imag;
}
如果D = 0
,有相同的三种解,但由于D = 0
,u0 = v0
,并且y3 = y2
。这意味着只得到两个解。在代码中,它是:
if (d == 0) {
u = Xroot(-q / 2.0, 3.0);
v = Xroot(-q / 2.0, 3.0);
x1.real = u + v - a / 3.0;
x2.real = -(u + v) / 2.0 - a / 3.0;
}
如果D < 0
,这是所谓的“不可约情况”。现在D
的根变得复杂,u
和v
的立方根也变得复杂。
...
为了解决这个复杂根,将复数解释为长度r
和复数角度α
。通过上面的替换:
4((q/2)^2 + (p/3)^3) = 4D
得到u
和v
的相同长度:
...
角度变为u
:
...
角度变为v
:
...
现在要计算出这个的立方根,只需要计算出r
的立方根,基本上将α
除以3就可以了。至少对于第一个解是这样。复数的立方根不仅有一个解,而是有三个解。通过将α/3
乘以3,两次将(2π + α)/3
乘以3,第三次将(4π + α)/3
乘以3来得到α
。这给出了u
的三个解和v
的三个解。它们看起来像:
...
由于αv = 2π - α
,这将再次给出九个可能的解。但由于对实数解感兴趣,可以将其减少到三个实数解。对于三种组合u0 + v2
、u1 + v1
、u2 + v0
,虚部变为0,解是:
...
if (d < 0) {
r = Math.sqrt(-p * p * p / 27.0);
alpha = Math.atan(Math.sqrt(-d) / -q * 2.0);
if (q > 0) {
// 如果q > 0,角度变为2 * PI - alpha
alpha = 2.0 * Math.PI - alpha;
}
x1.real = Xroot(r, 3.0) * (Math.cos((6.0 * Math.PI - alpha) / 3.0) + Math.cos(alpha / 3.0)) - a / 3.0;
x2.real = Xroot(r, 3.0) * (Math.cos((2.0 * Math.PI + alpha) / 3.0) + Math.cos((4.0 * Math.PI - alpha) / 3.0)) - a / 3.0;
x3.real = Xroot(r, 3.0) * (Math.cos((4.0 * Math.PI + alpha) / 3.0) + Math.cos((2.0 * Math.PI - alpha) / 3.0)) - a / 3.0;
}