皆さん、こんにちは。リーダーです。
組み込みエンジニアとして大手電機メーカーで20年以上、泥臭くコードを書き続けてきましたが、若手のコードレビューをしていて最も「惜しい!」と思うポイントの一つが三角関数の扱いです。
「計算結果がなんかズレるんです」と相談に来る新人のコードを見ると、大抵は度数法(Degree)と弧度法(Radian)をごっちゃにしていたり、精度不足のfloatで無理やり計算していたり……。
三角関数はゲーム開発やロボット制御、画像処理など、あらゆる現場で使われる「数学の道具」ですが、C++で正しく、かつ効率的に使うにはいくつかの「作法」があります。
今日は、仕事帰りの晩酌を楽しみにしている私が、酔いが回る前にシラフでしっかりと、C++における三角関数の流儀を叩き込んでいきたいと思います。
執筆者:リーダー |
|
基本中の基本:cmathヘッダーと標準的な使い方
まず、C++で三角関数(sin, cos, tan)を使うには、<cmath> というヘッダーファイルをインポートするのが鉄則です。
C言語時代の &math.h> も使えますが、C++の流儀としては名前空間 std:: が使える <cmath> を選びましょう。
#include <iostream>
#include <cmath> // 三角関数を使うための必須ヘッダー
int main() {
double angle_rad = 1.04719755; // 約60度をラジアンで表現
// 基本の3関数。戻り値はdouble型
double s = std::sin(angle_rad);
double c = std::cos(angle_rad);
double t = std::tan(angle_rad);
std::cout &lt;&lt; "sin: " &lt;&lt; s &lt;&lt; " cos: " &lt;&lt; c &lt;&lt; " tan: " &lt;&lt; t &lt;&lt; std::endl;
return 0;
}
このコードのポイントは、引数として渡している値が「度数(0~360)」ではなく「ラジアン(0~2π)」であることです。
ここを間違えると、月面着陸を目指しているのに地面に激突するような、とんでもないバグを引き起こします。
ちなみに、C++の std::sin たちは「オーバーロード」されています。
floatを渡せばfloatが返り、doubleを渡せばdoubleが返る。
現場では精度の要求に合わせて型を使い分けるのがプロの仕事です。
現場で100回は書く「度数法とラジアン」の変換術
エンジニアの頭は「30度」「90度」といった度数法で考えがちですが、C++の関数はすべてラジアンを要求します。
そこで、変換用の定数や関数を自分で定義しておくのが、現場の「共通言語」になります。
#include <iostream>
#include <cmath>
// 度数法から弧度法への変換定数
const double PI = 3.14159265358979323846;
double deg_to_rad(double deg) {
return deg * (PI / 180.0);
}
int main() {
double degree = 90.0;
double radian = deg_to_rad(degree);
// 90度のsinは1.0になるはず
std::cout &lt;&lt; degree &lt;&lt; "度のsinは: " &lt;&lt; std::sin(radian) &lt;&lt; std::endl;
return 0;
}
コードを見てわかる通り、度数 * (π / 180.0) が変換公式です。
なぜこんな面倒なことをするのか?それは、ラジアンが「半径と弧の長さの比」に基づいた、自然界の計算において最も合理的な単位だからです。
「πなんて 3.14 でいいだろ」と思っている君、甘いですよ。
組み込みの現場では、そのわずかな誤差が積もり積もって、ロボットの腕を数センチ狂わせるんです。
逆三角関数と、最強の関数「atan2」
「辺の長さから角度を求めたい」という時に使うのが、逆三角関数(asin, acos, atan)です。
しかし、現場で最も重宝されるのは、ただの atan ではなく atan2 です。
#include <iostream>
#include <cmath>
int main() {
double y = 10.0;
double x = 10.0;
// atan(y/x) ではなく atan2(y, x) を使うのがプロの流儀
double angle = std::atan2(y, x);
// ラジアンから度数に戻して確認
std::cout &lt;&lt; "角度(度): " &lt;&lt; angle * (180.0 / 3.14159265) &lt;&lt; std::endl;
return 0;
}
なぜ std::atan2 が最強なのか。
それは、x が 0 の場合(90度や270度)でもエラーにならず、さらに全方位(-πからπまで)の角度を正しく返してくれるからです。
ターゲットへの方向を計算するプログラムを書くなら、迷わず atan2 を使いなさい。
これは私が後輩に一番最初に教える「秘伝のタレ」のようなものです。
C++20からの新常識:std::numbersでπを扱う
今まで M_PI を #define したり、自分で定義したりしてきましたが、モダンなC++(C++20以降)では、標準で高精度な数学定数が用意されています。
#include <iostream>
#include <numbers> // C++20から導入
#include <cmath>
int main() {
// std::numbers::pi で最高精度のπが手に入る
double val = std::sin(std::numbers::pi / 2.0);
std::cout &lt;&lt; "sin(π/2): " &lt;&lt; val &lt;&lt; std::endl;
return 0;
}
最新のコンパイラが使える環境なら、わざわざ 3.1415... と手入力するのはやめましょう。
タイポの元ですし、何よりスマートじゃありません。
std::numbers::pi を使うことで、その環境が許す最大の精度でπを利用できます。
精度と型の使い分け:float, double, long double
最後に、型について一言。
組み込みの世界ではメモリを節約するために float を使いたがることが多いですが、複雑な三角関数の計算が続く場合、累積誤差で結果が化けることがあります。
- float (std::sinf):メモリ制限が厳しく、精度がそこまで重要でない場合。
- double (std::sin):標準。迷ったらこれ。
- long double (std::sinl):天文学的な精度や、極めて微細な計算が必要な場合。
現場では「とりあえずdouble」で実装し、ボトルネックやリソースの問題が出てから最適化するのが、私の経験則から導き出された効率的なやり方です。
まとめ|基礎の積み重ねが「動くコード」を作る
C++の三角関数について、重要なポイントを振り返りましょう。
<cmath> を使い、名前空間 std:: を意識する。
引数は常に「ラジアン」であると肝に銘じる。
方向(ベクトル)から角度を出すなら atan2 一択。
C++20以降なら std::numbers::pi で精度と楽を手に入れる。
たかが数学、されど数学です。
コンピュータという冷徹な機械の上で、私たちの思い通りに世界を動かすには、こうした基礎的な関数一つひとつの挙動を正しく理解し、敬意を払う必要があります。
さて、講義はここまで。
私もこれから、自宅の「度数」の高い飲み物で、自分自身の関数をリセットしてくるとしますよ。
以上、リーダーでした。



コメント