【问题标题】:fast double exp2 function in C [closed]C中的快速双exp2函数[关闭]
【发布时间】:2021-04-09 17:46:19
【问题描述】:

我需要以下双精度快速 exp2 函数的版本,你能帮帮我吗?请不要回答说这是一个近似值,所以双重版本毫无意义,将结果转换为 (double) 就足够了..谢谢。我在某处找到并且对我有用的函数如下,它比 exp2f() 快得多,但不幸的是我找不到任何双精度版本:

inline float fastexp2(float p)
{
 if(p<-126.f) p= -126.f;
 int w=(int)p;
 float z=p-(float)w;
 if(p<0.f) z+= 1.f;
 union {uint32_t i; float f;} v={(uint32_t)((1<<23)*(p+121.2740575f+27.7280233f/(4.84252568f -z)-1.49012907f * z)) };
 return v.f;
}

【问题讨论】:

  • 此公式无法编译:error: expected '}' before ')' token
  • Re“请不要回答说这是一个近似值,所以双精度版本毫无意义,将结果转换为 (double) 就足够了……谢谢”:为什么?您希望将问题中的代码结果转换为double 不会提供什么?您想要更准确的近似值吗?还有多少?您是否只想用double 重写它,所以它使用相同的值进行double 算术?你想要更广泛的范围吗?您需要指定问题的标准。
  • elena,请发布一个可以编译的版本。
  • elena,当p 远大于INT_MAX 时,int w = (int) p; 步骤将失败。如果您只寻找适用于 float 子范围的函数,那么该范围是多少?

标签: c approximation exp math-functions


【解决方案1】:

我的假设是问题中的现有代码假设 IEEE-754 二进制浮点计算,特别是将 C 的 float 类型映射到 IEEE-754 的 binary32 格式。

现有代码表明,只有在正常范围内的浮点结果才有意义:通过从下方钳位输入来避免次正常结果,并忽略溢出。所以对于float 计算,有效输入在区间 [-126, 128) 中。通过详尽的测试,我发现问题中函数的最大相对误差为7.16e-5,均方根(RMS)误差为1.36e-5。

我的假设是,对double 计算的期望更改应该将允许输入的范围扩大到 [-1022, 1024),并且应该保持相同的相对精度。代码是以相当神秘的方式编写的。因此,作为第一步,我将其重新排列为更具可读性的版本。在第二步中,我调整了核心近似的系数,以最小化最大相对误差。这会产生以下 ISO-C99 代码:

/* compute 2**p, for p in [-126, 128). Maximum relative error: 5.04e-5; RMS error: 1.03e-5 */
float fastexp2 (float p)
{
    const int FP32_MIN_EXPO = -126; // exponent of minimum binary32 normal
    const int FP32_MANT_BITS = 23;  // number of stored mantissa (significand) bits
    const int FP32_EXPO_BIAS = 127; // binary32 exponent bias
    float res;

    p = (p < FP32_MIN_EXPO) ? FP32_MIN_EXPO : p; // clamp below
    /* 2**p = 2**(w+z), with w an integer and z in [0, 1) */
    float w = floorf (p); // integral part
    float z = p - w;      // fractional part
    /* approximate 2**z-1 for z in [0, 1) */
    float approx = -0x1.6e7592p+2f + 0x1.bba764p+4f / (0x1.35ed00p+2f - z) - 0x1.f5e546p-2f * z;
    /* assemble the exponent and mantissa components into final result */
    int32_t resi = ((1 << FP32_MANT_BITS) * (w + FP32_EXPO_BIAS + approx));
    memcpy (&res, &resi, sizeof res);
    return res;
}

系数的重构和重新调整导致精度略有提高,最大相对误差为 5.04e-5,RMS 误差为 1.03e-5。需要注意的是,浮点运算通常不具有关联性,因此任何浮点运算的重新关联,无论是通过手动代码更改还是编译器转换,都可能影响所述精度,需要仔细重新测试。

