閉じる

【C言語】 atan2関数とは?使い方・atanとの違いを解説

C言語でベクトルの向きや2次元座標から角度を求めたいとき、atan2関数は非常に強力な道具になります。

単純なatanでは誤判定しやすいケースも、atan2を使えば安全かつ直感的に処理できます。

本記事では、atan2とは何かから、atanとの違い使うときの注意点や実用的なサンプルまで、図解とコード例を交えて詳しく解説します。

atan2関数とは

atan2関数の概要と特徴

atan2関数は、C言語の数学ライブラリmath.hに含まれる関数で、2次元座標(x, y)から、その点へのベクトルがx軸となす角度を求めるために用いられます。

単純な逆三角関数atanと異なり、引数が2つ(yとx)であることが大きな特徴です。

一般的な特徴をまとめると次のようになります。

  • 引数は(y, x)の2つで、y座標とx座標を指定します。
  • 戻り値はラジアン(radian)で表される角度です。
  • 戻り値の範囲は[-π, π]で、第1象限から第4象限まですべての象限を区別できます。
  • xやyが負の値ゼロを含んでいても、適切に角度を返します。

このように、atan2関数は2次元空間における「向き」を正確に求めるための関数です。

atan2が解く問題

三角関数のtanθは、y/xの比で定義されています。

そのため、atan(y/x)を使うと、y/xが同じ比であれば同じ角度を返してしまいます。

例えば、点(1, 1)(-1, -1)は、どちらもy/x = 1なので、通常のatanでは同じ角度になってしまいます。

しかし、実際には次のように象限が異なります。

  • (1, 1) は第1象限
  • (-1, -1) は第3象限

この2つは明らかに向きが180度異なるベクトルですが、atanはこれを区別できません。

ここで役立つのがatan2(y, x)です。

atan2はyとxの符号(正負)を使ってどの象限にあるかを判定し、適切な角度を返してくれます。

つまり、atan2は「比」だけでなく「どちらが正か負か」も考慮して角度を決める関数だと考えると理解しやすいです。

atan2関数の基本的な使い方

atan2の関数プロトタイプと引数の意味

C言語標準ライブラリで定義されているatan2のプロトタイプは次のとおりです。

C言語
#include <math.h>

/* 倍精度版 */
double atan2(double y, double x);

/* 単精度版(C99以降) */
float atan2f(float y, float x);

/* 拡張精度版(C99以降) */
long double atan2l(long double y, long double x);

ここで重要なのは、引数の順番が(y, x)である点です。

座標としては(x, y)という順番が一般的ですが、atan2はatan2(y, x)という順序になっているため、間違えやすいところです。

それぞれの引数の意味は次のとおりです。

  • 第1引数y
    原点から見た縦方向の成分(高さ)を表します。数学的にはsinと関係が深い成分です。
  • 第2引数x
    原点から見た横方向の成分を表します。こちらはcosと関係する成分です。

座標(x, y)から角度を求めたいときは、atan2(y, x)と呼び出します。

「座標の順番」と「関数の引数の順番」が逆になっているため、コードを書く際には注意が必要です。

atan2の戻り値の範囲とラジアンの扱い

atan2が返す値はdouble(またはfloatなど)型のラジアン角です。

戻り値の範囲は次のとおりです。

  • 範囲: -π 以上 π 以下
    つまり[-M_PI, M_PI]に相当します。

ラジアンは、円周を2πで割った単位で、次のような関係があります。

  • 0ラジアン … x軸正方向
  • π/2ラジアン … y軸正方向
  • -π/2ラジアン … y軸負方向
  • πラジアン(または-πラジアン) … x軸負方向

度数法(degree)との変換は次の関係で行います。

  • ラジアン → 度
    degree = radian * 180.0 / M_PI;
  • 度 → ラジアン
    radian = degree * M_PI / 180.0;

atan2の戻り値は必ずラジアンですので、画面表示やGUI、ゲーム開発などで度数が必要な場合は、自分で変換する必要があります。

atan2の簡単なコード例と出力結果

ここでは、いくつかの代表的な座標についてatan2を実行し、その結果を確認してみます。

C言語
#include <stdio.h>
#include <math.h>    // atan2, M_PI を使うために必要

