フェラーリの方法
の形の実数係数4次方程式の解の公式を求める方法がフェラーリさんによって考案されたフェラーリの方法です。 まずは、これを導出してみます。
1. 4次の係数を1にする
まず、4次の項の係数を1にします。全体を4次の係数で割ってやります。
3次以下の係数をA,B,C,Dと置きなおして次に進みます。
このように最高次の係数を1にした1変数多項式をモニック多項式と呼んだりします。
2. 3次の項を削除する(チルンハウス変換)
次に、チルンハウス変換により3次の項の係数を0にします。3次項を消し去ります・・・。 と置換してやります。
係数をp,q,rと置いて次に行きます。
チルンハウス変換
一般のn次方程式に対してのような変数変換を行うと、n-1次の係数を0にすることができます。これをチルンハウス変換と呼びます。
ちなみに、5次方程式で出てくるチルンハウス変換はこれを推し進めた結果4~2次までの項を消し去ることに成功した、少しハイレベルなチルンハウス変換です。結果として手計算がほぼ不可能になっているようですが・・・。
3. 完全平方式に
4次式のままだと相変わらず解けないので、何とかして式の次数を落とします。
式を右辺と左辺に分け、両辺をに関する完全平方式にすることを考えます。どういうことかというと、の形にするのです。ただし、途中で出てくる係数を綺麗にするためにと置き換えて置きます。 まず、という公式を利用します。ここではまだ、は任意の数として具体的に決めないでおきます。
左辺をまず完全平方式にするために、4次の項だけを左辺に残して両辺にを加えて変形していきます。結果、左辺は完全平方式になりましたが右辺はまだそうなってはいません。右辺の式はよく見てみるとに関する2次式になっています。
ここで、2次式が完全平方式になるためにはその判別式()が0でなければならないという条件を利用します。その条件を満足するようにの値を決めてやります。
と、このようにに関しての3次方程式が出てきました。この3次方程式の3つの解のうち1つを選んでとしてやれば、の条件を満たしているため先ほどの式の右辺を完全平方式の形に置き換えることができます(3次方程式の求解は以前の記事に投げます)。
そして、ここで得られた3次方程式のことを三次分解方程式と呼びます。
それでは右辺を完全平方式に変形します。途中で、という関係を使って式を変形します。
途中で判別式のを利用して不要な項をまとめて消してしまっています(というか、これが出てくるため前述の条件がある)。結果、完全平方式一歩手前のすっきりした式になりました。
さらに変形してを求めてやりましょう。外に出ているを何とかしてかっこの中に埋め込みます。
半ば無理やりかっこの中へ押し込み、の形に持っていきました。下から二行目ではの有理化によって係数を少しすっきりとさせています。
4. 2次式へ
天下り的に4次方程式から両辺をに関する完全平方式の形に変換していましたが、なぜそのようなことをするのでしょうか?得られた完全平方式を変形してみると
2行目→3行目では、因数分解でよく出る二乗の公式()を使っています。
このように二つのに関する2次方程式の積に変形することができます。この式が成り立つのはどちらか(もしくは両方)の2次式が0になるとき、つまりがそれぞれの2次方程式の解となるときです。
遠回りをしてきたので印象薄いかもしれませんが、ここのはチルンハウス変換後の式から来ています。つまり、この二つの2次式の解として求められるこそが欲しかった4次方程式の解となります。
2次方程式の解の公式は分かっているのでそれを利用すれば4次方程式の解の公式は以下のようになります。
必要な係数はここまでですでに求まっていますので、この式は解くことができそうです。
5. 得られた各値より解を求める
無事にが4つ得られたので、チルンハウス変換を解いてを求めてやりましょう。と言ってもそのままそれぞれのについて
としてやれば、4次方程式の4つの解を求めることができます。
先ほどの式をまとめて少し展開すると
は二つの2次式より、は解の公式より来ているものなので独立に変化します。分かりやすく分けて、チルンハウス逆変換も含めて書けば各式は以下のようになります。
判別式
3次以下の多項式に判別式があったように、4次方程式にも判別式があります。一見すると、最終的に求められた公式の2,3次と同じようにルートの中身が使えそうに見えますが、この中のプラスマイナスは2組の2次方程式の解の公式毎に独立しています。つまり、2次方程式毎の判別式としてしか使えません。
Wikipediaによると4次方程式の判別式は以下のようになります(としてここで求めた式に合わせて係数も置き換えています)。
このは3次以下の時と同じように
となります。
解の様子がどうなるかは分かりますが、この式はここで求めた解の公式(の各項)との関連が見られず、計算したとしても使いまわせないので今回の実装においては使用しないことにします。
一部の係数が0になる場合の変形
複二次式
という形の4次方程式に対して、奇数次の項の係数が0になっている形の4次方程式を複二次式と呼びます。
複二次式はについての二次方程式と見ることで簡単に解くことができます。
とおいてやるととなるので、二次方程式の解の公式を適用してやると
この様に、簡単に解を求めることができます。
求めたの場合は、のときにとなって、と置いてやれば同じように、と求めることができます。
4次方程式には4つの解がありますが、複二次式の場合は二次方程式を解いた解が2つ出てきて、最後に平方根を求めるところでそれぞれ最大2つの解が得られます。
の場合
の切片の場合も式を簡単にすることができます。
となり、解はとの3つの解となります。
C++実装
さて、必要な公式などは一通りそろったのでC++コードにコピペしていきます。公式通り順番にやっていきます。
3次式の時と同じく、値型はテンプレートにしておきます。相変わらず構造化束縛を使用しているのでC++17未満のコンパイラではstd::tieを使う等してください。
まずstep1、4次の係数を1にするところです。
template<typename T> auto SolveQuarticEquation(const T a, const T b, const T c,const T d, const T e) -> std::tuple<std::complex<T>, std::complex<T>, std::complex<T>, std::complex<T>> { if (a == T(0.0)) { constexpr std::tuple<std::complex<T>> zero{}; auto x = SolveCubicEquation(b, c, d, e); return std::tuple_cat(std::move(x), zero); } else if (b == T(0.0) && d == T(0.0)) { //複二次式 return BiquadraticEquation(a, c, e); } return SolveQuarticEquation(b / a, c / a, d / a, e / a); }
普通に各係数を割り算をするだけ。もし4次の係数がゼロなら三次方程式として、奇数次係数がゼロなら複二次式として解いてやります。
template<typename T> auto SolveQuarticEquation(const T A, const T B, const T C, const T D) -> std::tuple<std::complex<T>, std::complex<T>, std::complex<T>, std::complex<T>> { //(A/4) const auto A_d4 = A / T(4.0); //(A/4)^2 const auto A_d4_sq = A_d4 * A_d4; //(A/4)^3 const auto A_d4_cu = A_d4 * A_d4_sq; const auto p = T(-6.0) * A_d4_sq + B; const auto q = T(2.0) * (T(4.0) * A_d4_cu - B * A_d4) + C; const auto r = (T(-3.0) * A_d4_cu - C) * A_d4 + B * A_d4_sq + D; auto [y1, y2, y3, y4] = SolveQuarticEquation(p, q, r); return std::make_tuple(y1 - A_d4, y2 - A_d4, y3 - A_d4, y4 - A_d4); }
step2、チルンハウス変換部分。を求めて、次のステップに渡します。帰ってきた各からを引いてチルンハウス変換を戻し、最終的な解として返します。
template<typename T> auto SolveQuarticEquation(const T p, const T q, const T r) -> std::tuple<std::complex<T>, std::complex<T>, std::complex<T>, std::complex<T>> { if (q == T(0.0)) { //複二次式 return BiquadraticEquation(T(1.0), p, r); } else if (r == T(0.0)) { constexpr std::tuple<std::complex<T>> zero{}; auto x = SolveCubicEquation(p, q); return std::tuple_cat(std::move(x), zero); } //tの式の各係数を求める //t^3 -pt^2-4rt+(4pr-q^2) = 0 const auto r4 = T(4.0)*r; const auto c =r4*p - q * q; std::complex<T> t_c{}; //tのうち実数のものを選択 std::tie(t_c, std::ignore, std::ignore) = SolveCubicEquation(-p, -r4, c); const auto t = t_c.real(); //t-p const auto t_p = t - p; if (T(0.0) <= t_p) { //m = √(t-p) const auto m = std::sqrt(t_p); //n = q/2√(t-p) const auto n = q / (T(2.0)*m); //t/2 const auto half_t = T(0.5)*t; //2つの二次式を解く! auto&& y_12 = SolveQuadraticEquation(T(1.0), m, half_t - n); auto&& y_34 = SolveQuadraticEquation(T(1.0), -m, half_t + n); return std::tuple_cat(std::move(y_12), std::move(y_34)); } else { //負の数の平方根を求めなければならない場合 //m = √(t-p) const std::complex<T> m = { 0.0, std::sqrt(-t_p) }; //n = q/2√(t-p) const auto n = q / (T(2.0)*m); //t/2 const auto half_t = T(0.5)*t; //複素係数二次方程式 auto&& y_12 = SolveQuadraticEquation({ T(1.0) }, m, half_t - n); auto&& y_34 = SolveQuadraticEquation({ T(1.0) }, -m, half_t + n); return std::tuple_cat(std::move(y_12), std::move(y_34)); } }
最後のstep、二つの二次方程式の解を求めることで各の値を求めます。ここでもの場合に複二次式として、の場合には3次方程式として、それぞれ飛ばします。
を3次方程式から求めますが、3次方程式は必ず一つは実数解があるのでそれを使うようにします(前回実装の通りなら、3次方程式の一つ目の解が実数解になるようになっています)。
しかしそれでも、の中身が負になってしまうと結局複素数が出て来てしまうのでその場合は複素数係数の2次方程式を解くようにします(その選択はオーバーロードにやってもらっています)。
template<typename T> auto BiquadraticEquation(const T a, const T c, const T e) -> std::tuple<std::complex<T>, std::complex<T>, std::complex<T>, std::complex<T>> { auto [x1, x2] = SolveQuadraticEquation(a, c, e); auto sqrt_x1 = std::sqrt(x1); auto sqrt_x2 = std::sqrt(x2); return std::make_tuple(sqrt_x1, -sqrt_x1, sqrt_x2, -sqrt_x2); }
最後に、複二次式を解く部分。こちらも2次方程式求解部分に投げます。帰ってきた値の符号を反転させたものが残りの解です。
3次も4次も主に引算の桁落ちについて慎重な考慮が必要ですが、それはまたの機会に・・・
コードとテスト実行
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ
解の検証
四次方程式の解 - 高精度計算サイト
Githubにも上げておきます(ヘッダ分けしてあったり一部異なっています、後随時更新されると思います)
QuarticEquation.hpp