C++のstd::atan2について

AI実装検定のご案内

std::atan2は、2次元座標上の点やベクトルが、原点から見てどの方向を向いているかを角度として求める関数です。

基本的な書き方は次のとおりです。

#include <cmath>

double angle = std::atan2(y, x);

std::atan2では、引数を次の順番で指定します。

std::atan2(y座標, x座標);

座標は通常(x, y)の順で表しますが、std::atan2ではyが先、xが後です。

戻り値の単位は度ではなく、ラジアンです。

目次

std::atan2が求める角度

std::atan2(y, x)は、正のX軸を基準として、原点から点(x, y)へ向かう方向の角度を返します。

一般的な数学座標では、反時計回りが正、時計回りが負です。

          y
          ↑
          |
          |       ● (x, y)
          |      /
          |     / θ
----------+----------------→ x
        原点

代表的な座標と角度の関係は、次のとおりです。

(x, y)呼び出し角度
(1, 0)std::atan2(0, 1)0
(1, 1)std::atan2(1, 1)π / 4
(0, 1)std::atan2(1, 0)π / 2
(-1, 1)std::atan2(1, -1)3π / 4
(-1, 0)std::atan2(0, -1)πまたは
(-1, -1)std::atan2(-1, -1)-3π / 4
(0, -1)std::atan2(-1, 0)-π / 2
(1, -1)std::atan2(-1, 1)-π / 4

std::atan2の基本的な使い方

次の例では、点(1, 1)の方向を求めています。

#include <cmath>
#include <iostream>

int main()
{
    const double x = 1.0;
    const double y = 1.0;

    const double angle = std::atan2(y, x);

    std::cout << angle << '\n';
}

実行結果は、おおよそ次の値です。

0.785398

これはラジアンであり、度数法では45度に相当します。

0.785398 ≒ π / 4

std::atan2の戻り値の範囲

通常、std::atan2(y, x)の戻り値は、次の範囲になります。

-π以上、π以下

数式では、次のように表されます。

[-π, π]

方向との関係は、おおよそ次のとおりです。

                  π / 2
                    ↑
                    |
          π または -π ← + → 0
                    |
                    ↓
                 -π / 2

負のX軸上では、yが正のゼロか負のゼロかによって、πまたはになる場合があります。

std::atan2(+0.0, -1.0); // +π
std::atan2(-0.0, -1.0); // -π

std::atanではなくstd::atan2を使う理由

角度を求めるだけなら、次のように書けそうに見えます。

double angle = std::atan(y / x);

しかし、座標やベクトルの方向を求める場合は、一般にstd::atan2を使用するほうが適切です。

std::atan(y / x)では象限を区別できない

次の2点を考えます。

A = (1, 1)
B = (-1, -1)

それぞれについてy / xを計算すると、どちらも1になります。

1.0 / 1.0;      // 1.0
-1.0 / -1.0;    // 1.0

そのため、std::atan(y / x)では同じ角度になります。

std::atan(1.0); // π / 4

しかし、実際には2つの点は反対方向にあります。

std::atan2(1.0, 1.0);    //  π / 4
std::atan2(-1.0, -1.0);  // -3π / 4

std::atan2は、xyを別々に受け取り、それぞれの符号から象限を判定します。

x == 0でも除算せずに計算できる

std::atan(y / x)では、xが0の場合にゼロ除算が発生する可能性があります。

double x = 0.0;
double y = 1.0;

double angle = std::atan(y / x);

浮動小数点数の場合、環境によっては無限大になることがありますが、浮動小数点例外や特殊値に依存する書き方になります。

また、整数同士の除算で分母が0の場合は、未定義動作です。

int x = 0;
int y = 1;

auto angle = std::atan(y / x); // 整数のゼロ除算

std::atan2であれば、除算せずに計算できます。

double angle = std::atan2(1.0, 0.0);

結果は次のとおりです。

π / 2

つまり90度です。

ラジアンを度に変換する方法

std::atan2の戻り値はラジアンです。

ラジアンを度に変換する式は、次のとおりです。

