当前位置: 首页 > 知识库问答 >
问题:

用单精度浮点逼近[0,pi]上的余弦

呼延明朗
2023-03-14

我目前正在研究余弦的近似值。由于最终的目标设备是一个使用32位浮点ALU/LU的自开发设备,并且有一个专门的C编译器,所以我不能使用C库的数学函数(cosf,...)。我的目标是编写在精度和指令/周期数量方面不同的各种方法。

我已经尝试了很多不同的近似算法,从fdlibm开始,taylor展开,pade近似,remezhtml" target="_blank">算法使用maple等等....

但是一旦我只使用浮点精度来实现它们,精度就会有很大的损失。而且要确定:我知道有了双倍精度,高得多的精度根本没有问题...

现在,我有一些近似,精确到几千个ulp在pi/2附近(最大误差发生的范围),我觉得我受到了单一精度转换的限制。

float ua_cos_v2(float x)
{
    float output;
    float myPi = 3.1415927410125732421875f;
    if (x < 0) x = -x;
    int quad = (int32_t)(x*0.63661977236f);//quad = x/(pi/2) = x*2/pi
    if (x<1.58f && x> 1.57f) //exclude approximation around pi/2
    {
        output = -(x - 1.57079637050628662109375f) - 2.0e-12f*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f) + 0.16666667163372039794921875f*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f) + 2.0e-13f*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)+ 0.000198412701138295233249664306640625f*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f);
        output -= 4.37E-08f;
    }
    else {
        float param_x;
        int param_quad = -1;
        switch (quad)
        {
        case 0:
            param_x = x;
            break;
        case 1:
            param_x = myPi - x;
            param_quad = 1;
            break;
        case 2:
            param_x = x - myPi;
            break;
        case 3:
            param_x = 2 * myPi - x;
            break;
        }
        float c1 = 1.0f,
            c2 = -0.5f,
            c3 = 0.0416666679084300994873046875f,
            c4 = -0.001388888922519981861114501953125f,
            c5 = 0.00002480158218531869351863861083984375f,
            c6 = -2.75569362884198199026286602020263671875E-7f,
            c7 = 2.08583283978214240050874650478363037109375E-9f,
            c8 = -1.10807162057025010426514199934899806976318359375E-11f;
        float _x2 = param_x * param_x;
        output = c1 + _x2*(c2 + _x2*(c3 + _x2*(c4 + _x2*(c5 + _x2*(c6 + _x2*(c7 
        + _x2* c8))))));
        if (param_quad == 1 || param_quad == 0)
            output = -output;
    }
    return output;
}

如果我忘记了任何信息,请不要犹豫询问!

提前致谢

共有1个答案

轩辕佑运
2023-03-14

当然,只要使用本地精度运算,就可以在[0,π]上以任何期望的误差范围>=0.5ulp计算余弦。然而,目标越接近一个正确的舍入函数,就需要更多的前期设计工作和运行时的计算工作。

超越函数的实现通常包括变元约简、核心近似和最终修正来抵消变元约简。在参数减少涉及减法的情况下,需要通过显式或隐式地使用更高的精度来避免灾难性的抵消。隐式技术可以html" target="_blank">设计成只依赖于本地精度计算,例如,在使用IEEE-754binary32(单精度)时,将像π这样的常数拆分为一个未求和,如1.57079637 e+0f-4.37113883 e-8f

当硬件提供融合乘加(FMA)操作时,用本机精度计算实现高精度要容易得多。OP没有具体说明他们的目标平台是否提供这种操作,所以我将首先展示一种非常简单的方法,它只依赖于乘法和加法,提供中等精度(最大误差<5ulps)。我假设硬件符合IEEE-754标准,并假设float映射到IEEE-754binary32格式。

以下是基于Colin Wallace的一篇博客文章,标题是“用Chebyshev多项式逼近sin(x)到5 ULP”,在撰写本文时,这篇文章还没有在网上找到。我最初是在这里检索的,Google现在在这里保留了一个缓存副本。他们建议通过使用sin(x)/(x*(x2--π2))中的一个多项式来逼近[-π,π]上的正弦,然后将其乘以x*(x2--π2))。要更准确地计算Aâ-Bâ,一个标准技巧是将其改写为(a-b)*(A+b)。将π表示为两个浮点数pi_high和pi_low的未求值之和,可以避免减法过程中的灾难性抵消,从而将计算xè-πé变为((x-pi_hi)-pi_lo)*((x+pi_hi)+pi_lo)

多项式核近似最好使用极小极大近似,使最大误差最小。我在这里已经这么做了。可以使用各种标准工具,如Maple或数学,或者根据Remez算法创建自己的代码。

对于[0,PI]上的余弦计算,我们可以利用cos(t)=sin(π/2-t)这一事实。将x=(π/2-t)代入x*(x-π/2)*(x+π/2)得到(π/2-t)*(3π/2-t)*(-π/2-t)。常量可以像前面一样分为高和低两部分(或者用另一个常见的习惯用法来说,头和尾)。

