c - 从 log1pf() 计算 asinhf() 的最准确方法?

标签 c math floating-point floating-accuracy

反双曲函数asinh()与自然对数密切相关。我试图确定从 C99 标准数学函数 log1p() 计算 asinh() 的最准确方法。为了便于实验,我现在限制自己使用 IEEE-754 单精度计算,即我正在查看 asinhf()log1pf()。我打算稍后重新使用完全相同的算法进行 double 计算,即 asinh()log1p()

我的主要目标是尽量减少 ulp 错误,次要目标是尽量减少不正确舍入结果的数量,前提是改进后的代码最多比下面发布的版本慢一点。欢迎任何对准确性的增量改进,比如 0.2 ulp。添加几个 FMA(融合乘法加法)就可以了,另一方面,我希望有人可以找到一个采用快速 rsqrtf()(平方根倒数)的解决方案。

生成的 C99 代码应该适合向量化,可能通过一些简单的小转换。所有中间计算必须以函数参数和结果的精度进行,因为任何切换到更高精度都可能对性能产生严重的负面影响。代码必须在 IEEE-754 反规范支持和 FTZ(清零)模式下正常工作。

到目前为止,我已经确定了以下两个候选实现。请注意,通过一次调用 log1pf(),代码可以很容易地转换为无分支向量化版本,但我在这个阶段没有这样做以避免不必要的混淆。

/* for a >= 0, asinh(a) = log (a + sqrt (a*a+1))
                        = log1p (a + (sqrt (a*a+1) - 1))
                        = log1p (a + sqrt1pm1 (a*a))
                        = log1p (a + (a*a / (1 + sqrt(a*a + 1))))
                        = log1p (a + a * (a / (1 + sqrt(a*a + 1))))
                        = log1p (fma (a / (1 + sqrt(a*a + 1)), a, a)
                        = log1p (fma (1 / (1/a + sqrt(1/a*a + 1)), a, a)
*/
float my_asinhf (float a)
{
    float fa, t;
    fa = fabsf (a);
#if !USE_RECIPROCAL
    if (fa >= 0x1.0p64f) { // prevent overflow in intermediate computation
        t = log1pf (fa) + 0x1.62e430p-1f; // log(2)
    } else {
        t = fmaf (fa / (1.0f + sqrtf (fmaf (fa, fa, 1.0f))), fa, fa);
        t = log1pf (t);
    }
#else // USE_RECIPROCAL
    if (fa > 0x1.0p126f) { // prevent underflow in intermediate computation
        t = log1pf (fa) + 0x1.62e430p-1f; // log(2)
    } else {
        t = 1.0f / fa;
        t = fmaf (1.0f / (t + sqrtf (fmaf (t, t, 1.0f))), fa, fa);
        t = log1pf (t);
    }
#endif // USE_RECIPROCAL
    return copysignf (t, a); // restore sign
}

使用精确到 < 0.6 ulps 的特定 log1pf() 实现,我在对所有 232 可能的 IEEE-754 进行详尽测试时观察到以下错误统计数据单精度输入。当 USE_RECIPROCAL = 0 时,最大误差为 1.49486 ulp,并且有 353,587,822 个不正确的舍入结果。使用 USE_RECIPROCAL = 1,最大误差为 1.50805 ulp,并且只有 77,569,390 个不正确的舍入结果。

就性能而言,如果倒数和完全除法花费的时间大致相同,则变体 USE_RECIPROCAL = 0 会更快,但变体 USE_RECIPROCAL = 1 可能如果有非常快速的互惠支持,速度会更快。

答案可以假设所有基本算术,包括 FMA(融合乘加)都根据 IEEE-754 舍入到最近或偶数模式正确舍入。 此外,倒数和rsqrtf() 可能的版本更快,几乎正确舍入,其中“几乎正确舍入”意味着最大值ulp 误差将限制在 0.53 ulp 之类的范围内,并且绝大多数结果(例如 > 95%)都被正确舍入。具有定向舍入的基本算术可能可用,不会对性能产生额外成本。

最佳答案

首先,您可能想查看 log1pf 函数的准确性和速度:这些在 libms 之间可能会有所不同(我发现 OS X 数学函数很快,glibc速度较慢但通常正确舍入的)。

Openlibm,基于 BSD libm,而 BSD libm 又基于 Sun 的 fdlibm,按范围使用多种方法,但主要位是 the relation :

t = x*x;
w = log1pf(fabsf(x)+t/(one+sqrtf(one+t)));

您可能还想尝试使用 -fno-math-errno 选项进行编译,该选项禁用 sqrt 的旧 System V 错误代码(IEEE-754 异常将仍然有效)。

关于c - 从 log1pf() 计算 asinhf() 的最准确方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34535278/

相关文章:

c++ - 由于标准测试失败,用于查找点是否在 2D 多边形内的替代测试

algorithm - 计算一个整数的除数而不只是枚举它们(或者估计如果不可能的话)?

c - 用 C 序列化 double 和 float

c - 我在哪里可以获得 `struct file* file_open(const char* path, int flags, int rights)` 的手册页

c - 来自两个不同文件的 fscanf

c - 与信号的简单同步

math - 寻找线性同余系统等分布解的算法

c++ - 为什么我的 C++ 程序不计算 'e' 的更多位数?

javascript - 为什么 e 符号数字在 JavaScript 中的计算方式不同?

javascript - 为什么具有许多有效数字的数字在 C# 和 JavaScript 中的处理方式不同?