度 = ラジアン × 180 ÷ π

C++20以降でstd::numbers::piを使う

C++20以降では、std::numbers::piを使用できます。

#include <cmath>
#include <iostream>
#include <numbers>

int main()
{
    const double x = 1.0;
    const double y = 1.0;

    const double radians = std::atan2(y, x);
    const double degrees =
        radians * 180.0 / std::numbers::pi;

    std::cout << degrees << " degrees\n";
}

実行結果は、おおよそ次のとおりです。

45 degrees

C++17以前で円周率を使う

C++17以前では、std::numbers::piを利用できません。

その場合は、円周率を定数として定義します。

#include <cmath>
#include <iostream>

constexpr double pi = 3.14159265358979323846;

int main()
{
    const double radians = std::atan2(1.0, 1.0);
    const double degrees = radians * 180.0 / pi;

    std::cout << degrees << '\n';
}

環境によってはM_PIを使用できる場合もありますが、M_PIは標準C++で常に利用できる定数ではありません。

移植性を重視する場合は、C++20以降ならstd::numbers::piを使用し、それ以前では自分で定数を定義する方法が分かりやすいでしょう。

std::atan2が対応する型

std::atan2は、<cmath>をインクルードして使用します。

#include <cmath>

主に次の浮動小数点型で利用できます。

float
double
long double

使用例は次のとおりです。

float fy = 1.0f;
float fx = 1.0f;
auto angle1 = std::atan2(fy, fx);

double dy = 1.0;
double dx = 1.0;
auto angle2 = std::atan2(dy, dx);

long double ly = 1.0L;
long double lx = 1.0L;
auto angle3 = std::atan2(ly, lx);

整数を渡すこともできます。

auto angle = std::atan2(1, 1);

ただし、角度は整数ではなく、浮動小数点数として扱われます。

意図を明確にする場合は、最初から浮動小数点数を使用すると分かりやすくなります。

double angle = std::atan2(1.0, 1.0);

整数座標を明示的に変換することもできます。

int x = 10;
int y = 20;

double angle = std::atan2(
    static_cast<double>(y),
    static_cast<double>(x)
);

atan2fatan2lとの違い

C由来の関数として、次の名前もあります。

std::atan2f(y, x); // float向け
std::atan2(y, x);  // 型に応じて使用
std::atan2l(y, x); // long double向け

C++では、通常は引数の型に応じてstd::atan2を使用すれば十分です。

float y = 1.0f;
float x = 1.0f;

float angle = std::atan2(y, x);

2点間の方向を求める方法

現在位置を(x1, y1)、目的地を(x2, y2)とします。

2点間の方向を求めるには、まず座標の差を計算します。

double dx = x2 - x1;
double dy = y2 - y1;

その後、std::atan2dydxを渡します。

double angle = std::atan2(dy, dx);

完全な例は次のとおりです。

#include <cmath>
#include <iostream>
#include <numbers>

int main()
{
    const double x1 = 2.0;
    const double y1 = 3.0;

    const double x2 = 5.0;
    const double y2 = 7.0;

    const double dx = x2 - x1;
    const double dy = y2 - y1;

    const double radians = std::atan2(dy, dx);
    const double degrees =
        radians * 180.0 / std::numbers::pi;

    std::cout << "angle: "
              << degrees
              << " degrees\n";
}

重要なのは、次の順番です。

std::atan2(dy, dx);

次のように逆にすると、異なる角度になります。

std::atan2(dx, dy); // 引数が逆

ベクトルの角度を求める方法

2次元ベクトルの方向も、std::atan2で求められます。

struct Vector2
{
    double x;
    double y;
};

double getAngle(const Vector2& vector)
{
    return std::atan2(vector.y, vector.x);
}

使用例は次のとおりです。

Vector2 vector{0.0, 1.0};

double angle = getAngle(vector);

結果はπ / 2、つまり90度です。

ゲームでキャラクターを対象に向ける方法

ゲームでは、キャラクターから対象物への方向を求めるためにstd::atan2を使用できます。

