arctan是如何实施的?

时间:2014-04-13 20:20:53

标签: algorithm floating-point processor bits

库的许多实现深入到所有弧函数的FPATAN实现。 FPATAN是如何实施的?假设我们有1位符号,M位尾数和N位指数,得到这个数字反正切的算法是什么?应该有这样的算法,因为FPU就是这样做的。

3 个答案:

答案 0 :(得分:15)

x86处理器中FPATAN指令的实现通常是专有的。为了计算arctan或其他(逆)三角函数,常见算法遵循三个步骤:

  1. 用于将完整输入域映射到窄间隔的参数缩减
  2. 在窄间隔(初级近似间隔)上计算核心近似值
  3. 基于参数减少扩展中间结果以产生最终结果
  4. 参数减少通常基于众所周知的三角恒等式,可以在各种标准参考中查找,例如MathWorld(http://mathworld.wolfram.com/InverseTangent.html)。对于arctan的计算,常用的身份是

    • arctan(-x)= -arctan(x)
    • arctan(1 / x)= 0.5 * pi - arctan(x)[x> 0]
    • arctan(x)= arctan(c)+ arctan((x - c)/(1 + x * c))

    请注意,最后一个身份适用于构造值arctan(i / 2 n ),i = 1 ... 2 n 的表,其中允许使用任意窄的初级近似间隔,但需要额外的表存储。这是空间和时间之间的经典编程权衡。

    核心区间的近似通常是足够程度的极小极大多项式近似。由于浮点除法的高成本,理性近似在现代硬件上通常没有竞争力,并且由于两个多项式的计算加上除法所贡献的误差,还会遭受额外的数值误差。

    最小极大多项式近似的系数通常使用Remez算法(http://en.wikipedia.org/wiki/Remez_algorithm)计算。像Maple和Mathematica这样的工具具有内置工具来计算这种近似值。通过确保所有系数都是可精确表示的机器编号,可以提高多项式近似的精度。我所知道的唯一具有内置功能的工具是Sollya(http://sollya.gforge.inria.fr/),它提供fpminimax()功能。

    多项式的评估通常使用Horner的方案(http://en.wikipedia.org/wiki/Horner%27s_method),该方案有效且准确,或者是Estrin方案(http://en.wikipedia.org/wiki/Estrin%27s_scheme)和Horner's的混合。 Estrin的方案允许人们充分利用超标量处理器提供的指令级并行性,对总体指令数量产生轻微影响,并且通常(但不总是)对准确性产生良性影响。

    FMA(融合乘法加法)的使用增强了任一评估方案的准确性和性能,因为减少了舍入步骤的数量并提供了一些防减法消除保护。 FMA存在于许多处理器上,包括GPU和最新的x86 CPU。在标准C和标准C ++中,FMA操作作为fma()标准库函数公开,但是它需要在不提供硬件支持的平台上进行模拟,这使得它在这些平台上变慢。

    从编程的角度来看,在将从文本到机器表示的近似和参数减少所需的浮点常量进行转换时,我们希望避免转换错误的风险。 ASCII到浮点转换例程因包含棘手的错误(例如http://www.exploringbinary.com/php-hangs-on-numeric-value-2-2250738585072011e-308/)而臭名昭着。标准C提供的一种机制( C ++,我知道,它仅作为专有扩展可用)是将浮点常量指定为十六进制文字,直接表达底层位模式,有效避免复杂的转换。

    下面是计算双精度arctan()的C代码,它演示了上面提到的许多设计原则和技术。这个快速构造的代码缺乏其他答案中指向的实现的复杂性,但应该提供小于2 ulps的错误的结果,这在各种上下文中可能是足够的。我使用Remez算法的简单实现创建了一个自定义的minimax近似,该算法对所有中间步骤使用了1024位浮点算法。我希望使用Sollya或类似的工具来获得数值上优越的近似值。

    double my_atan (double x)
    {
        double a, z, p, r, s, q, o;
        /* argument reduction: 
           arctan (-x) = -arctan(x); 
           arctan (1/x) = 1/2 * pi - arctan (x), when x > 0
        */
        z = fabs (x);
        a = (z > 1.0) ? 1.0 / z : z;
        /* evaluate minimax polynomial approximation */
        s = a * a; // a**2
        q = s * s; // a**4
        o = q * q; // a**8
        /* use Estrin's scheme for low-order terms */
        p = fma (fma (fma (-0x1.53e1d2a25ff34p-16, s, 0x1.d3b63dbb65af4p-13), q,
                      fma (-0x1.312788dde0801p-10, s, 0x1.f9690c82492dbp-9)), o,
                 fma (fma (-0x1.2cf5aabc7cef3p-7, s, 0x1.162b0b2a3bfcep-6), q, 
                      fma (-0x1.a7256feb6fc5cp-6, s, 0x1.171560ce4a483p-5)));
        /* use Horner's scheme for high-order terms */
        p = fma (fma (fma (fma (fma (fma (fma (fma (fma (fma (fma (fma (p, s,
            -0x1.4f44d841450e1p-5), s,
             0x1.7ee3d3f36bb94p-5), s, 
            -0x1.ad32ae04a9fd1p-5), s,  
             0x1.e17813d66954fp-5), s, 
            -0x1.11089ca9a5bcdp-4), s,  
             0x1.3b12b2db51738p-4), s,
            -0x1.745d022f8dc5cp-4), s,
             0x1.c71c709dfe927p-4), s,
            -0x1.2492491fa1744p-3), s,
             0x1.99999999840d2p-3), s,
            -0x1.555555555544cp-2) * s, a, a);
        /* back substitution based on argument reduction */
        r = (z > 1.0) ? (0x1.921fb54442d18p+0 - p) : p;
        return copysign (r, x);
    }
    

答案 1 :(得分:6)

三角函数确实有非常难看的实现,这些实现很糟糕,并且做了很多小事。我认为在这里找到一个能够解释实际使用的算法的人会非常困难。

以下是atan2实施:https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/ieee754/dbl-64/e_atan2.c;h=a287ca6656b210c77367eec3c46d72f18476d61d;hb=HEAD

编辑:其实我找到了这个:http://www.netlib.org/fdlibm/e_atan2.c这个更容易理解,但可能因为那个(?)而慢。

FPU在某些电路中完成所有这些工作,因此CPU不必完成所有这些工作。

答案 2 :(得分:6)

总结:很难。此外,Eric Postpischil和Stephen Canon,他们有时候也很喜欢SO,非常擅长。

许多特殊功能的常用方法如下:

  • 处理NaN,无穷大和签名零作为特殊情况。
  • 如果数字太大而结果转向M_PI,则返回M_PI。请调用此阈值M
  • 如果有任何类型的参数减少标识,请使用它将参数带入更好的范围。 (这可能很棘手:对于sincos,这意味着你选择了2pi的精确值的倍数,这样你就可以登陆在正确的范围内。)
  • [0,M)分成有限的多个区间。在每个区间使用Chebyshev approximation来确定相当高的arctan。 (这是离线完成的,它通常是你在这些实现中看到的所有魔法数字的来源。而且,人们可以使用Remez的交换算法稍微收紧Chebyshev近似值,但是我可以不知道有什么情况可以帮助很多。)
  • 找出参数所在的区间(使用if s和东西或只是表索引的技巧),并在该区间内评估Chebyshev系列。

这里特别需要一些属性:

  • arctan实施应该是单调的;也就是说,如果是x < y,那么arctan(x) <= arctan(y)
  • arctan实现应始终在正确答案的1 ulp内返回答案。请注意,这是一个相对错误界限。

评估Chebyshev系列以使这两个属性成立并不是完全简单的。两个double用于表示单个值的不同部分的技巧在这里很常见。然后可能有一些案例表明实施是单调的。此外,接近于零,泰勒近似arctan而不是切比雪夫近似 - 你在相对误差约束之后并使用霍纳规则评估该系列应该有效。

如果你正在寻找一个atan实现来阅读,那么fdlibm似乎不如目前在glibc中那样令人讨厌。参数减少似乎基于trig标识tan(a+b) = (tan(a) + tan(b)) / (1 - tan(a) tan(b)),适当地使用0.511.5 tan(a)