首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用标准C数学库实现sinpi()和cospi()

使用标准C数学库实现sinpi()和cospi()
EN

Stack Overflow用户
提问于 2017-03-14 17:39:39
回答 1查看 3.4K关注 0票数 17

函数sinpi(x)计算sin(πx),函数cospi(x)计算cos(πx),其中与π的乘法隐含在函数中。这些函数最初是作为Sun Microsystems在20世纪80年代末中的扩展引入到C标准数学库中的。IEEE 754 sinPi -2008在第9节中指定了等效函数cosPi和™。

sin(πx)和cos(πx)是自然存在的。一个非常简单的例子是Box-Muller变换(G.E.P.Box和MervinE.Muller,“关于随机正态偏差产生的注记”)。“数理统计年鉴”,第29卷,第2期,第610至611页),给出了具有均匀分布的两个独立随机变量U₁和U₂,产生了具有标准正态分布的独立随机变量Z₁和Z₂:

代码语言:javascript
复制
Z₁ = √(-2 ln U₁) cos (2 π U₂)
Z₂ = √(-2 ln U₁) sin (2 π U₂)

另一个例子是度参数的正弦和余弦的计算,例如用Haversine公式计算大圆距离:

代码语言:javascript
复制
/* This function computes the great-circle distance of two points on earth 
   using the Haversine formula, assuming spherical shape of the planet. A 
   well-known numerical issue with the formula is reduced accuracy in the 
   case of near antipodal points.

   lat1, lon1  latitude and longitude of first point, in degrees [-90,+90]
   lat2, lon2  latitude and longitude of second point, in degrees [-180,+180]
   radius      radius of the earth in user-defined units, e.g. 6378.2 km or 
               3963.2 miles

   returns:    distance of the two points, in the same units as radius

   Reference: http://en.wikipedia.org/wiki/Great-circle_distance
*/
double haversine (double lat1, double lon1, double lat2, double lon2, double radius)
{
    double dlat, dlon, c1, c2, d1, d2, a, c, t;

    c1 = cospi (lat1 / 180.0);
    c2 = cospi (lat2 / 180.0);
    dlat = lat2 - lat1;
    dlon = lon2 - lon1;
    d1 = sinpi (dlat / 360.0);
    d2 = sinpi (dlon / 360.0);
    t = d2 * d2 * c1 * c2;
    a = d1 * d1 + t;
    c = 2.0 * asin (fmin (1.0, sqrt (a)));
    return radius * c;
}

对于C++,Boost库提供sin_picos_pi,一些供应商提供sinpicospi功能作为系统库中的扩展。例如,苹果将__sinpi__cospi和相应的单精度版本__sinpif__cospif添加到iOS 7和OSX10.9 (介绍性,幻灯片101)中。但是对于许多其他平台来说,C程序没有现成的实现。

与传统的使用sin (M_PI * x)cos (M_PI * x)的方法相比,sinpicospi的使用通过与π的内部乘法来减少舍入误差,从而提高了精度,同时由于参数的简化也带来了性能优势。

如何使用标准C数学库以合理高效和符合标准的方式实现sinpi()cospi()功能?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-03-14 17:39:39

为了简单起见,我将重点讨论sincospi(),它同时提供正弦和余弦结果。然后,可以将sinpicospi构造为丢弃不需要的数据的包装器函数。在许多应用程序中,不需要处理浮点标志(请参阅fenv.h),也不需要在大多数情况下报告errno错误,因此我将省略这些。

基本的算法结构很简单。由于非常大的参数总是偶数整数,因此2π的倍数,它们的正弦和余弦值是众所周知的。其他参数被折叠到范围-¼,+1/4,同时记录象限信息。利用多项式极小极大逼近计算初等逼近区间上的正弦和余弦。最后,通过周期性的结果交换和符号变化,利用象限数据将初步结果映射到最终结果。

正确处理特殊操作数(特别是-0、无穷大和NaNs)要求编译器只应用符合IEEE-754规则的优化。它可能不会将x*0.0转换为0.0 (对于-0、无穷大和NaNs,这是不正确的),也不能将0.0-x优化为-x,因为根据IEEE-754的5.5.1节,否定是一种位级操作(对零和NaNs产生不同的结果)。大多数编译器将提供强制使用“安全”转换的标志,例如英特尔C/C++编译器的-fp-model=precise

还有一个附加的警告适用于在参数约简期间使用nearbyint函数。与rint一样,此函数根据当前的舍入模式指定为整数。当不使用fenv.h时,舍入模式默认为整“to -最近-或-偶数”。当它被使用时,有一个风险,一个定向舍入模式是有效的。这可以通过使用round来解决,它总是提供舍入模式“从零到最近,与当前舍入模式无关”。但是,由于在大多数处理器体系结构上不支持等效的机器指令,此函数将趋于较慢。

关于性能的注意事项:下面的C99代码严重依赖于fma()的使用,后者实现了融合相加操作。在大多数现代硬件体系结构中,这是由相应的硬件指令直接支持的。如果不是这样的话,代码可能会由于FMA仿真的速度过慢而经历显著的减速。

