【Rustで作る電卓】sinとcos完成 Rustで挑む三角関数の実装(4) #21

ここ数回にわたり、三角関数をマクローリン展開から実装してきました。前回は、高精度レンジリダクションの手法であるPayne-Hanek法(ペイン・ハネック法)を用いて、三角関数を巨大数でも精度を出せるように改良しsin関数を実装しました。

今回はちょっと息抜きに前回のsin関数をもとにcos関数を実装し、三角関数の実装手順を振り返っていこうと思います。

シンプルなsinの実装と課題 : 【Rustで作る電卓】sin関数実装 Rustで挑む三角関数の実装(1) マクローリン展開と範囲還元 #18
余角とcosの実装: 【Rustで作る電卓】cos関数実装 Rustで挑む三角関数の実装(2) マクローリン展開と範囲還元 #19
Payne-Hanek法の詳細→Payne-Hanek 法詳解:ビットスライドで読み解く高精度な剰余演算と Rust による最小実装
前回巨大数対応sin関数【Rustで作る電卓】Payne-Hanek法 Rustで挑む三角関数の実装(3) 高精度レンジリダクション #20

payne-hanek法で1億ラジアンでも狂わない三角関数を実装するcos関数編

1. 巨大数対応のcos関数を実装する。

前回までの実装で部品はそろっているので、条件分岐させながら呼び出すだけで実装できます。

  • kernel_cosとkernel_sin : [0,π/2]範囲で正確にcosとsinの値を返す。
  • cos4small :kernel_cosとkernel_sinを用い比較的小さい数には通常の割り算で範囲縮小しcosを求める。
  • reduce_payne_hanek :通常の割り算だと誤差が大きくなってしまうものについては前回紹介したPayne-Hanek法で範囲縮小し第何象限なのか[0,π/2]に範囲還元した結果を返す
/// Payne-Hanek法を統合した cos 関数 hanek_cos
pub fn cos(x: f64) -> f64 {
    let abs_x = x.abs();
    
    // 小さな数は、これまでの安全な方法で計算する
    if abs_x < 100.0 {
        return cos4small(x);
    }

    let (reduced_x, quadrant) = reduce_payne_hanek(abs_x);
    // cosは偶関数なので符号は無視してよい let sign = if x < 0.0 { -1.0 } else { 1.0 };
    //範囲還元結果はcosに合わせて入れ替える
    let res = match quadrant {
        0 => kernel_cos(reduced_x),
        1 => kernel_sin(reduced_x), // cos(pi/2 + x) = sin(x)
        2 => -kernel_cos(reduced_x), // cos(pi + x) = -cos(x)
        3 => -kernel_sin(reduced_x), // cos(3pi/2 + x) = -sin(x)
        _ => unreachable!(),
    };
    res
}

2. 三角関数(sin,cos)の実装結果

三角関数の計算をフルスクラッチしてみた結果です。
f64(倍精度浮動小数点数)の有効桁数は約15.9桁です。今回の実装では、内部計算の丸め誤差を含め、小数第14位〜15位程度まで正確な値を得られています。これ以上の精度(例えば30桁など)を求めるには、Double-Double演算などの特殊な手法が必要になりますが、実用上はこれで十分すぎる性能と言えるでしょう。

sin(11111)を自作関数で計算した結果とf64::sinを比較してみた

sin(11111)を計算してみた例

cos(3000)を自作関数で計算した結果とf64::cosを比較してみた

cos(3000)を計算してみた例

3. 三角関数の実装を振り返る。

ただの波と考えていた、三角関数をコンピュータで計算するのは、実は非常に奥が深いものでした。これまでのプロセスを図解すると以下のようになります。

実装のロードマップ

  1. マクローリン展開(基礎)
    • \(0\) 付近の値を多項式で近似する。
    • 課題: \(0\) から離れると急激に精度が落ちる。
  2. レンジリダクション(基本)
    • \(2\pi\) で割った余りを使うことで、入力を \([0, 2\pi]\) に押し込める。
    • 課題: \(x\) が巨大になると \(\pi\) の精度不足で「情報落ち」が発生する。
  3. 高精度レンジリダクション(Payne-Hanek法)
    • \(\pi\) の逆数を数百ビット先まで用意し、巨大数でも正確に余りを出す。
    • 結果: どんな大きな数でも \(10^{-14}\) の精度を維持可能に!
  4. カーネル関数による最適化
    • レンジリダクションで\([0, \pi/2]\) の範囲だけに特化すればよくなったので、ここに最適化した高精度な sin / cos 計算機を作る。

4. まとめ

マクローリン展開という数学の基礎から出発し、巨大数問題というコンピュータ特有の壁を Payne-Hanek 法で乗り越え、ようやく満足のいく sincos が完成しました。
実際に作ってみることで、浮動小数点数の扱いやアルゴリズムの工夫を深く学ぶことができました。

速度や正確さを考えると、ハードウェアレベルで実装されている標準ライブラリに任せるのが一番ですが、その『ブラックボックス』の中で何が行われているかを知ることで、浮動小数点数の限界やアルゴリズムの工夫を肌で感じることができました。
次は、ちょっと曲者な tanをフルスクラッチしながら計算機の中身を理解していこうと思います。

次回:関連記事は、2026年5月30日に公開予定 (あと5時間) 
ゼロ除算をどう回避するかが焦点になりそうです。

ここまで読んでいただきありがとうございます。

では、次の記事で。 lumenHero


関連記事

以下の記事を読めば、自力で三角関数の電卓を実装できます。

シンプルなsinの実装と課題 : 【Rustで作る電卓】sin関数実装 Rustで挑む三角関数の実装(1) マクローリン展開と範囲還元 #18
余角とcosの実装: 【Rustで作る電卓】cos関数実装 Rustで挑む三角関数の実装(2) マクローリン展開と範囲還元 #19
Payne-Hanek法の詳細→Payne-Hanek 法詳解:ビットスライドで読み解く高精度な剰余演算と Rust による最小実装
巨大数対応sin関数【Rustで作る電卓】Payne-Hanek法 Rustで挑む三角関数の実装(3) 高精度レンジリダクション #20

ほかの関数の実装が気になる方以下がおすすめ

第11回 平方根の計算実装:【Rust自作TUI電卓】mod.rsを活用したスマートなモジュール管理と基本数学関数の実装 #11
第14回 ハレー法による立方根:
【Rust自作TUI電卓】ニュートン法を超えろ!ハレー法による立方根(cbrt)の極限最適化 #14
第15回 logの計算基本編:
【Rustで作る電卓】浮動小数点数の構造をハックして対数関数(log)を実装する #15