C言語の逆三角関数は、三角関数の結果から角度(ラジアン)を求めるための関数です。
角度の求め方と戻り値の範囲、コンパイル時の注意点(-lmの指定など)、浮動小数点誤差への対処を、C初心者の方にもわかりやすく解説し、最後に動くサンプルを掲載します。
逆三角関数の基本
逆三角関数でできること
逆三角関数は、次のように三角関数の値から角度(主値)を計算します。
主値とは、数学的に定められた標準の範囲内の解のことです。
asin(x)
はsin(θ) = x
を満たす角度θ
の主値を返します。acos(x)
はcos(θ) = x
を満たす角度θ
の主値を返します。atan(x)
はtan(θ) = x
を満たす角度θ
の主値を返します。
戻り値はいずれもラジアンで返されます。
ここを度(°)と勘違いすると結果がおかしくなるため、後述の変換方法を必ず確認してください。
次の表に、引数の許容範囲と戻り値の範囲を整理します。
関数 | 引数の許容範囲 | 戻り値の主値範囲 | 例 |
---|---|---|---|
asin(x) | -1 ≤ x ≤ 1 | -π/2 ≤ θ ≤ π/2 | asin(0.5) = π/6 |
acos(x) | -1 ≤ x ≤ 1 | 0 ≤ θ ≤ π | acos(0.5) = π/3 |
atan(x) | すべての実数 | -π/2 < θ < π/2 | atan(1) = π/4 |
インクルードとリンク
逆三角関数は標準ヘッダー#include <math.h>
で宣言されます。
UNIX系環境のGCC/Clangでリンクする場合は必ず-lmを付けます。
WindowsのMSVCでは-lmは不要です。
// 例: 必要なインクルード
#include <stdio.h>
#include <math.h>
# LinuxやmacOS (GCC/Clang) のビルド例
gcc main.c -lm -o app
# MSVC のビルド例(Developer Command Prompt)
cl /EHsc main.c
-lmを付け忘れると「未定義参照(Undefined reference)」でリンクエラーになります。
引数と戻り値の型
C言語ではオーバーロードがないため、型ごとに関数名が分かれています。
最もよく使うのはdouble版です。
// <math.h> にある代表的なプロトタイプ
double asin(double x);
double acos(double x);
double atan(double x);
// float 版
float asinf(float x);
float acosf(float x);
float atanf(float x);
// long double 版
long double asinl(long double x);
long double acosl(long double x);
long double atanl(long double x);
値の型に合わせた関数を選ぶと、無駄な型変換を避けられ、精度や速度の面で有利です。
例えばfloat
の計算ならasinf
を使い、リテラルには0.5f
のようにfサフィックスを付けます。
asin/acos/atanの使い方
asinの使い方
asin(x)
はx
が[-1, 1]
のときに定義され、sin(θ)=x
を満たすθ
を返します。
例えばasin(0.5)
はπ/6
です。
Cでは数値はラジアンなので、度に直す場合はθ_deg = θ_rad * 180.0 / π
を使います。
補足: 端点の扱い
asin(1.0)
はπ/2
、asin(-1.0)
は-π/2
を返します。
asinの戻り値の範囲
戻り値の範囲は[-π/2, π/2]
です。
負の値も返り得るため、絶対値ではなく符号を含む角度として解釈してください。
acosの使い方
acos(x)
はx
が[-1, 1]
のときに定義され、cos(θ)=x
を満たすθ
を返します。
例えばacos(0.5)
はπ/3
です。
補足: 対称性
コサインは偶関数なのでacos(-x) = π - acos(x)
が成り立ちます。
数値検証の目安になります。
acosの戻り値の範囲
戻り値の範囲は[0, π]
です。
必ず非負になり、180度までの角度が返されます。
atanの使い方
atan(x)
は任意の実数x
に対して定義され、tan(θ)=x
を満たすθ
を返します。
例えばatan(1.0)
はπ/4
です。
補足: 傾きから角度へ
2D座標でy/x
が傾きのとき角度をatan(y/x)
で求められますが、象限の判定はできません。
象限も含めて正しい角度が必要な場合はatan2の使用を検討してください(別記事で解説予定)。
atanの戻り値の範囲
戻り値の範囲は(-π/2, π/2)
で、端点±π/2
は含みません。
非常に大きい正数で≈ +π/2
、非常に大きい負数で≈ -π/2
に近づきます。
asin/acos/atanの注意点
角度はラジアン
Cの数学関数はすべてラジアンで動作します。
度との変換は次式です。
- 度へ:
deg = rad * 180.0 / PI
- ラジアンへ:
rad = deg * PI / 180.0
PI
は自分で定義するのが移植性の点で安全です。
例えば#define PI 3.14159265358979323846
とします。
環境によってはM_PI
が使えますが、標準ではありません(MSVCでは#define _USE_MATH_DEFINES
を定義してから#include <math.h>
が必要です)。
範囲外入力はNaNになることがある
asin/acosの引数は必ず[-1, 1]
に収める必要があります。
範囲外だと多くの実装でNaNが返り、errno
にEDOM
が設定される場合があります。
浮動小数点誤差で1.0000000002
のようにはみ出すこともあるため、小さくクリップするのが実務的です。
// xを[-1, 1]に丸めてからasin/acosを呼ぶユーティリティ
#include <math.h>
static double clamp_unit(double x) {
if (x > 1.0) return 1.0;
if (x < -1.0) return -1.0;
return x;
}
浮動小数点の誤差に注意
浮動小数点(double/float)は有限の精度しか持たず、演算の積み重ねで誤差が生じます。
例えば理論上はsin(π/2)=1
ですが、計算の過程次第で1.0000000000000002
のような値になることがあります。
その結果asin(1.0000000000000002)
がNaNになる恐れがあります。
境界付近ではクリップや丸めを行うのが堅実です。
float用の関数
float
で計算する場合はasinf/acosf/atanf
を使います。
リテラルはf
サフィックスを付けるのがポイントです。
以下はfloatでの最小例です。
#include <stdio.h>
#include <math.h>
int main(void) {
float x = 0.5f; // floatリテラル
float a = asinf(x); // float版のasin
float b = acosf(x); // float版のacos
float c = atanf(1.0f); // float版のatan
// 表示桁数はfloatの精度に合わせて控えめに
printf("asinf(0.5f) = %.7f rad\n", a);
printf("acosf(0.5f) = %.7f rad\n", b);
printf("atanf(1.0f) = %.7f rad\n", c);
return 0;
}
asinf(0.5f) = 0.5235988 rad
acosf(0.5f) = 1.0471976 rad
atanf(1.0f) = 0.7853982 rad
逆三角関数のサンプル例
ここからは、コンパイルしてそのまま実行できるサンプルをまとめて示します。
いずれもラジアンを度に変換して表示し、境界値や誤差処理の勘所がわかるようにしています。
正弦から角度を求める
// asin_demo.c: sin値から角度(主値)を求める例
#include <stdio.h>
#include <math.h>
#define PI 3.14159265358979323846
// [-1, 1]に収めてからasinに渡す
static double clamp_unit(double x) {
if (x > 1.0) return 1.0;
if (x < -1.0) return -1.0;
return x;
}
// ラジアンを度に変換
static double rad_to_deg(double rad) {
return rad * 180.0 / PI;
}
int main(void) {
// 代表的な値を試す
double xs[] = { -1.0, -0.5, 0.0, 0.5, 1.0, 1.0000000001 }; // 最後は誤差で少しはみ出す例
size_t n = sizeof(xs) / sizeof(xs[0]);
for (size_t i = 0; i < n; ++i) {
double x = xs[i];
double x_c = clamp_unit(x); // はみ出し対策
double rad = asin(x_c); // 逆正弦
double deg = rad_to_deg(rad); // 度に変換
// isnanでNaNを検出
if (isnan(rad)) {
printf("asin(% .10f) = NaN (入力が範囲外の可能性)\n", x);
} else {
printf("asin(% .10f) = %.10f rad = %.6f deg\n", x, rad, deg);
}
}
return 0;
}
asin(-1.0000000000) = -1.5707963268 rad = -90.000000 deg
asin(-0.5000000000) = -0.5235987756 rad = -30.000000 deg
asin( 0.0000000000) = 0.0000000000 rad = 0.000000 deg
asin( 0.5000000000) = 0.5235987756 rad = 30.000000 deg
asin( 1.0000000000) = 1.5707963268 rad = 90.000000 deg
asin( 1.0000000001) = 1.5707963268 rad = 90.000000 deg
上の最後の例は、クリップ処理によりNaNを回避し、90度として扱えています。
コサインから角度を求める
// acos_demo.c: cos値から角度(主値)を求める例
#include <stdio.h>
#include <math.h>
#define PI 3.14159265358979323846
static double clamp_unit(double x) {
if (x > 1.0) return 1.0;
if (x < -1.0) return -1.0;
return x;
}
static double rad_to_deg(double rad) {
return rad * 180.0 / PI;
}
int main(void) {
double xs[] = { 1.0, 0.5, 0.0, -0.5, -1.0, -1.0000000001 };
size_t n = sizeof(xs) / sizeof(xs[0]);
for (size_t i = 0; i < n; ++i) {
double x = xs[i];
double x_c = clamp_unit(x);
double rad = acos(x_c);
double deg = rad_to_deg(rad);
if (isnan(rad)) {
printf("acos(% .10f) = NaN (入力が範囲外の可能性)\n", x);
} else {
printf("acos(% .10f) = %.10f rad = %.6f deg\n", x, rad, deg);
}
}
return 0;
}
acos( 1.0000000000) = 0.0000000000 rad = 0.000000 deg
acos( 0.5000000000) = 1.0471975512 rad = 60.000000 deg
acos( 0.0000000000) = 1.5707963268 rad = 90.000000 deg
acos(-0.5000000000) = 2.0943951024 rad = 120.000000 deg
acos(-1.0000000000) = 3.1415926536 rad = 180.000000 deg
acos(-1.0000000001) = 3.1415926536 rad = 180.000000 deg
acosは常に0〜180度の範囲に収まっていることが確認できます。
傾きy/xから角度を求める
// atan_demo.c: 傾き(y/x)から角度(主値)を求める例
#include <stdio.h>
#include <math.h>
#define PI 3.14159265358979323846
static double rad_to_deg(double rad) {
return rad * 180.0 / PI;
}
int main(void) {
// (y, x)のペアと、その傾きy/x
struct Pair { double y, x; } data[] = {
{ 1.0, 1.0 }, // 第1象限: 45度相当
{ -1.0, 1.0 }, // 第4象限: -45度相当
{ 1.0, -1.0 }, // 本来は135度(第2象限)だがatan(y/x)では-45度に
{ 2.0, 0.5 }, // 傾き4: 急な正の角度
{ 1.0, 0.0 } // x=0: y/xは±∞になり得る(実装依存でinf)
};
size_t n = sizeof(data) / sizeof(data[0]);
for (size_t i = 0; i < n; ++i) {
double y = data[i].y;
double x = data[i].x;
double slope = y / x; // x=0だとinfや-Infになる
double rad = atan(slope);
double deg = rad_to_deg(rad);
printf("y=% .1f, x=% .1f -> slope=% 7.3f -> atan= % .6f rad = % .2f deg\n",
y, x, slope, rad, deg);
}
// 注意: 象限を正しく扱うにはatan2(y, x)を使う(別記事で解説)
return 0;
}
y= 1.0, x= 1.0 -> slope= 1.000 -> atan= 0.785398 rad = 45.00 deg
y= -1.0, x= 1.0 -> slope= -1.000 -> atan= -0.785398 rad = -45.00 deg
y= 1.0, x= -1.0 -> slope= -1.000 -> atan= -0.785398 rad = -45.00 deg
y= 2.0, x= 0.5 -> slope= 4.000 -> atan= 1.325818 rad = 75.96 deg
y= 1.0, x= 0.0 -> slope= inf -> atan= 1.570796 rad = 90.00 deg
3行目は本来135度ですが、atan(y/x)では-45度になります。
象限を含めて正しい角度が必要ならatan2を使うべきです。
角度を度数法で表示する
// deg_demo.c: ラジアン↔度の相互変換と、逆三角関数の結果を度で表示
#include <stdio.h>
#include <math.h>
#define PI 3.14159265358979323846
static double rad_to_deg(double rad) { return rad * 180.0 / PI; }
static double deg_to_rad(double deg) { return deg * PI / 180.0; }
int main(void) {
// 代表的な値を度で表示
double a = asin(0.5); // ≈ π/6
double b = acos(0.5); // ≈ π/3
double c = atan(1.0); // ≈ π/4
printf("asin(0.5) = %.10f rad = %.6f deg\n", a, rad_to_deg(a));
printf("acos(0.5) = %.10f rad = %.6f deg\n", b, rad_to_deg(b));
printf("atan(1.0) = %.10f rad = %.6f deg\n", c, rad_to_deg(c));
// 任意の度をラジアンに変換する例
double deg = 30.0;
double rad = deg_to_rad(deg);
printf("%.1f deg = %.10f rad\n", deg, rad);
return 0;
}
asin(0.5) = 0.5235987756 rad = 30.000000 deg
acos(0.5) = 1.0471975512 rad = 60.000000 deg
atan(1.0) = 0.7853981634 rad = 45.000000 deg
30.0 deg = 0.5235987756 rad
度数法での表示は、デバッグやUI表示に便利です。
一方、内部計算は一貫してラジアンで行うと誤差の扱いがシンプルになります。
まとめ
本記事では、C言語の逆三角関数asin
/acos
/atan
の基本と使い方、注意点、サンプルを解説しました。
引数の範囲(asin/acosは[-1,1])、戻り値の範囲(asinは[-π/2,π/2]、acosは[0,π]、atanは(-π/2,π/2))、そして角度がラジアンで返る点を押さえるのが第一歩です。
UNIX系環境では-lmの付け忘れに注意し、floatを使うならasinf/acosf/atanfを選ぶとよいでしょう。
境界値付近ではクリップ処理や度変換のユーティリティを用意しておくと、NaNや誤差に強い安定したコードになります。