代码语言:javascript
复制
 #include <math.h>
 #include <stdint.h>

/* Writes result sine result sin(πa) to the location pointed to by sp
   Writes result cosine result cos(πa) to the location pointed to by cp

   In extensive testing, no errors > 0.97 ulp were found in either the sine
   or cosine results, suggesting the results returned are faithfully rounded.
*/
void my_sincospi (double a, double *sp, double *cp)
{
    double c, r, s, t, az;
    int64_t i;

    az = a * 0.0; // must be evaluated with IEEE-754 semantics
    /* for |a| >= 2**53, cospi(a) = 1.0, but cospi(Inf) = NaN */
    a = (fabs (a) < 9.0071992547409920e+15) ? a : az;  // 0x1.0p53
    /* reduce argument to primary approximation interval (-0.25, 0.25) */
    r = nearbyint (a + a); // must use IEEE-754 "to nearest" rounding
    i = (int64_t)r;
    t = fma (-0.5, r, a);
    /* compute core approximations */
    s = t * t;
    /* Approximate cos(pi*x) for x in [-0.25,0.25] */
    r =            -1.0369917389758117e-4;
    r = fma (r, s,  1.9294935641298806e-3);
    r = fma (r, s, -2.5806887942825395e-2);
    r = fma (r, s,  2.3533063028328211e-1);
    r = fma (r, s, -1.3352627688538006e+0);
    r = fma (r, s,  4.0587121264167623e+0);
    r = fma (r, s, -4.9348022005446790e+0);
    c = fma (r, s,  1.0000000000000000e+0);
    /* Approximate sin(pi*x) for x in [-0.25,0.25] */
    r =             4.6151442520157035e-4;
    r = fma (r, s, -7.3700183130883555e-3);
    r = fma (r, s,  8.2145868949323936e-2);
    r = fma (r, s, -5.9926452893214921e-1);
    r = fma (r, s,  2.5501640398732688e+0);
    r = fma (r, s, -5.1677127800499516e+0);
    s = s * t;
    r = r * s;
    s = fma (t, 3.1415926535897931e+0, r);
    /* map results according to quadrant */
    if (i & 2) {
        s = 0.0 - s; // must be evaluated with IEEE-754 semantics
        c = 0.0 - c; // must be evaluated with IEEE-754 semantics
    }
    if (i & 1) { 
        t = 0.0 - s; // must be evaluated with IEEE-754 semantics
        s = c;
        c = t;
    }
    /* IEEE-754: sinPi(+n) is +0 and sinPi(-n) is -0 for positive integers n */
    if (a == floor (a)) s = az;
    *sp = s;
    *cp = c;
}

单精度版本基本上只有核心近似不同。使用详尽的测试可以精确地确定错误的界限。

代码语言:javascript
复制
#include <math.h>
#include <stdint.h>

/* Writes result sine result sin(πa) to the location pointed to by sp
   Writes result cosine result cos(πa) to the location pointed to by cp

   In exhaustive testing, the maximum error in sine results was 0.96677 ulp,
   the maximum error in cosine results was 0.96563 ulp, meaning results are
   faithfully rounded.
*/
void my_sincospif (float a, float *sp, float *cp)
{
    float az, t, c, r, s;
    int32_t i;

    az = a * 0.0f; // must be evaluated with IEEE-754 semantics
    /* for |a| > 2**24, cospi(a) = 1.0f, but cospi(Inf) = NaN */
    a = (fabsf (a) < 0x1.0p24f) ? a : az;
    r = nearbyintf (a + a); // must use IEEE-754 "to nearest" rounding
    i = (int32_t)r;
    t = fmaf (-0.5f, r, a);
    /* compute core approximations */
    s = t * t;
    /* Approximate cos(pi*x) for x in [-0.25,0.25] */
    r =              0x1.d9e000p-3f;
    r = fmaf (r, s, -0x1.55c400p+0f);
    r = fmaf (r, s,  0x1.03c1cep+2f);
    r = fmaf (r, s, -0x1.3bd3ccp+2f);
    c = fmaf (r, s,  0x1.000000p+0f);
    /* Approximate sin(pi*x) for x in [-0.25,0.25] */
    r =             -0x1.310000p-1f;
    r = fmaf (r, s,  0x1.46737ep+1f);
    r = fmaf (r, s, -0x1.4abbfep+2f);
    r = (t * s) * r;
    s = fmaf (t, 0x1.921fb6p+1f, r);
    if (i & 2) {
        s = 0.0f - s; // must be evaluated with IEEE-754 semantics
        c = 0.0f - c; // must be evaluated with IEEE-754 semantics
    }
    if (i & 1) {
        t = 0.0f - s; // must be evaluated with IEEE-754 semantics
        s = c;
        c = t;
    }
    /* IEEE-754: sinPi(+n) is +0 and sinPi(-n) is -0 for positive integers n */
    if (a == floorf (a)) s = az;
    *sp = s;
    *cp = c;
}
票数 15
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42792939

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档