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

从 log1pf() 计算阿辛夫()的最准确方法?

呼延俊良
2023-03-14

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

我的主要目标是最小化ulp错误,次要目标是在改进的代码最多比下面发布的版本慢一点的约束下,最小化不正确舍入的结果的数量。精度的任何增量改进,比如0.2 ulp,都是受欢迎的。添加几个fma(融合乘加)会很好,另一方面,我希望有人能找到一个采用快速< code>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
}

使用特定的< code>log1pf()实现,可以精确地

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

答案可以假设所有基本算法,包括FMA(融合乘法加法),都根据IEEE-754正确四舍五入到最接近或偶数模式。此外,可能会提供更快、几乎正确舍入的倒数和rsqrtf()版本,其中“近似正确舍入”意味着最大ulp误差将限制在0.53 ulps左右,并且绝大多数结果都是如此

共有2个答案

燕宜修
2023-03-14

经过各种额外的实验,我确信一个简单的参数转换,如果不使用比参数和结果更高的精度,就不能实现比我发布的代码中的第一个变量更严格的错误界限。

由于我的问题是关于最小化参数转换的错误,除了log1pf()本身中的错误之外,最直接的实验方法是使用对数函数的正确舍入实现。请注意,在高性能环境中不太可能存在正确舍入的实现。根据J.-M. Muller等人的工作,要产生准确的单精度结果,例如,x86扩展精度计算应该就足够了

float accurate_log1pf (float a)
{
    float res;
    __asm fldln2;
    __asm fld     dword ptr [a];
    __asm fyl2xp1;
    __asm fst     dword ptr [res];
    __asm fcompp;
    return res;
}

使用我的问题中的第一个变体的< code>asinhf()的实现如下所示:

float my_asinhf (float a)
{
    float fa, s, t;
    fa = fabsf (a);
    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 = accurate_log1pf (t);
    }
    return copysignf (t, a); // restore sign
}

使用所有232 IEEE-754单精度操作数进行测试表明,1.49486070 ulp的最大误差出现在< code>0x1.ff5022p-9处,并且有353,521,140个不正确舍入的结果。如果整个参数转换使用双精度算术会怎么样?代码更改为

float my_asinhf (float a)
{
    float fa, s, t;
    fa = fabsf (a);
    if (fa >= 0x1.0p64f) { // prevent overflow in intermediate computation
        t = log1pf (fa) + 0x1.62e430p-1f; // log(2)
    } else {
        double tt = fa;
        tt = fma (tt / (1.0 + sqrt (fma (tt, tt, 1.0))), tt, tt);
        t = (float)tt;
        t = accurate_log1pf (t);
    }
    return copysignf (t, a); // restore sign
}

但是,此更改不会改善错误绑定!1.49486070 ulp 的最大误差仍然出现在 ±0x1.ff5022p-9 处,现在有 350,971,046 个错误舍入的结果,比以前略少。问题似乎是浮点操作数无法向 log1pf() 传达足够的信息来产生更准确的结果。在计算辛夫()坐标()时,也会出现类似的问题。如果将简化的参数(表示为正确舍入的浮点操作数)传递给核心多项式,则 sinf()cosf() 中产生的误差在 1.5 ulp 下只是一点点,就像我们在这里用 my_asinhf() 观察到的那样。

一种解决方案是计算比单精度更高的转换参数,例如作为双浮点操作数对(在Andrew Thall的文章中可以找到对双浮点技术的有用的简要概述)。在这种情况下,基于对数的导数是倒数的知识,我们可以使用附加信息对结果执行线性插值。这给了我们:

float my_asinhf (float a)
{
    float fa, s, t;
    fa = fabsf (a);
    if (fa >= 0x1.0p64f) { // prevent overflow in intermediate computation
        t = log1pf (fa) + 0x1.62e430p-1f; // log(2)
    } else {
        double tt = fa;
        tt = fma (tt / (1.0 + sqrt (fma (tt, tt, 1.0))), tt, tt);
        t = (float)tt;                // "head" of double-float
        s = (float)(tt - (double)t);  // "tail" of double-float
        t = fmaf (s, 1.0f / (1.0f + t), accurate_log1pf (t)); // interpolate
    }
    return copysignf (t, a); // restore sign
}

