0xAA55 发表于 2023-5-2 15:23:58

【C】自己造 atan2() 的轮子

## 自己造 `atan2()` 的轮子

有时候有的情况下你并没有现成的 `atan2()` 轮子,或者你的嵌入式环境不支持,或者你写 Shader 的环境不支持,或者你用的编程语言(比如 VB)不支持的时候,你可以通过根据判断 (x, y) 的坐标值所在的象限,使用 `atan()` 来自己造一个 `atan2()` 的轮子。

`atan2()`主要被用来求解一个二维向量与 x 坐标轴正方向的夹角的角度值,返回值的取值范围是 _[-π, π]_ 区间。而取角度值的思路是使用反正切函数 `atan()` 计算 y ÷ x 的值的反正切值,但是光用这个值还不够,因为无法判断 x 和 y 的正负。于是,在使用 `atan()` 的基础上,对 x 和 y 值的正负做一些 if 判定,就可以修出正确的值了。

### 例子:`my_atan2()` 的 C 代码实现

并没有什么特别的优化,只是实现了功能。
gcc 编译的时候需要 `-lm` 链接器参数。

```
#include<math.h>

#define PI 3.141592653589793238462643

double my_atan2(double y, double x)
{
        if (x == 0)
        {
                if (y > 0) return PI * 0.5;
                else if (y < 0) return -PI * 0.5;
                else
                {
                        // 如果你的编程语言支持抛出数学异常,此处应当抛出数学异常
                        // 比如 return y / x; 抛一个除零异常
                        // C 语言不支持,所以算了,返回 0。
                        return 0;
                }
        }
        else
        {
                double tanv = y / x;
                if (x > 0) return atan(tanv);
                else if (x < 0)
                {
                        if (y >= 0) return atan(tanv) + PI;
                        else return atan(tanv) - PI;
                }
        }
}
```


### 写个 POC 检验轮子

```
#include<math.h>

#define PI 3.141592653589793238462643

double my_atan2(double y, double x)
{
        if (x == 0)
        {
                if (y > 0) return PI * 0.5;
                else if (y < 0) return -PI * 0.5;
                else
                {
                        // 如果你的编程语言支持抛出数学异常,此处应当抛出数学异常
                        // 比如 return y / x; 抛一个除零异常
                        // C 语言不支持,所以算了,返回 0。
                        return 0;
                }
        }
        else
        {
                double tanv = y / x;
                if (x > 0) return atan(tanv);
                else if (x < 0)
                {
                        if (y >= 0) return atan(tanv) + PI;
                        else return atan(tanv) - PI;
                }
        }
}

#include<stdio.h>
#include<stdlib.h>

int main(int argc, char **argv)
{
        double degree;
        int should_be_corrected = 0;

        // 遍历 720 个角度
        for (degree = -360; degree < 360; degree ++)
        {
                double angle = degree * PI / 180; // 弧度
                double length = (double)rand() / RAND_MAX; // 随机的向量长度
                double x = cos(angle) * length;
                double y = sin(angle) * length;
                double crt_atan2_rad = atan2(y, x); // CRT 库的 atan2 求解的弧度值
                double my_atan2_rad = my_atan2(y, x); // 自己的轮子求解的弧度值
                double crt_atan2_deg = crt_atan2_rad * 180 / PI; // 转换为角度
                double my_atan2_deg = my_atan2_rad * 180 / PI;
                if (fabs(crt_atan2_deg - my_atan2_deg) >= 0.1) // 判断误差
                {
                        printf("误差大于 1:原始值:%lf, CRT 值:%lf, 轮子值:%lf\n", degree, floor(crt_atan2_deg), floor(my_atan2_deg));
                        should_be_corrected = 1;
                }
        }

        if (should_be_corrected)
        {
                printf("检测到误差,应修正代码实现。\n");
        }
        else
        {
                printf("检验完毕。\n");
        }

        return 0;
}
```
运行后,显示“检验完毕。”

YY菌 发表于 2023-5-9 09:03:31

我觉得x和y都为0的时候不应该返回0,而是返回nan来表示错误。

usr 发表于 2023-5-9 14:45:28

YY菌 发表于 2023-5-9 09:03
我觉得x和y都为0的时候不应该返回0,而是返回nan来表示错误。

返回0/0触发异常试试?

YY菌 发表于 2023-5-10 09:32:52

usr 发表于 2023-5-9 14:45
返回0/0触发异常试试?

你那是整数,浮点数 0.0 / 0.0 是不会有异常的,计算结果是 nan 值。

美俪女神 发表于 2023-5-17 08:54:06

我的这些知识都还给老师了。。。

0xAA55 发表于 2023-5-22 09:22:04

YY菌 发表于 2023-5-10 09:32
你那是整数,浮点数 0.0 / 0.0 是不会有异常的,计算结果是 nan 值。

啊?是吗?我大意了
页: [1]
查看完整版本: 【C】自己造 atan2() 的轮子