int main(void) {
    /* 代表的な座標の例 */
    double x_values[] = { 1.0,  0.0, -1.0,  0.0,  1.0, -1.0 };
    double y_values[] = { 0.0,  1.0,  0.0, -1.0,  1.0, -1.0 };
    const char *labels[] = {
        "(1, 0)", "(0, 1)", "(-1, 0)", "(0, -1)", "(1, 1)", "(-1, -1)"
    };

    int n = (int)(sizeof(x_values) / sizeof(x_values[0]));

    for (int i = 0; i < n; i++) {
        double x = x_values[i];
        double y = y_values[i];

        /* atan2は(y, x)の順番に注意 */
        double rad = atan2(y, x);

        /* ラジアンを度数に変換して分かりやすく表示 */
        double deg = rad * 180.0 / M_PI;

        printf("point %s: atan2(y, x) = %f [rad], %f [deg]\n",
               labels[i], rad, deg);
    }

    return 0;
}

実行結果の一例は次のようになります(環境により小数点以下は多少異なります)。

実行結果
point (1, 0): atan2(y, x) = 0.000000 [rad], 0.000000 [deg]
point (0, 1): atan2(y, x) = 1.570796 [rad], 90.000000 [deg]
point (-1, 0): atan2(y, x) = 3.141593 [rad], 180.000000 [deg]
point (0, -1): atan2(y, x) = -1.570796 [rad], -90.000000 [deg]
point (1, 1): atan2(y, x) = 0.785398 [rad], 45.000000 [deg]
point (-1, -1): atan2(y, x) = -2.356194 [rad], -135.000000 [deg]

この結果から、例えば(-1, -1)に対しては-135度が返されており、第3象限の向きを正しく表現していることが分かります。

atan2関数とatan関数の違い

atanとatan2の処理の違い

C言語にはatanという関数も存在し、こちらは「逆正接(逆tan)」を計算します。

プロトタイプは次のようになっています。

C言語
#include <math.h>

/* 倍精度版 */
double atan(double x);

/* 単精度版(C99以降) */
float atanf(float x);

/* 拡張精度版(C99以降) */
long double atanl(long double x);

atanxtanθとみなし、そのときの角度θを-π/2からπ/2の範囲で返します。

つまり、

  • atan(tanθ) = θ (ただしθは-π/2 < θ < π/2内)

という関係を満たすように動作します。

一方でatan2(y, x)yとxの比を使いながらも、yとxの符号から象限を判定して-π〜πの範囲で角度を返します。

ですので、次のように考えると分かりやすいです。

  • atan: 比y/xだけ見て、-π/2〜π/2の範囲で角度を返す。
  • atan2: 比に加えて符号(象限)も見て-π〜πの範囲で角度を返す。

atanでは判定できないケースとatan2の優位性

atanを使うと、次のような問題が発生します。

  1. xの符号が分からない
    atanに与える引数はy/xの比のみです。そのため、元のyxが両方正だったのか、両方負だったのか、片方だけ負だったのかが分かりません。
  2. 第2象限・第3象限を区別しづらい
    例えば、y/x = 1に対しては、atanは約45度(π/4)しか返しません。しかし、実際には(1, 1)(-1, -1)では向きが180度異なります。
  3. x=0のときに計算できない
    atan(y/x)という形で書くと、x=0のときにゼロ除算エラーになってしまいます。

これに対してatan2は次のような利点があります。

  • yとxの符号を使って、第1〜第4象限を自動的に判断する。
  • x=0のときも安全に使える(内部で特別扱いされる)。
  • 2次元座標から角度を求める用途に最適化されている。

そのため、2次元座標から角度を求める場合は、原則としてatanではなくatan2を使うべきだと言えます。

具体例で比較するatanとatan2の使い分け

次のサンプルコードでは、同じ座標についてatan(y/x)atan2(y, x)を比較し、違いを確認してみます。

C言語
#include <stdio.h>
#include <math.h>