该版本的详尽测试表明,最大误差已降至0.99999948 ulp,它出现在< code > 0x 1 . dee EAP-22 处。有349,653,534个不正确的舍入结果。< code>asinhf()的完整实现已经实现。

不幸的是,这个结果的实际效用是有限的。根据硬件平台的不同,double上算术运算的吞吐量可能仅为floatoperations吞吐量的1/2到1/32。双精度计算可以替换为双浮点计算,但这也会产生非常大的成本。最后,我在这里的方法是将单精度实现用作后续双精度工作的试验场,许多硬件平台(当然是我感兴趣的所有硬件平台)都不支持精度高于IEEE-754二进制64(双精度)的数字格式。因此,在中间计算中,任何解决方案都不需要较高精度的算法。

由于在asinhf()的情况下,所有麻烦的参数都很小,因此可以[部分地?]通过对原点周围区域使用多项式minimax近似来解决精度问题。由于这将创建另一个代码分支,因此可能会使矢量化更加困难。

韦原
2023-03-14

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

Openlibm基于BSD libm,而BSD libm又基于Sun的fdlibm,它使用多种方法,但主要是以下关系:

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

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

 类似资料:
  • 问题内容: $fooValue = 100.68; $cowValue = 100.67; 这将显示“错误”。 我知道用Java做些什么。但是我不太擅长PHP,尤其是在计算方面。 请帮帮我。我是说如何成功? 问题答案: 浮点数是一种不精确的数据类型(就像所有浮点数据类型一样),因为在二进制之间进行转换可能会失去精度。这就是为什么当您需要高精度(精确)时不应该使用浮点运算的原因。 在PHP中,检查B

  • 问题内容: 我一直在尝试实现单例,以用作我从网络上传到我的iOS应用的照片的缓存。我在下面的代码中附加了三个变体。我试图使版本2正常工作,但是它导致了我不理解的编译器错误,并希望就我做错的事情寻求帮助。变体1进行缓存,但我不喜欢使用全局变量。变体3并没有进行实际的缓存,我相信这是因为我在赋给var ic = ....的赋值中获得了副本,对吗? 任何反馈和见解将不胜感激。 谢谢Zvi 问题答案: 标

  • 本文向大家介绍Python计算库numpy进行方差/标准方差/样本标准方差/协方差的计算,包括了Python计算库numpy进行方差/标准方差/样本标准方差/协方差的计算的使用技巧和注意事项,需要的朋友参考一下 使用numpy可以做很多事情,在这篇文章中简单介绍一下如何使用numpy进行方差/标准方差/样本标准方差/协方差的计算。 variance: 方差 方差(Variance)是概率论中最基础

  • 问题内容: 在MySQL中,哪种方式计算行数应该更快? 这个: 或者,替代方案: 有人会认为第一种方法应该更快,因为在内部确定类似情况时,这显然是数据库领域,而数据库引擎应该比其他任何人都要快。 问题答案: 当您使用count列索引时,它将是最好的结果。使用 MyISAM 引擎的Mysql 实际上存储行数,每次尝试对所有行进行计数时,它都不会对所有行进行计数。(基于主键的列) 使用PHP计数行不是

  • 本文向大家介绍PHP中浮点数计算比较及取整不准确的解决方法,包括了PHP中浮点数计算比较及取整不准确的解决方法的使用技巧和注意事项,需要的朋友参考一下 浮点数计算结果比较 一则浮点数计算例子如下: 打印出的结果是:bool(false)。也就是说在这里 0.2+0.7 的计算结果与 0.9 并不相等,这显然是有违我们的常识的。 对此问题,PHP官方手册曾又说明:显然简单的十进制分数如 0.2 不能

  • 我希望获得PDF中每个页面的准确大小,作为我将创建的PDF单元测试的一部分。当我处理每个文档中具有许多不同页面大小的PDF时,代码返回一个ArrayList维度。 AFAIK每个页面也可以有自己的DPI设置。 我在谷歌上搜索了很多次,但我只找到了这个答案,这只给了我部分答案,因为我仍然需要计算出每页的DPI。 PDFBox-查找页面维度