#include <cmath>

struct Point
{
    double x;
    double y;
};

double calculateDirection(
    const Point& character,
    const Point& target)
{
    const double dx = target.x - character.x;
    const double dy = target.y - character.y;

    return std::atan2(dy, dx);
}

使用例は次のとおりです。

Point character{100.0, 50.0};
Point target{150.0, 120.0};

double direction =
    calculateDirection(character, target);

画面座標で使用する場合の注意

数学座標では、Y軸は上方向に増加します。

数学座標

y
↑
|
+------→ x

一方、多くの画面座標では、Y軸は下方向に増加します。

画面座標

+------→ x
|
↓
y

画面上の向きをそのまま使用する場合

右を0度、下方向を正の角度として扱う場合は、そのまま計算できます。

double angle = std::atan2(dy, dx);

この場合、画面上ではおおよそ次の向きになります。

右:0度
下:90度
左:180度
上:-90度

数学座標と同じ角度にしたい場合

右を0度、上を90度、反時計回りを正にしたい場合は、Y成分を反転します。

double angle = std::atan2(-dy, dx);

どちらを使用するかは、利用している描画システムや、角度の基準によって決まります。

角度を0度以上360度未満に変換する方法

std::atan2は負の角度を返す場合があります。

double radians = std::atan2(-1.0, 1.0);

この結果は約-45度です。

これを315度として扱いたい場合は、負の値に360度を加えます。

#include <cmath>
#include <numbers>

double toDegrees0To360(double y, double x)
{
    double degrees =
        std::atan2(y, x)
        * 180.0
        / std::numbers::pi;

    if (degrees < 0.0)
    {
        degrees += 360.0;
    }

    return degrees;
}

使用例は次のとおりです。

double angle = toDegrees0To360(-1.0, 1.0);

結果は、おおよそ次の値です。

315

負のゼロを正のゼロに統一する

浮動小数点数では、-0.0が返る場合があります。

double degrees = -0.0;

-0.0 < 0.0falseなので、必要に応じて正のゼロに統一します。

if (degrees == 0.0)
{
    degrees = 0.0;
}

ラジアンを0以上2π未満に変換する方法

std::atan2の戻り値に限定する場合は、負の値に一度を加えるだけで正規化できます。

#include <numbers>

double normalizeAtan2Radians(double angle)
{
    if (angle < 0.0)
    {
        angle += 2.0 * std::numbers::pi;
    }

    return angle;
}

ただし、この関数はstd::atan2の戻り値を対象としています。

任意の大きな角度や小さな角度を正規化する場合は、std::fmodを使用します。

#include <cmath>
#include <numbers>

double normalizeRadians(double angle)
{
    constexpr double twoPi =
        2.0 * std::numbers::pi;

    angle = std::fmod(angle, twoPi);

    if (angle < 0.0)
    {
        angle += twoPi;
    }

    if (angle == 0.0)
    {
        return 0.0;
    }

    return angle;
}

std::atan2(0, 0)の扱い

数学的には、点(0, 0)には方向がありません。

原点から原点へ向かうベクトルは長さが0であり、どの方向を向いているかを定義できないためです。

原点から原点への方向は定義できない

ライブラリ上の具体的な戻り値やエラー処理は、浮動小数点環境や実装の影響を受ける可能性があります。

そのため、方向が存在するかどうかが重要な処理では、呼び出し前に判定する方法が安全です。

#include <cmath>
#include <optional>

std::optional<double> calculateAngle(
    double y,
    double x)
{
    if (x == 0.0 && y == 0.0)
    {
        return std::nullopt;
    }

    return std::atan2(y, x);
}

使用側では、次のように確認できます。

auto angle = calculateAngle(y, x);

if (angle.has_value())
{
    // 有効な方向
}
else
{
    // 方向を定義できない
}

浮動小数点数のゼロ判定

浮動小数点数のゼロ判定には、完全一致と許容誤差を使う方法があります。

どちらを使うべきかは、アプリケーションの仕様によって異なります。

完全に同じ座標だけを判定する場合

