C言語で三角関数を正しく使うには、ヘッダの導入、コンパイル時のリンク、角度の単位(ラジアン)の理解が肝心です。
本稿ではsin、cos、tanの基本と、初心者がつまずきやすい点を、実行できるサンプルとともに丁寧に解説します。
特に度ではなくラジアンで渡す点と、tanの発散、浮動小数点誤差には注意が必要です。
三角関数を使う準備
math.hをインクルードする
C言語の三角関数は標準ライブラリmath.h
に宣言されています。
必ずインクルードしてから使用します。
#include <stdio.h> // printf用
#include <math.h> // sin, cos, tanなどの宣言
三角関数を使わずにビルドすると、未宣言の関数呼び出しとして警告やリンクエラーの原因になります。
コンパイルとリンクの指定
UNIX系環境では、数学ライブラリにリンクするため-lm
指定が必要です。
WindowsのMSVCでは不要ですが、MinGWなどでは必要です。
# GCCの例(Linux, macOS)
gcc trig.c -std=c11 -O2 -Wall -Wextra -lm
# Clangの例
clang trig.c -std=c11 -O2 -Wall -Wextra -lm
# MSVCの例(Developer Command Prompt)
cl /std:c11 /W4 trig.c # -lm相当は不要
リンクエラーが出たときは-lm
の付け忘れを疑うと良いです。
引数と戻り値の型はdouble
標準のsin
、cos
、tan
は、引数も戻り値もdouble
型です。
宣言は次のとおりです。
/* math.h より(概略) */
double sin(double x);
double cos(double x);
double tan(double x);
整数リテラルやfloat
を渡しても、関数呼び出し時に自動でdouble
へ拡張されます。
ただし、計算コストや精度を意識して型を合わせるのがよい実践です。
sinf, cosf, tanfなどの型別関数
float用やlong double用の関数も用意されています。
型に合わせて使うと無駄な型変換を避けられます。
sinf
、cosf
、tanf
…float版sinl
、cosl
、tanl
…long double版
#include <stdio.h>
#include <math.h>
int main(void) {
// float版
float deg_f = 30.0f;
const float PI_F = 3.14159265358979323846f;
float rad_f = deg_f * (PI_F / 180.0f);
float sf = sinf(rad_f), cf = cosf(rad_f), tf = tanf(rad_f);
// double版
double deg = 30.0;
const double PI = 3.14159265358979323846;
double rad = deg * (PI / 180.0);
double sd = sin(rad), cd = cos(rad), td = tan(rad);
// long double版
long double deg_l = 30.0L;
const long double PI_L = 3.141592653589793238462643383279502884L;
long double rad_l = deg_l * (PI_L / 180.0L);
long double sl = sinl(rad_l), cl = cosl(rad_l), tl = tanl(rad_l);
// printfではfloatはdoubleに昇格されるので%fでOK、long doubleは%Lf
printf("float: sin=%.7f cos=%.7f tan=%.7f\n", sf, cf, tf);
printf("double: sin=%.10f cos=%.10f tan=%.10f\n", sd, cd, td);
printf("long double: sin=%.15Lf cos=%.15Lf tan=%.15Lf\n", sl, cl, tl);
return 0;
}
float: sin=0.5000000 cos=0.8660254 tan=0.5773503
double: sin=0.5000000000 cos=0.8660254038 tan=0.5773502692
long double: sin=0.500000000000000 cos=0.866025403784439 tan=0.577350269189626
基本はdouble
から始めて、必要に応じてfloat
やlong double
を選ぶと理解しやすいです。
角度はラジアンで扱う
度からラジアンへの式
Cの三角関数は度ではなくラジアンで受け取ります。
換算式はrad = deg × π ÷ 180です。
#include <stdio.h>
#include <math.h>
static const double PI = 3.14159265358979323846;
double deg2rad(double deg) {
return deg * (PI / 180.0);
}
int main(void) {
double deg = 60.0;
double rad = deg2rad(deg);
printf("%.1f度 は %.15fラジアンです\n", deg, rad);
return 0;
}
60.0度 は 1.047197551196598ラジアンです
πの扱い
πの定義は環境差に注意します。
- 移植性重視…
const double PI = 3.14159265358979323846;
のように自前定義 - 計算で得る…
const double PI = acos(-1.0);
とすれば確実にπが得られます M_PI
…一部環境で<math.h>が定義。MSVCでは#define _USE_MATH_DEFINES
を<math.h>より前に書く必要があります
#include <stdio.h>
#define _USE_MATH_DEFINES // MSVCでM_PIを有効化
#include <math.h>
int main(void) {
#ifdef M_PI
printf("M_PI = %.17f\n", M_PI);
#else
const double PI = acos(-1.0);
printf("PI (from acos) = %.17f\n", PI);
#endif
return 0;
}
M_PI = 3.14159265358979312
自前定義かacos(-1.0)
で得る方法が移植性の面で安心です。
代表角のラジアン値
よく使う角度とラジアンの関係をまとめます。
度からラジアンへ即座に変換できるとコーディングが安定します。
度(deg) | πの倍数表現 | ラジアン近似値 |
---|---|---|
0 | 0 | 0.000000 |
30 | π/6 | 0.523599 |
45 | π/4 | 0.785398 |
60 | π/3 | 1.047198 |
90 | π/2 | 1.570796 |
180 | π | 3.141593 |
270 | 3π/2 | 4.712389 |
360 | 2π | 6.283185 |
sin, cos, tanの基本的な使い方
sinの使い方
正弦sin(x)
は対象角の縦方向成分を返します。
例として30度の正弦は0.5です。
#include <stdio.h>
#include <math.h>
int main(void) {
const double PI = 3.14159265358979323846;
double x = PI / 6.0; // 30度
printf("sin(π/6) = %.10f\n", sin(x));
return 0;
}
sin(π/6) = 0.5000000000
cosの使い方
余弦cos(x)
は横方向成分です。
60度の余弦は0.5になります。
#include <stdio.h>
#include <math.h>
int main(void) {
const double PI = 3.14159265358979323846;
double x = PI / 3.0; // 60度
printf("cos(π/3) = %.10f\n", cos(x));
return 0;
}
cos(π/3) = 0.5000000000
tanの使い方
正接tan(x)
はsin(x)/cos(x)
に等しい値です。
45度の正接は1です。
#include <stdio.h>
#include <math.h>
int main(void) {
const double PI = 3.14159265358979323846;
double x = PI / 4.0; // 45度
printf("tan(π/4) = %.10f\n", tan(x));
return 0;
}
tan(π/4) = 1.0000000000
基本サンプルコード
よく使う角度を配列で回して、度→ラジアン変換と三角関数の組を一気に表示するサンプルです。
#include <stdio.h>
#include <math.h>
/* πの自前定義。移植性のために定数で持ちます */
static const double PI = 3.14159265358979323846;
static double deg2rad(double deg) {
return deg * (PI / 180.0);
}
int main(void) {
double degrees[] = {0.0, 30.0, 45.0, 60.0}; // 90度はtanが発散するため別節で解説
size_t n = sizeof degrees / sizeof degrees[0];
for (size_t i = 0; i < n; ++i) {
double d = degrees[i];
double r = deg2rad(d);
double s = sin(r);
double c = cos(r);
double t = tan(r);
printf("%6.1f度 -> rad=% .10f sin=% .10f cos=% .10f tan=% .10f\n",
d, r, s, c, t);
}
return 0;
}
0.0度 -> rad= 0.0000000000 sin= 0.0000000000 cos= 1.0000000000 tan= 0.0000000000
30.0度 -> rad= 0.5235987756 sin= 0.5000000000 cos= 0.8660254038 tan= 0.5773502692
45.0度 -> rad= 0.7853981634 sin= 0.7071067812 cos= 0.7071067812 tan= 1.0000000000
60.0度 -> rad= 1.0471975512 sin= 0.8660254038 cos= 0.5000000000 tan= 1.7320508076
環境により末尾の桁がわずかに異なる場合があります。
戻り値の範囲
範囲を理解しておくと不思議な値に戸惑いません。
sin(x)
、cos(x)
…理論上は[-1, 1]。ただし浮動小数点誤差でごくわずかに外れるように見えることがありますtan(x)
…全実数範囲に広がります。cos(x)=0の点(±π/2, ±3π/2, …)で発散し、実装によってはinf
を返すことがあります
初心者がつまずきやすいポイント
tan(90度付近)は発散に注意
90度(π/2)ではcosが0になり、tanは定義できません。
近い角度でも非常に大きな値になります。
#include <stdio.h>
#include <math.h>
static const double PI = 3.14159265358979323846;
static double deg2rad(double deg) { return deg * (PI / 180.0); }
int main(void) {
double d1 = 89.999; // 90度に非常に近い
double r1 = deg2rad(d1);
double t1 = tan(r1);
double d2 = 90.0;
double r2 = deg2rad(d2);
printf("tan(%.3f度) = %.3f\n", d1, t1);
// cosがほぼ0であれば、発散と見なして直接tanを使わない
if (fabs(cos(r2)) < 1e-12) {
printf("tan(%.1f度) は発散(非定義)です\n", d2);
} else {
printf("tan(%.1f度) = %.6f\n", d2, tan(r2));
}
return 0;
}
tan(89.999度) = 57295.780
tan(90.0度) は発散(非定義)です
分母となるcos(x)
が0に近いかを判定して回避するのが実務では重要です。
度とラジアンの取り違え
よくあるミスは度のままsin
に渡すことです。
sin(90)は1ではありません(90ラジアンの正弦)。
#include <stdio.h>
#include <math.h>
static const double PI = 3.14159265358979323846;
static double deg2rad(double deg) { return deg * (PI / 180.0); }
int main(void) {
double wrong = sin(90.0); // 誤り: 90はラジアン
double right = sin(deg2rad(90.0)); // 正しい: 90度→ラジアンへ変換
printf("sin(90.0) = %.10f (誤り)\n", wrong);
printf("sin(deg2rad(90)) = %.10f (正しい)\n", right);
return 0;
}
sin(90.0) = 0.8939966636 (誤り)
sin(deg2rad(90)) = 1.0000000000 (正しい)
常に入力の単位を明示し、必要ならdeg2rad
ヘルパを用意して統一しましょう。
浮動小数点の誤差
理論上0になるはずの値が0にならないのはよくある現象です。
sin(π)
は0のはずですが、丸め誤差でごく小さな値になります。
#include <stdio.h>
#include <math.h>
#include <float.h>
int main(void) {
const double PI = 3.14159265358979323846;
double s = sin(PI);
printf("sin(π) = %.20f\n", s);
// しきい値(イプシロン)で近似比較する
double eps = 1e-12;
if (fabs(s) < eps) {
printf("sin(π)は誤差内で0とみなせます (|s| < %.0e)\n", eps);
} else {
printf("sin(π)は0から有意に離れています\n");
}
return 0;
}
sin(π) = 0.00000000000000024492
sin(π)は誤差内で0とみなせます (|s| < 1e-12)
等号比較==
で浮動小数点を直接比べないのが鉄則です。
許容誤差内で比較しましょう。
角度の正規化
角度を加算・減算していくと、範囲がどんどん広がって扱いづらくなります。一般には[0, 2π)や[-π, π)に正規化します。
#include <stdio.h>
#include <math.h>
static const double PI = 3.14159265358979323846;
static const double TWO_PI = 2.0 * 3.14159265358979323846;
/* [0, 2π) に正規化する簡易版。極端に大きな値には回数が増える点に注意 */
double normalize_rad_0_2pi(double rad) {
while (rad >= TWO_PI) rad -= TWO_PI;
while (rad < 0.0) rad += TWO_PI;
return rad;
}
/* [-π, π) に正規化 */
double normalize_rad_mpi_pi(double rad) {
rad = normalize_rad_0_2pi(rad);
if (rad >= PI) rad -= TWO_PI;
return rad;
}
int main(void) {
double a = 15.0 * TWO_PI + PI / 6.0; // たくさん周回した角度
printf("元: %.6f, [0,2π): %.6f, [-π,π): %.6f\n",
a, normalize_rad_0_2pi(a), normalize_rad_mpi_pi(a));
return 0;
}
元: 95.105652, [0,2π): 0.523599, [-π,π): 0.523599
性能や巨大値対応が必要ならfmod
を使う方法もありますが、詳細は別記事で扱います。
まとめ
本稿ではmath.hをインクルードし、ラジアンで三角関数を呼ぶという基礎から、πの扱い、型別関数(sinf/cosf/tanf, sinl/cosl/tanl)、代表角のラジアン、そしてsin/cos/tanの基本的な使い方を解説しました。
特にtan(90度付近)の発散、度とラジアンの取り違え、浮動小数点誤差は初心者が陥りやすいポイントです。
これらを避けるために、度→ラジアン変換ヘルパを用意し、入力単位を明示、必要に応じた誤差許容で比較する習慣を身につけましょう。
まずはdoubleと基本サンプルから始め、慣れてきたらfloat/long doubleや正規化などの工夫を取り入れると、より堅牢で読みやすいコードになります。