/* Approximate cosine on [0, PI] with maximum error of 4.704174 ulp */
float cosine (float x)
{
    const float half_pi_hi       =  1.57079637e+0f; //  0x1.921fb6p+0
    const float half_pi_lo       = -4.37113883e-8f; // -0x1.777a5cp-25
    const float three_half_pi_hi =  4.71238899e+0f; //  0x1.2d97c8p+2
    const float three_half_pi_lo = -1.19248806e-8f; // -0x1.99bc5cp-27
    float p, s, hpmx, thpmx, nhpmx;

    /* cos(x) = sin (pi/2 - x) = sin (hpmx) */
    hpmx = (half_pi_hi - x) + half_pi_lo;               // pi/2-x
    thpmx = (three_half_pi_hi - x) + three_half_pi_lo;  // 3*pi/2 - x
    nhpmx = (-half_pi_hi - x) - half_pi_lo;             // -pi/2 - x

    /* P(hpmx*hpmx) ~= sin (hpmx) / (hpmx * (hpmx * hpmx - pi * pi)) */
    s = hpmx * hpmx;
    p =         1.32729383e-10f;
    p = p * s - 2.33177868e-8f;
    p = p * s + 2.52223435e-6f;
    p = p * s - 1.73503853e-4f;
    p = p * s + 6.62087463e-3f;
    p = p * s - 1.01321176e-1f;
    return hpmx * nhpmx * thpmx * p;
}

代码简单明了,除了用于计算象限的从浮点到int的转换,该转换采用舍入模式到最近或偶,该转换通过“幻数加法”方法执行,并与2/π的乘法相结合(等效于π/2)。最大误差小于1.5ulps。

/* compute cosine on [0, PI] with maximum error of 1.429027 ulp */
float my_cosf (float a)
{
    const float half_pi_hi =  1.57079637e+0f; //  0x1.921fb6p+0
    const float half_pi_lo = -4.37113883e-8f; // -0x1.777a5cp-25
    float c, j, r, s, sa, t;
    int i;

    /* subtract closest multiple of pi/2 giving reduced argument and quadrant */
    j = fmaf (a, 6.36619747e-1f, 12582912.f) - 12582912.f; // 2/pi, 1.5 * 2**23
    a = fmaf (j, -half_pi_hi, a);
    a = fmaf (j, -half_pi_lo, a);

    /* phase shift of pi/2 (one quadrant) for cosine */
    i = (int)j;
    i = i + 1;

    sa = a * a;
    /* Approximate cosine on [-PI/4,+PI/4] with maximum error of 0.87444 ulp */
    c =               2.44677067e-5f;  //  0x1.9a8000p-16
    c = fmaf (c, sa, -1.38877297e-3f); // -0x1.6c0efap-10
    c = fmaf (c, sa,  4.16666567e-2f); //  0x1.555550p-5
    c = fmaf (c, sa, -5.00000000e-1f); // -0x1.000000p-1
    c = fmaf (c, sa,  1.00000000e+0f); //  1.00000000p+0
    /* Approximate sine on [-PI/4,+PI/4] with maximum error of 0.64196 ulp */
    s =               2.86567956e-6f;  //  0x1.80a000p-19
    s = fmaf (s, sa, -1.98559923e-4f); // -0x1.a0690cp-13
    s = fmaf (s, sa,  8.33338592e-3f); //  0x1.111182p-7
    s = fmaf (s, sa, -1.66666672e-1f); // -0x1.555556p-3
    t = a * sa;
    s = fmaf (s, t, a);

    /* select sine approximation or cosine approximation based on quadrant */
    r = (i & 1) ? c : s;
    /* adjust sign based on quadrant */
    r = (i & 2) ? (0.0f - r) : r;

    return r;
}

事实证明,在这种特殊情况下,FMA的使用在准确性方面只提供了一个微小的好处。如果将对FMAF(a,b,c)的调用替换为((a)*(b)+(c)),则最大错误最小地增加到1.451367ulps,即保持在1.5ulps以下。

 类似资料:
  • 我试图改变一个数字在df,但熊猫转换为楼层号码。 我换了一个号码: 它给出: 而不是:

  • 我编写了一个程序来演示Go中的浮点错误: 它打印: 这与用C编写的相同程序的行为相匹配(使用双代码类型) 但是,如果改用,程序就会陷入无限循环!如果将C程序修改为使用而不是,它将打印 为什么在使用时,Go程序的输出与C程序的输出不一样?

  • 在我的计算机科学课程中,我们正在研究浮点数以及它们在内存中是如何表示的。我已经理解了它们在内存中是如何表示的(尾数/有效数、指数及其偏差、符号位),我也理解了浮点是如何相互添加和减去的(反规格化和所有那些有趣的东西)。然而,在翻阅一些学习问题时,我注意到一些我无法解释的东西。 当一个不能精确表示的浮点数加到自己身上几次时,答案比我们在数学上预期的要低,但当同一个浮点数乘以一个整数时,答案就精确地得

  • 问题内容: $a = ‘35’; $b = ‘-34.99’; echo ($a + $b); 结果为0.009999999999998 这是怎么回事?我想知道为什么我的程序不断报告奇怪的结果。 为什么PHP不返回预期的0.01? 问题答案: 因为浮点运算!=实数运算。对于一些浮子和,由不精确性引起的差异的说明是。这适用于使用浮点数的任何语言。 由于浮点数是具有有限精度的二进制数,因此存在有限数量

  • 本文向大家介绍Fortran 浮点数精度,包括了Fortran 浮点数精度的使用技巧和注意事项,需要的朋友参考一下 示例 类型的浮点数real不能有任何实数值。它们可以表示实数,最多可以包含一定数量的十进制数字。 FORTRAN 77保证了两种浮点类型,而最新的标准则至少保证了两种实数类型。实变量可以声明为 x这是默认类型的实数,并且y是比更大的十进制精度的实数x。在Fortran 2008中,十