【Rustで作る電卓】cos関数実装 Rustで挑む三角関数の実装(2) マクローリン展開と範囲還元 #19

前回の sin 実装では、マクローリン展開の限界と「範囲縮小(Range Reduction)」の重要性が見えてきました。今回は、その相棒である cos を実装し、ついに「\(\pi/4\) 還元」という実用レベルのアルゴリズムへと昇華させていきましょう。

前回 sinの実装と課題: 【Rustで作る電卓】sin関数実装 Rustで挑む三角関数の実装(1) マクローリン展開と範囲還元 #18

マクローリン展開を用いてcosを計算機に実装する記事のサムネイル。

1. cos関数(余弦波関数)の計算

sin と同様に、まずはもっとも基本的な近似手法であるマクローリン展開から考えます。\(\cos(x)\) を \(x=0\) の周りで展開すると、偶数次の項のみが現れる美しい等式になります。
\[ \cos(x) = 1 – \frac{x^2}{2!} + \frac{x^4}{4!} – \frac{x^6}{6!} + \cdots = \sum_{n=0}^{\infty} (-1)^n \frac{x^{2n}}{(2n)!} \]
こちらも f64 精度を目指し、まずは 10〜12項(20〜24次)程度をターゲットに実装してみます。

cos 関数を超シンプルに実装してみた例

pub fn cos_simple(x: f64) -> f64 {
    let mut sum = 0.0;
    let mut term = 1.0; // 第0項は x^0 / 0! = 1
    let x2 = x * x;

    for i in 1..=12 {
        sum += term;
        
        // 次の項を計算するための漸化式:
        // 次の項 = 前の項 * (-x^2) / ((2n-1) * (2n))
        let n = i as f64;
        term *= -x2 / ((2.0 * n - 1.0) * (2.0 * n));
    }
    sum
}

2. 余角の関係を利用する

前回説明したように(前回のコラム 余角度の関係)、範囲を還元しないと計算結果が爆発するので、前回実装したsinと合わせて最適化を行っていきます。

前回の説明でも紹介しましたが、周期性からmod(2π)で→対称性から\([-\pi/2, \pi/2]\)余角の関係から\([-\pi/4, \pi/4]\)へと範囲を還元(縮小)することでマクローリン展開が得意な0に近い値に対して計算を行うように改良するという方法で高速化と高精度化を行っていきます。

余角の公式を図で詳細に説明した画像

3.1 実装

余角の公式含めて、小さい角に対して計算させるsin_kernelとcos_kernel関数を作り、sinやcos関数で周期や余角からどちらで計算させるか判定し\([-\pi/4, \pi/4]\)に範囲還元したものをsin_kernelとcos_kernelに渡すという処理を行うように実装してみました。

sinや三角関数の範囲縮小(range reduction)の流れ
// 実装してみた
pub fn sin(mut x: f64) -> f64 {
    let pi_2 = PI / 2.0;
    let pi_4 = PI / 4.0;

    // 1. 周期還元
    x %= 2.0 * PI;
    if x < 0.0 { x += 2.0 * PI; }

    // 2. 象限ごとの処理と π/4 への追い込み
    if x <= pi_4 {
        kernel_sin(x)
    } else if x <= 3.0 * pi_4 {
        kernel_cos(x - pi_2)
    } else if x <= 5.0 * pi_4 {
        -kernel_sin(x - PI)
    } else if x <= 7.0 * pi_4 {
        -kernel_cos(x - 1.5 * PI)
    } else {
        kernel_sin(x - 2.0 * PI)
    }
}

pub fn cos(mut x: f64) -> f64 {
    let pi_2 = PI / 2.0;
    let pi_4 = PI / 4.0;

    // 1. 周期還元
    x %= 2.0 * PI;
    if x < 0.0 { x += 2.0 * PI; }

    // 2. 象限ごとの処理と π/4 への追い込み
    if x <= pi_4 {
        kernel_cos(x)
    } else if x <= 3.0 * pi_4 {
        -kernel_sin(x - pi_2)
    } else if x <= 5.0 * pi_4 {
        -kernel_cos(x - PI)
    } else if x <= 7.0 * pi_4 {
        kernel_sin(x - 1.5 * PI)
    } else {
        kernel_cos(x - 2.0 * PI)
    }
}

sin (“1億”)を計算してみた結果

range-reduction(範囲還元)を行い、実装した自作sin関数でsin(1億)を計算させた結果。誤差はあるが小数第9位程度に抑えることができた

計算結果 sin(”一億”)

比較対象計算結果(\(sin(10^8)\))
f64::sin (Rust標準)0.931639027109726
自作sin (範囲還元版)0.9316390256931886
Python (80桁精度)0.9316390271097260080275166...

標準関数が多倍長精度の結果と一致しているのに対し、自作関数は小数点第9位付近から誤差が生じ始めました。手元でサクッと計算する程度の利用であればそこまで問題がない程度の精度を出せたかなと思います。
これは、前回の考察で触れた「\(\pi\) 自体の精度不足」が、引数が大きくなることで顕在化した決定的な証拠です。\(10^8\) という巨大な数に対して、\(f64\) の持つ約16桁の精度では、周期を引いた後の「あまり」を正確に保持できないのです。

3. まとめ

1億 rad を入力すると、前回の実験結果の通り、std::f64::sin とは小数点第9位あたりで決別してしまいます。

自作sin(1億): 0.9316390256931886
標準sin(1億): 0.931639027109726
誤差: \(\approx 1.4 \times 10^{-9}\)

この誤差は、どんなにマクローリン展開の項数を増やしても、\(\sin/\cos\) を巧妙に入れ替えても決して埋まりません。原因は計算式ではなく、「引数を \(2\pi\) で割った瞬間に、私たちが \(f64\) という有限の精度の \(\pi\) を選んでしまったこと」にあります。

標準ライブラリはこの巨大な数値をどうやって処理しているのか?

次回は三角関数実装の聖域、「Payne-Hanek 法」を実装して改良していこうと思います。
次回:関連記事は、2026年5月28日に公開予定 (あと1時間)

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

では、次の記事で。 lumenHero

関連記事

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

シンプルなsinの実装と課題 : 【Rustで作る電卓】sin関数実装 Rustで挑む三角関数の実装(1) マクローリン展開と範囲還元 #18
余角とcosの実装: 【Rustで作る電卓】cos関数実装 Rustで挑む三角関数の実装(2) マクローリン展開と範囲還元 #19
Payne-Hanek法の詳細→Payne-Hanek 法詳解:ビットスライドで読み解く高精度な剰余演算と Rust による最小実装
巨大数対応sin関数関連記事は、2026年5月28日に公開予定 (あと1時間)

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

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