int main(void) {
    /* 例として4つの座標を用意 */
    double x_values[] = {  1.0, -1.0, -1.0,  0.0 };
    double y_values[] = {  1.0,  1.0, -1.0,  1.0 };
    const char *labels[] = {
        "( 1,  1)", "(-1,  1)", "(-1, -1)", "( 0,  1)"
    };

    int n = (int)(sizeof(x_values) / sizeof(x_values[0]));

    for (int i = 0; i < n; i++) {
        double x = x_values[i];
        double y = y_values[i];

        printf("point %s\n", labels[i]);

        if (x != 0.0) {
            /* x!=0のときだけ、比較のためにatan(y/x)を計算 */
            double ratio = y / x;
            double rad_atan  = atan(ratio);
            double deg_atan  = rad_atan * 180.0 / M_PI;
            printf("  atan(y/x)   = %8.3f [deg] (ratio=%5.2f)\n",
                   deg_atan, ratio);
        } else {
            printf("  atan(y/x)   = 計算不能 (x=0 のため 0除算)\n");
        }

        /* atan2はx=0でも安全に使える */
        double rad_atan2 = atan2(y, x);
        double deg_atan2 = rad_atan2 * 180.0 / M_PI;
        printf("  atan2(y, x) = %8.3f [deg]\n\n", deg_atan2);
    }

    return 0;
}
実行結果
point ( 1,  1)
  atan(y/x)   =   45.000 [deg] (ratio= 1.00)
  atan2(y, x) =   45.000 [deg]

point (-1,  1)
  atan(y/x)   =  -45.000 [deg] (ratio=-1.00)
  atan2(y, x) =  135.000 [deg]

point (-1, -1)
  atan(y/x)   =   45.000 [deg] (ratio= 1.00)
  atan2(y, x) = -135.000 [deg]

point ( 0,  1)
  atan(y/x)   = 計算不能 (x=0 のため 0除算)
  atan2(y, x) =   90.000 [deg]

特に(-1, 1)(-1, -1)に注目すると、atanでは

  • y/xの符号によって±45度にしかならず、象限を正しく表現できていないこと

が分かります。

一方、atan2では第2象限として135度、第3象限として-135度が返され、直感的な向きになっています。

atan2関数を使うときの注意点と活用例

ゼロや負の値を含む引数の注意点

atan2は、xやyがゼロや負の値であっても、可能な限り妥当な値を返すように設計されています。

ただし、いくつか注意しておきたいポイントがあります。

  1. x=0 かつ y≠0 の場合yの符号によって、真上か真下の方向として扱われます。
    1. (x=0, y>0) → 角度は+π/2(90度)
    2. (x=0, y<0) → 角度は-π/2(-90度)
  2. x<0 の場合
    xが負の場合、結果は必ずπ/2〜πまたは-π〜-π/2の範囲になります。これは第2・第3象限を表します。
  3. x=0 かつ y=0 の場合
    これは方向が定義できない特別なケースです。標準規格上、atan2(0, 0)の結果は未定義となっています。多くの実装では0を返しますが、移植性を考えるとこの呼び出しは避けるべきです。

これらを踏まえると、実際のコードでは次のような防御的な書き方をしておくと安全です。

C言語
#include <stdio.h>
#include <math.h>

int main(void) {
    double x = 0.0;
    double y = 0.0;

    /* 原点(0, 0)かどうかを事前にチェックする例 */
    if (x == 0.0 && y == 0.0) {
        printf("原点なので向き(角度)は定義できません。\n");
    } else {
        double rad = atan2(y, x);
        double deg = rad * 180.0 / M_PI;
        printf("atan2(y, x) = %f [rad], %f [deg]\n", rad, deg);
    }

    return 0;
}

角度(度数法)への変換と表示方法

先ほども触れたように、atan2の戻り値はラジアンです。

しかし、人間には度数(°)のほうが直感的に理解しやすいことが多いです。

度数に変換する標準的な方法は次の式です。

  • ラジアンから度数への変換
    deg = rad * 180.0 / M_PI;

ここでM_PIπの定数です。

環境によっては定義されていない場合もあるため、もしコンパイルエラーになる場合は次のように自前で定義してもかまいません。

C言語
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

度数で表示する簡単なサンプルコードは次のようになります。

C言語
#include <stdio.h>
#include <math.h>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

int main(void) {
    double x = -1.0;
    double y =  1.0;

    double rad = atan2(y, x);
    double deg = rad * 180.0 / M_PI;

    printf("座標(%.1f, %.1f) の角度:\n", x, y);
    printf("  ラジアン: %f\n", rad);
    printf("  度数    : %f\n", deg);

    return 0;
}

このように、内部の計算はラジアンで行い、表示のときだけ度数に変換するというスタイルが、C言語や多くの数学ライブラリでは一般的です。

2次元座標から角度を求める実用的なサンプルコード

