【问题标题】:Calculate atan2 without std functions or C99不使用 std 函数或 C99 计算 atan2
【发布时间】:2012-08-13 08:26:10
【问题描述】:

我正在计算 3 轴加速度计的角度,但我的编译器没有 atan 或 atan2 函数。它有一个保留的内存插槽,但它调用了一个我在任何文件中都找不到的函数。

我的编译器是运行 ARMCC 编译器的 Keil µVision 4。 编译有文件 math.h,但函数是 extern 并且不存在:

  extern _ARMABI double atan2(double /*y*/, double /*x*/);

是否有我可以包含的库或函数已实现函数 arctan? 或者是否有替代功能来计算加速度计的角度?我需要对角度进行完整的 3 轴校准。

编辑:我希望避免一个充满预先计算值的表格。

【问题讨论】:

  • 我无法验证这一点,但如果您将 GNU ARM 编译器与 uVision 一起使用,那么 IDE 中应该有一个选项可以使用 here 中描述的数学库。您可能还需要使用标准库而不是排除一些浮点函数的微库。
  • 这个函数肯定存在于 ARMCC 编译器附带的库中,甚至应该与 microlib 一起使用,因为数学函数是相同的(区别在于低级软件浮点函数)。

标签: c math embedded arm atan2


【解决方案1】:

以下代码使用rational approximation 将反正切归一化为 [0 1) 区间(您可以将结果乘以 Pi/2 以获得真正的反正切)

normalized_atan(x) ~ (b x + x^2) / (1 + 2 b x + x^2)

其中 b = 0.596227

最大误差为0.1620º

#include <stdint.h>
#include <math.h>

// Approximates atan(x) normalized to the [-1,1] range
// with a maximum error of 0.1620 degrees.

float normalized_atan( float x )
{
    static const uint32_t sign_mask = 0x80000000;
    static const float b = 0.596227f;

    // Extract the sign bit
    uint32_t ux_s  = sign_mask & (uint32_t &)x;

    // Calculate the arctangent in the first quadrant
    float bx_a = ::fabs( b * x );
    float num = bx_a + x * x;
    float atan_1q = num / ( 1.f + bx_a + num );

    // Restore the sign bit
    uint32_t atan_2q = ux_s | (uint32_t &)atan_1q;
    return (float &)atan_2q;
}

// Approximates atan2(y, x) normalized to the [0,4) range
// with a maximum error of 0.1620 degrees

float normalized_atan2( float y, float x )
{
    static const uint32_t sign_mask = 0x80000000;
    static const float b = 0.596227f;

    // Extract the sign bits
    uint32_t ux_s  = sign_mask & (uint32_t &)x;
    uint32_t uy_s  = sign_mask & (uint32_t &)y;

    // Determine the quadrant offset
    float q = (float)( ( ~ux_s & uy_s ) >> 29 | ux_s >> 30 ); 

    // Calculate the arctangent in the first quadrant
    float bxy_a = ::fabs( b * x * y );
    float num = bxy_a + y * y;
    float atan_1q =  num / ( x * x + bxy_a + num );

    // Translate it to the proper quadrant
    uint32_t uatan_2q = (ux_s ^ uy_s) | (uint32_t &)atan_1q;
    return q + (float &)uatan_2q;
} 

如果您需要更高的精度,可以使用三阶有理函数:

normalized_atan(x) ~ ( c x + x^2 + x^3) / ( 1 + (c + 1) x + (c + 1) x^2 + x^3)

其中 c = (1 + sqrt(17)) / 8

最大近似误差为 0.00811º

【讨论】:

  • 为了解决 (0,0) 附近的问题,我更改了一行以添加 .0001,我认为这不会导致问题? float atan_1q = num / (x * x + bxy_a + num + (float).0001); // .0001 to fix dbz and nan issue
  • +1 这些功能做得很好,甚至可以避免分支。你有关于你在哪里得到有理函数和实现的参考吗?
【解决方案2】:

实现自己的arctan2 并不难。使用this 公式将arctan2 转换为arctan。然后您可以使用此infinite series 计算arctan。如果你对这个无穷级数的足够数量的项求和,你将非常接近库函数arctan2 所做的。

这是一个类似的implementation for exp(),您可以用作参考。

【讨论】:

  • 那些无穷级数对于探索函数的数学性质很有用,但它们不适合计算。它们收敛缓慢,需要许多项才能获得准确的结果,并且很难在不累积误差的情况下进行浮点计算。通常,数学库通过使用极小极大多项式来计算这些函数,这些多项式旨在使用一些项来最小化最大误差。
  • 此外,由于各种原因,函数被划分为区间。 arctan 应该被划分为 |x| 1(|x| = 1 可以在任一分区中)。对于 |x| 1,arctan(x) 可以用 1/x 作为输入的极小极大多项式来近似。
  • 这会是一个有效的实现吗? GCC 库中使用的算法是什么?
  • @Jay 效率不高,但很容易实现。我不确定libc 库,但请查看 Eric 对开源实现之一的回答,该实现广泛使用预定义常量来加快计算速度
  • @PavanManjunath 不使用泰勒级数的原因是误差呈多项式增长。更好的近似是使用 Remez 交换算法来找到一个多项式(相同阶),该多项式在所需区间内给出有界绝对误差,但在区间外没有约束。
【解决方案3】:

有一个开源的 atan 实现 here

【讨论】:

  • 苹果有开源门户吗?我从来没想过。 +1
【解决方案4】:

数学函数的实际实现(或 HWFPU 的存根,如果存在的话)应该在 libm 中。对于 GCC,这通过将 -lm 传递给编译器来表示,但我不知道它是如何使用您的特定工具完成的。

【讨论】:

  • -lm 不受 Keil µVision 支持。 “无法识别的选项”
  • 据我所知,Keil 是一个完全不同的编译器,与 GCC 无关。
  • @PavanManjunath:对于 GCC 和类似的编译器,“-l”并不意味着链接器选项。是图书馆的意思。 “-l”表示名为“lib.”的库,用于适当的后缀,例如“.a”或“.dylib”,具体取决于编译器版本和目标平台。当然,在命令行中列出的库会被传递给链接器。传递链接器选项的 GCC 开关是“-X”。
  • @EricPostpischil。是的,你是具体的和正确的! :) 感谢您的更正。我删除了我模棱两可的评论。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-05-20
  • 1970-01-01
  • 2011-05-04
  • 2020-03-27
  • 1970-01-01
  • 2020-04-28
  • 1970-01-01
相关资源
最近更新 更多