始点と終点が厳密に同じ場合だけを方向なしとするなら、次の判定で問題ありません。

if (dx == 0.0 && dy == 0.0)
{
    // 完全に同じ位置
}

例えば、整数座標を浮動小数点数に変換して使用している場合や、同じ値をそのまま引き算している場合は、完全一致が適切なことがあります。

十分に近い座標も同じとみなす場合

測定値や物理シミュレーションなど、計算誤差を含む場合は、許容誤差を使用します。

#include <cmath>

bool hasDirection(
    double dx,
    double dy,
    double tolerance)
{
    return std::hypot(dx, dy) > tolerance;
}

std::hypot(dx, dy)は、ベクトルの長さを求める関数です。

長さ = √(dx² + dy²)

許容誤差は、座標の単位やスケールに合わせて決める必要があります。

固定値の1e-12が、すべてのプログラムに適しているわけではありません。

特殊な浮動小数点値に関する注意

浮動小数点数には、通常の値以外に次の特殊値があります。

+0.0
-0.0
+∞
-∞
NaN

正のゼロと負のゼロ

正のゼロと負のゼロは、比較上は等しい値です。

+0.0 == -0.0; // true

ただし、符号情報は保持されています。

#include <cmath>

bool negative = std::signbit(-0.0); // true

この符号は、負のX軸上でstd::atan2の結果を決める際に影響する場合があります。

std::atan2(+0.0, -1.0); // +π
std::atan2(-0.0, -1.0); // -π

NaNや無限大を確認する関数

特殊値を確認する場合は、次の関数を使用できます。

std::isnan(value);
std::isinf(value);
std::isfinite(value);
std::signbit(value);

外部データや計算結果を受け取る場合は、必要に応じて入力値を確認します。

#include <cmath>
#include <optional>

std::optional<double> safeAtan2(
    double y,
    double x)
{
    if (!std::isfinite(x)
        || !std::isfinite(y))
    {
        return std::nullopt;
    }

    if (x == 0.0 && y == 0.0)
    {
        return std::nullopt;
    }

    return std::atan2(y, x);
}

ただし、無限大を含む値を受け入れるかどうかは、アプリケーションの仕様によります。

最短の角度差を求める方法

角度には、πの境界があります。

例えば、次の2つの角度を考えます。

179度
-179度

数値を単純に引くと358度の差になりますが、実際の最短角度差は2度です。

ラジアンで最短角度差を求める場合は、std::remainderを利用できます。

#include <cmath>
#include <numbers>

double shortestAngleDifference(
    double from,
    double to)
{
    return std::remainder(
        to - from,
        2.0 * std::numbers::pi
    );
}

この方法では、角度差をおおむねからπの範囲に収められます。

180度ちょうどの場合の注意

差がちょうど180度の場合、正方向に回っても負方向に回っても距離は同じです。

+180度
-180度

そのため、180度をどちらの符号として扱うかは、アプリケーション側で決める必要があります。

例えば、180度を常にとして扱いたい場合は、追加の処理を行います。

#include <cmath>
#include <numbers>

double shortestAngleDifference(
    double from,
    double to)
{
    constexpr double pi =
        std::numbers::pi;

    constexpr double twoPi =
        2.0 * pi;

    double difference =
        std::remainder(to - from, twoPi);

    if (difference == pi)
    {
        difference = -pi;
    }

    return difference;
}

ただし、浮動小数点数の計算過程によっては、difference == piが成立しない場合もあります。

境界の扱いが重要な処理では、入力値の正規化方法も含めて設計する必要があります。

std::atanstd::atan2の違い

std::atanstd::atan2は、どちらも逆正接に関係する関数ですが、用途が異なります。

std::atan

std::atanは、1つの値の逆正接を求めます。

double angle = std::atan(value);

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

-π / 2以上、π / 2以下

傾きや比率から角度を求める場合に使用します。

std::atan2

std::atan2は、X成分とY成分から方向を求めます。

double angle = std::atan2(y, x);

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

-π以上、π以下

座標やベクトルの方向を求める場合に適しています。