ここでは、2次元ゲームやシミュレーションなどを想定した、より実用的なサンプルコードを示します。

プレイヤー(自分)の位置とターゲットの位置が与えられたとき、プレイヤーから見たターゲットの方向角を求める例です。

この状況では、プレイヤーを(px, py)、ターゲットを(tx, ty)とすると、ターゲット方向へのベクトルは

  • dx = tx - px;
  • dy = ty - py;

となります。

この(dx, dy)に対してatan2(dy, dx)を適用すれば、プレイヤーからターゲットへの角度が求まります。

C言語
#include <stdio.h>
#include <math.h>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

/* 2次元座標を表す構造体 */
typedef struct {
    double x;
    double y;
} Vec2;

/* プレイヤーからターゲットへの角度を求める関数
   戻り値はラジアン */
double get_angle_rad(Vec2 player, Vec2 target) {
    double dx = target.x - player.x;  // x方向の差分
    double dy = target.y - player.y;  // y方向の差分

    /* 原点チェック: 同じ位置なら向きは定義できない */
    if (dx == 0.0 && dy == 0.0) {
        return 0.0;  // ここでは便宜的に0を返す(用途に応じて扱いを決める)
    }

    /* ベクトル(dx, dy)の向きの角度を求める */
    return atan2(dy, dx);
}

/* ラジアンを度数に変換するユーティリティ関数 */
double rad_to_deg(double rad) {
    return rad * 180.0 / M_PI;
}

int main(void) {
    Vec2 player = { 0.0, 0.0 };  // プレイヤーの位置
    Vec2 targets[] = {
        {  1.0,  0.0 },  // 右
        {  0.0,  1.0 },  // 上
        { -1.0,  0.0 },  // 左
        {  0.0, -1.0 },  // 下
        {  1.0,  1.0 },  // 右上(第1象限)
        { -1.0,  1.0 },  // 左上(第2象限)
        { -1.0, -1.0 },  // 左下(第3象限)
        {  1.0, -1.0 },  // 右下(第4象限)
    };

    int n = (int)(sizeof(targets) / sizeof(targets[0]));

    for (int i = 0; i < n; i++) {
        Vec2 target = targets[i];
        double rad = get_angle_rad(player, target);
        double deg = rad_to_deg(rad);

        printf("Player(%.1f, %.1f) -> Target(%.1f, %.1f):\n",
               player.x, player.y, target.x, target.y);
        printf("  角度: %7.3f [rad], %7.3f [deg]\n\n", rad, deg);
    }

    return 0;
}
実行結果
Player(0.0, 0.0) -> Target(1.0, 0.0):
  角度:   0.000 [rad],   0.000 [deg]

Player(0.0, 0.0) -> Target(0.0, 1.0):
  角度:   1.571 [rad],  90.000 [deg]

Player(0.0, 0.0) -> Target(-1.0, 0.0):
  角度:   3.142 [rad], 180.000 [deg]

Player(0.0, 0.0) -> Target(0.0, -1.0):
  角度:  -1.571 [rad], -90.000 [deg]

Player(0.0, 0.0) -> Target(1.0, 1.0):
  角度:   0.785 [rad],  45.000 [deg]

Player(0.0, 0.0) -> Target(-1.0, 1.0):
  角度:   2.356 [rad], 135.000 [deg]

Player(0.0, 0.0) -> Target(-1.0, -1.0):
  角度:  -2.356 [rad], -135.000 [deg]

Player(0.0, 0.0) -> Target(1.0, -1.0):
  角度:  -0.785 [rad], -45.000 [deg]

このように、atan2を使うことで2次元ベクトルの向きを簡潔かつ正確に求めることができます。

ゲームではこの角度を使ってスプライトの向きを変えたり、物理シミュレーションでは力の方向を決定したりと、さまざまな場面で応用できます。

まとめ

atan2関数は、2次元座標から角度を求めるための標準的な手段であり、単純なatanでは判定できない象限の違いを正しく扱える点が最大の利点です。

引数の順番が(y, x)であること、戻り値が-π〜πのラジアンで返ってくること、そして(0, 0)の場合は未定義であることを押さえておけば、実用上のトラブルは大きく減らせます。

2次元ベクトルの向きや座標から角度を求める場面では、まずatan2を使うという習慣を身につけると、より安全で分かりやすいコードを書くことができます。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!