我预计不需要修改代码,因为它可以编译为通用架构的高效机器代码,这可以从 Compiler Explorer 的试用编译中看出,例如gcc with x86-64gcc with ARM64

此时,很明显需要更改哪些内容才能切换到double 计算。将float 的所有实例更改为double,将int32_t 的所有实例更改为int64_t,更改文字常量和数学函数的类型后缀,并将IEEE-754 的浮点格式特定参数binary32 更改为那些用于 IEEE-754 binary64。核心逼近需要重新调整,以便在核心逼近中充分利用双精度系数。

/* compute 2**p, for p in [-1022, 1024). Maximum relative error: 4.93e-5. RMS error: 9.91e-6 */
double fastexp2 (double p)
{
    const int FP64_MIN_EXPO = -1022; // exponent of minimum binary64 normal
    const int FP64_MANT_BITS = 52;   // number of stored mantissa (significand) bits
    const int FP64_EXPO_BIAS = 1023; // binary64 exponent bias
    double res;

    p = (p < FP64_MIN_EXPO) ? FP64_MIN_EXPO : p; // clamp below
    /* 2**p = 2**(w+z), with w an integer and z in [0, 1) */
    double w = floor (p); // integral part
    double z = p - w;     // fractional part
    /* approximate 2**z-1 for z in [0, 1) */
    double approx = -0x1.6e75d58p+2 + 0x1.bba7414p+4 / (0x1.35eccbap+2 - z) - 0x1.f5e53c2p-2 * z;
    /* assemble the exponent and mantissa components into final result */
    int64_t resi = ((1LL << FP64_MANT_BITS) * (w + FP64_EXPO_BIAS + approx));
    memcpy (&res, &resi, sizeof res);
    return res;
}

最大相对误差和均方根误差均略微下降至 4.93e-5 和 9.91e-6。这是预期的,因为对于大致精确到 15 位的近似值,中间计算是以 24 位精度还是 53 位精度执行的无关紧要。计算使用除法,在我熟悉的所有平台上,double 的速度往往比float 慢,因此双精度端口似乎没有提供任何显着优势,除了可能消除转换开销如果调用代码使用双精度计算。

【讨论】:

  • Njuffa 非常感谢您抽出宝贵的时间,这正是我所需要的!我目前无法花时间对浮动版本进行逆向工程并自己制作双重版本。它运作良好。谢谢!
  • 在 [−10, +10] 中接近 0.1 的所有倍数附近的快速测试表明,float 版本的函数在 201 次中有 76 次更准确!
  • @elena:使用更高的精度不会创造更高的精度。考虑一个有一些多项式 p(x) 近似于 2^x。它以一定的精度接近 2^x(无论是测量为最小最大误差还是其他一些指标)。如果该精度大约为 2^-20,则在此范围内,然后使用 float 计算它会增加一些舍入误差(可能每个操作平均 2^-25,但以各种方式复合) .所以精度仍然会在 2^-20 左右。在double 中计算它会将舍入误差更改为每个大约 2^-54,但多项式误差...
  • … 仍然在 2^-20 左右。多项式近似的准确性(或缺乏准确性)是限制因素,而不是用于计算的精度。因此,您不仅需要将例程从float 转换为double,还需要重新设计近似函数的多项式。这是一项复杂的工作,世界上没有多少人能做到,也很少有人能做好。但是您来找我们并要求提供该功能的“快速双倍”版本。 njuffa 实现了这一点。如果您感到失望,这是意料之中的,因为您没有告诉我们您的准确度目标到底是什么,只是要求......
  • ……以特定方式交付的新程序。未正确说明问题要求是该问题被否决并关闭的部分原因。
猜你喜欢
  • 1970-01-01
  • 2012-12-20
  • 1970-01-01
  • 1970-01-01
  • 2011-02-11
  • 1970-01-01
  • 1970-01-01
  • 2012-10-15
  • 2015-12-24
相关资源
最近更新 更多