目的使用する関数
1つの値の逆正接を求めるstd::atan
傾きや比率から角度を求めるstd::atan
座標やベクトルの方向を求めるstd::atan2
象限を正しく区別するstd::atan2
x == 0を除算なしで扱うstd::atan2

std::atan2でよくある間違い

引数を逆にする

最も多い間違いは、xyの順番を逆にすることです。

std::atan2(x, y); // 意図と異なる可能性がある

正しくは次の順番です。

std::atan2(y, x);

内部で考える比率がy / xであるため、yが先、xが後と覚えると分かりやすいでしょう。

戻り値を度だと思う

次の値は45ではありません。

double angle = std::atan2(1.0, 1.0);

結果は、おおよそ次の値です。

0.785398

これはラジアンです。

度に変換する場合は、次の計算を行います。

double degrees =
    angle * 180.0 / std::numbers::pi;

常に正の角度が返ると思う

std::atan2は、負の角度を返す場合があります。

std::atan2(-1.0, 1.0); // 約 -π / 4

0度以上360度未満で扱いたい場合は、負の角度に360度を加えます。

std::atan(y / x)で代用する

std::atan(y / x);

この方法では、象限情報が失われます。

また、x == 0の場合の処理も必要です。

方向を求める場合は、原則として次を使用します。

std::atan2(y, x);

原点に対して0度を返してしまう

始点と終点が同じ場合に0度を返すと、方向なしと右向きの0度を区別できません。

if (dx == 0.0 && dy == 0.0)
{
    return 0.0;
}

方向なしを明確に区別する場合は、std::optionalを使用します。

#include <optional>

std::optional<double> direction(...)
{
    if (dx == 0.0 && dy == 0.0)
    {
        return std::nullopt;
    }

    return std::atan2(dy, dx);
}

実用的な方向計算関数

2点間の方向を、0度以上360度未満で返す関数は次のように書けます。

#include <cmath>
#include <numbers>
#include <optional>

std::optional<double> directionDegrees(
    double fromX,
    double fromY,
    double toX,
    double toY)
{
    const double dx = toX - fromX;
    const double dy = toY - fromY;

    if (dx == 0.0 && dy == 0.0)
    {
        return std::nullopt;
    }

    double degrees =
        std::atan2(dy, dx)
        * 180.0
        / std::numbers::pi;

    if (degrees < 0.0)
    {
        degrees += 360.0;
    }

    if (degrees == 0.0)
    {
        degrees = 0.0;
    }

    return degrees;
}

使用例は次のとおりです。

#include <iostream>

int main()
{
    const auto angle =
        directionDegrees(0.0, 0.0, 1.0, 1.0);

    if (angle.has_value())
    {
        std::cout << *angle << " degrees\n";
    }
    else
    {
        std::cout << "direction is undefined\n";
    }
}

結果は、おおよそ次のとおりです。

45 degrees

まとめ

std::atan2は、2次元座標やベクトルの方向を求めるために使用する関数です。

基本形は次のとおりです。

double angle = std::atan2(y, x);

重要なポイントは、次のとおりです。

  • 引数はyxの順番で指定する
  • 戻り値の単位はラジアン
  • 正のX軸を基準とした角度を返す
  • 戻り値は通常以上π以下
  • xyの符号から象限を判定する
  • std::atan(y / x)より方向計算に適している
  • x == 0でも除算せずに計算できる
  • 画面座標ではY軸の向きに注意する
  • 原点(0, 0)には数学的な方向がない
  • 必要に応じて角度を0度以上360度未満に正規化する
  • 完全一致と許容誤差は、用途に応じて使い分ける

特に重要なのは、次の違いです。

std::atan(y / x);  // 象限情報を正しく扱えない
std::atan2(y, x);  // xとyの符号から方向を判定できる

座標、ベクトル、ゲームの向き、マウスカーソルの方向、ロボット制御など、2次元上の方向を求める場合は、std::atan2を使用すると考えると分かりやすいでしょう。

以上、C++のstd::atan2についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次