c++中atan精度不够的情况如何处理


问题的导出与思考

在一个demo的开发中,需要处理一个2D平面上的移动物体到某个具体的点的运动问题,其中有一个阶段需要判断物体当前坐标与目标坐标的夹角,以方向角(角度制0~360)表示。那么很自然的我们就会想到使用atan来计算目标与当前位置之间的夹角,只需要知道两点的xy坐标即可。则用C++表示如下

#include <cmath>
Point current_pos; //当前位置
Point target_pos; //目标位置
target_angle = atan((target_pos.y - current_pos.y) / (target_pos.x - current_pos.x));

当然,别忘了分母不能为0,加一下判断逻辑

//其中一种可能的判断逻辑
if (abs(target_pos.x - current_pos.x) < 0.01)
    target_angle = 90.0 * (target_pos.y > current_pos.y ? 1 : -1);

最后,将atan返回得到的弧度制转换为角度值

const double pi 3.141592653
target_angel = target_angle * 180.0 / pi;

随后,我对寻路逻辑的代码进行了审查,发现了许多问题
比如atan的值域之后±90°,转换为正也就是只有平面直角坐标系的I,IV象限能够识别,其他的象限不能够得到角度值,所以还需要根据坐标值判断方向角象限位置

if (target_pos.x - current_pos.x < 0)
    target_angle += 180.0;

然而,在实际的使用过程中,我发现移动物体的转角会在某些特定角度,比如0°,45°,90°,135°,180°,277°,314°
完整寻路逻辑代码如下

// 旧的寻路逻辑
double target_angle;
if (Move2DObject.target_pos.x - Move2DObject.pos.x == 0)
    target_angle = 90.0;
else
{
    target_angle = atan((Move2DObject.target_pos.y - Move2DObject.pos.y) / (Move2DObject.target_pos.x - Move2DObject.pos.x)) * 180.0 / Pi;
    //别忘了atan值域只有-90~+90
    if (Move2DObject.target_pos.x - Move2DObject.pos.x < 0)
        target_angle += 180.0;
}
target_angle = CARRY_IN(target_angle, 0.0, 360.0); //让超出0~360°范围的数通过加减回到这个区间

if (Move2DObject.direction_angle - target_angle < 180.0 && Move2DObject.direction_angle - target_angle > 0.0) // 转向算法
    Move2DObject.direction_angle -= 5.0;
else if (abs(Move2DObject.direction_angle - target_angle) < 5.0)
    Move2DObject.direction_angle = target_angle;
else
    Move2DObject.direction_angle += 5.0;

Move2DObject.direction_angle = CARRY_IN(Move2DObject.direction_angle, 0.0, 360.0); //让超出0~360°范围的数通过加减回到这个区间

最后通过debug,我发现当方向角象限在第IV象限时,尤其是当两点的x坐标相差较小时,会出现角度的失真状态
如何理解?也就是认为,在一定精度内,就算自变量变化了很多,但是函数几乎没有变化,我们可以从arctan函数的数学图像很清晰的看到这一点
三角函数图片

写到这里,我们已经发现了问题的关键,关键的问题:那就是atan的精度不足以对每一种可能的坐标的角度进行有效的相应
一旦角度过大,那么计算出来的计算角度就可能与实际角度相差过大。


如何解决这个问题?

未完成之理论解

我首先想到的,是从源头上解决这个问题,毕竟只要条件允许,就可以从理论上直接解决这个根本问题,我开始寻求三角函数的精确计算方法
比如:

  1. C/C++实现三角函数的方法:https://blog.csdn.net/weixin_38497390/article/details/78696576
  2. 三角函数计算,Cordic 算法入门: https://blog.csdn.net/liyuanbhu/article/details/8458769

虽然这些方法从理论上能够解决这样的问题,但是无论是哪一种方法,都涉及大量的非标准库的计算算法,在开发中耗时耗力,并不是一种经济的方法
不过我还是把连接放在这里,让有心之人能够继续研究

我又去问了我认识的ACM大佬有没有什么好的处理办法,能够解决atan精度不够的问题
我们得到的方法基本上分为两类
第一种是在特殊的点泰勒展开,使用分段函数来确保原来有误差的地方能够得到更为准确的值
第二种方法是使用特殊的函数,比如双曲函数来代替atan,然后通过一种变换得到另一个近似的值,从而比之前的误差更接近准确值

但是我们都已开发难度大而犹豫了

巧妙至极转换解

在冥思苦想中,我反复盯着我的提问
“就是,atan函数精度不够,或许在值域±45°之内好使,要是输入值大一点就很难精准相应了”

这句话给了我新的思路,既然±45°好用,那我就用这里面的值,然后三角变换不就行了吗?!!!

最后我发现程序能够进行判断,那么就不需要进行三角变换了,直接将平面直角坐标系分成8个部分,这8个部分的三角形使用atan函数时都不会使计算的角度大于45°
从而解决的角度的问题,优化寻路逻辑代码如下

// 第二次优化寻路角度算法,这里使用线性规划来得到每个区域
if (y_delta <= x_delta && y_delta > 0) // 1号区域
    target_angle = 0.0 + atan(y_delta / x_delta) * 180.0 / Pi;
else if (y_delta > x_delta && x_delta >= 0) // 2号区域
    target_angle = 90.0 - (atan(x_delta / y_delta) * 180.0 / Pi);
else if (y_delta >= -x_delta && x_delta < 0) // 3号区域
    target_angle = 90.0 + (atan(-x_delta / y_delta) * 180.0 / Pi);
else if (y_delta < -x_delta && y_delta >= 0) // 4号区域
    target_angle = 180.0 - (atan(y_delta / -x_delta) * 180.0 / Pi);
else if (y_delta >= x_delta && y_delta < 0) // 5号区域
    target_angle = 180.0 + (atan(-y_delta / -x_delta) * 180.0 / Pi);
else if (y_delta < x_delta && x_delta <= 0) // 6号区域
    target_angle = 270.0 - (atan(-x_delta / -y_delta) * 180.0 / Pi);
else if (y_delta <= -x_delta && x_delta > 0) // 7号区域
    target_angle = 270.0 + (atan(x_delta / -y_delta) * 180.0 / Pi);
else if (y_delta > -x_delta && y_delta <= 0) // 8号区域
    target_angle = 0.0 - atan(-y_delta / x_delta) * 180.0 / Pi;
//别忘了atan值域只有-90~+90,且我只使用0~+45之间

平面直角坐标系平分为8个区域
阅读代码,就知道每一个atan的返回角度值都不会大于45°,随后通过区域的判断得到准确的方向角

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值