在|x| < pi/4的范围内,对tan(x)进行双精度近似。

11
我想在范围为-pi/4到pi/4的区间内,快速地将tan(x)近似到1 ULP以内。我已经找到了一个几乎足够好的解决方案,但最后的两倍因子仍然让我无法理解,即使我花了相当多的计算时间和模拟退火算法。
我寻求一个特定的数值近似tan(x)的方法,其形式为:
tan(x) = x*P(x^2)/Q(x^2)
其中P和Q都是x^2的三次方程。我需要对近似方法施加一些额外的约束条件。即在零点处以及理想情况下在pi/4处匹配函数值和梯度。目前,我已经决定放宽在pi/4处的梯度约束。我在这里应该说明的是,我寻求的近似方法在Hart的“计算机近似圣经”中是不可思议地缺失,这表明它是一个相当棘手的收敛问题。
通常他会给出整个序列,但是tan和pi/4的表格跳过了3,3,这很奇怪,因为它应该对于64位实数是完美的。精度是在P和Q的最高非零系数为N和M时获得的正确小数位数。
Precision N M Index
10.66 2 2 4283
13.62 2 3 4284
19.74 4 3 4285
19.94 3 4 4286
我所拥有的系数是通过模拟退火和一些本地技巧从Pade逼近中得出的。虽然还可以,但对于我的目的来说还不够好。有一个顽固的区间约为0.14,其中相对误差峰值保持在5.5e-16(大约60亿个测试案例中的每个)并且4.4e-16区间中的数值稍微过多。这是我在10^9个随机挑战测试中迄今为止所得到的相对误差直方图。
-5.5511e-16 | 0
-4.4409e-16 | 704272
-3.3307e-16 | 0
-2.2204e-16 | 157067630
-1.1102e-16 | 0
 0.0000e+00 | 485332167
 1.1102e-16 | 240168741
 2.2204e-16 | 99172390
 3.3307e-16 | 16874505
 4.4409e-16 | 680236
 5.5511e-16 | 59
 6.6613e-16 | 0

 Mean  1.94185e-17   StdDev  1.31764e-16   Entropy 1.29231

熵是比较直方图分布的一个快速评估指标。对于所有值都在一个区间的完美解决方案,熵将为零。
各种近似的系数如下所示:
Pade 0 (1) 1 (x^2) 2 (x^4) 3 (x^6)
Pn 135135 -17325 378 -1
Qn 135135 -62370 3150 28
Pade近似的最大相对误差为1.34e-4,发生在pi/4处。
具有小数系数的Remez在5.5e-16的精度下表现良好,足够快速查看,并且在Excel电子表格中可以正常工作,但由于小数舍入而不精确。
Remez 0 (1) 1 (x^2) 2 (x^4) 3 (x^6)
Pn 133506.920100216 -17127.5484486707 374.700589646508 -1
Qn 133506.920100216 -61629.8551487417 3117.06295917985 -27.8423369154197

Remez完全准确的十六进制浮点表示(迄今为止最好的)

Remez* 0 (1) 1 (x^2) 2 (x^4) 3 (x^6)
Pn 0x1.04c175c5d80a5p+17 -0x1.0b9e319c87430p+14 0x1.76b359d7d3ac0p+8 -1
Qn 0x1.04c175c5d80a5p+17 -0x1.e17bb5d60e518p+15 0x1.85a203c2f84f8p+11 -0x1.bd7a3645ff105p+4
由于特定原因,我需要保持P0=Q0,以便对于非常小的值x,x == tan(x)。我还希望将x^6项的系数之一保持为1.0(节省一次乘法),但如果为了获得16位有效小数而必须做出妥协,那就是它。
这是一个示例实现(使用较短的、稍微不太准确的小数值)。升级为MRE 24/11/23。
// Toy_Pade.cpp : Demonstrates the failing cases err>5.5e-16 and prints out failures
//
#define _USE_MATH_DEFINES
#include <stdio.h>
#include <math.h>

double TanPQ(double x)
{
  const double P[4] = { 133506.920100216, -17127.548448670765,  374.700589646508, -1.0 };
  const double Q[4] = { 133506.920100216, -61629.8551487418,    3117.06295917985, -27.84233691541965 };

  double sumP, sumQ, x2;
  int i;
  sumP = sumQ = 0;
  x2 = x * x;
  for (i = 3; i >= 0; i--)
  {
     sumP = sumP * x2 + P[i];
     sumQ = sumQ * x2 + Q[i];
  }
  return x * sumP / sumQ;
}

double tan87_diff(double x)
// return x86 fptan x forced to the x87 coprocessor or tanl()
{
  long double y,t;
  if (x == 0) return 0;
  t = TanPQ(x);
  #ifdef _M_IX86
  _asm {
         fld qword ptr[x]
         fptan
         fxch st(1)  // save the 1
         fdivr qword ptr[t]
         fsubp st(1), st
         fstp qword ptr[y]
   }
  #else
  y = 1 - t / tanl(x);
 #endif
 return y;
}

int main()
{
  double x, dx, err;
  dx = M_PI / 2000000000;
  x = 0;
  while (x < M_PI / 4)
  {
     err = tan87_diff(x);
     if (fabs(err) > 5.0e-16) printf("%-26.18g %22.14a %g\n", x, x, err);
     x = x + dx;
  }
}

优化之所以困难,是因为在最后阶段有一个平滑的函数,但上面有一片稀疏的尖钉,所以有无数个局部最优解,它们都差不多,只有很少几个好的解。密码学中的LLL方法被认为可以解决这个难题,但我个人的编程能力无法从零开始实现。问题归结为找到一组尾数值,它们的比率具有“神奇”属性,使得它们的比率比任何单个系数更精确。
如果有人能够使用Intel的LLL优化包、Sollya或ARM的Remez包,或者能找到稍微更好的近似解,我会非常感兴趣。我自己尝试过使用ARM的代码和Sollya,但效果不如预期。它离所需的精度非常接近,但还差一点。我也对其他启发式调整的建议很感兴趣,我有一些想法。

我认为如果你希望获得16位数的精确度,你可能需要使用80位的算术和系数。 - undefined
@MartinBrown 顺便说一下,不要使用15个有效十进制数字,而是使用17个或者十六进制。假设十六进制常量是最好的,十进制会引入不必要的误差。d = 133506.920100216; printf("%.17g %a\n", d,d); --> 133506.920100216 0x1.04c175c5d8086p+17,这与0x1.04c175c5d80a5p+17有所不同。 - undefined
1
不清楚你所要求的是否可能。即使你有理想的多项式P和Q,你使用的是“double”浮点数进行计算,所以“sumP”和“sumQ”的最好情况只能正确舍入。它们每个都会有平均约为1/4个ULP或更多的舍入误差,并且在某些位置上可能高达接近1/2个ULP。然后你将它们相除并乘以“x”,产生更多的舍入误差。无论对于P和Q使用什么系数,似乎在定义域中都不太可能没有超过1个ULP的误差。你打算使用某种形式的扩展精度进行计算吗? - undefined
1
@chux 我对这段代码的输入范围有绝对控制。它永远不会接收到超出+/-pi/2的参数。如果我非常需要的话,我的另一个选择是将近似范围缩小到pi/8,并使用另一个if语句将范围0-pi分为3个段,而不是目前的<pi/4和>pi/4。为了确保速度更快,我可能还会尝试这种方法。我对两个非常接近的比较语句有些担心,因为它可能导致流水线停顿。 - undefined
@MartinBrown 使用fma()作为解决方案的可能性是否存在? - undefined
显示剩余10条评论
3个回答

5
我想在范围为-pi/4到pi/4的区间内快速近似tan(x),并且精度要在1个ULP以内。
如果没有使用更宽的类型或通过fma()来增加精度,我认为这是不可能的。 这可能是不可能的。 那么什么是一个可以接受的精度水平呢?1.5?2.0?
另一种选择是使用一个额外的q[]项:
double tan_alt(double x) {
  double g = x*x;
  static const double p[4] = { 1.0, -0.13338350006421960681e0, 0.34248878235890589960e-2, -0.17861707342254426711e-4 };
  static const double q[5] = { 1.0, -0.46671683339755294240e0, 0.25663832289440112864e-1, -0.31181531907010027307e-3, 0.49819433993786512270e-6 };
  double xnum = ((p[3]*g + p[2])*g + p[1])*g*x + x;
  double xden = (((q[4]*g + q[3])*g + q[2])*g + q[1])*g + 1;
  return  xnum/xden;
}

改进的随机测试的最坏情况下的误差小于2.7个最低有效位(ULP)。 这比原始问题(OP)的4.1个ULP误差稍微好一点。 但仍然没有达到OP设定的目标,即小于等于1.0个ULP。
根据OP的相对误差度量为5.0e-16,这里的误差小于3.4e-16。
另一个包含4个p[]和4个q[]项的集合:
double tan_alt4x4(double x) {
  double g = x * x;
  static const double p[4] = {1.0, -0.1282834704095743847e+0, //
      +0.2805918241169988906e-2, -0.7483634966612065149e-5};
  static const double q[4] = {1.0, -0.4616168037429048840e+0, //
      +0.2334485282206872802e-1, -0.2084480442203870948e-3};
  double xnum = ((p[3] * g + p[2]) * g + p[1]) * g * x + x; // f*P(g)
  double xden = ((q[3] * g + q[2]) * g + q[1]) * g + 1; // Q(g)
  return xnum / xden;
}

最差情况发现:优于3.0 ULP

与C库中的tan(x)相比,tanl(x)

t = TanPQ(x);不同,我尝试了t = tan(x);,并得到了最大相对误差(根据OP的代码)为-1.35525e-16。根据我的ULP测试,一个最坏情况的样本是在x:+0.66050441268436411处小于0.84。

以下比较由于我C库中的fma()出现故障而无效。
@njuffamy_tan_poly_cw()相比,tanl(x)

@njuffamy_tan_rat_cw()相比,tanl(x)



第二次努力
引用: 我想要在范围为-pi/4到pi/4的区间内,快速地近似计算tan(x),使其误差在1 ULP以内。
让我们回到基础。
从数学上讲:如果我们使用Taylor级数来计算tan(),在x接近π/4的情况下,我们需要相当多的项才能使误差小于2的53次方的1部分。
下面是一个直接的C语言实现。它不使用任何扩展精度的数学运算,也不使用fma()函数。
我的深度测试(10的9次方个值在[0.7 π/4]范围内)得到了最坏情况:
tan(x:0.78318202430135764) -->
 ref:0x1.fdbc560aa0a813d6p-1,
   f:0x1.fdbc560aa0a80p-1 --> 1.24 ulp

深入测试可能会导致更糟糕的ULP。我预计仍然小于1.3 ULP。OP的目标是小于等于1.0 ULP。
x = 0.7以下,ULP误差始终小于1.0。 候选改进
  • 在第一个循环之后使用psum[1] = pow(x, 3) * tan_coeff[1];仅仅将最坏情况的ULP改进到了1.064。

  • 在第二个循环中使用扩展精度的long double sum将最坏情况的ULP改进到了1.071。我们也可以使用其他技巧来实现更宽的求和计算。

  • 同时使用两者将最坏情况的ULP改进到了0.904。

  • 由于ULP在1.0以下很容易低于0.7,当|x|小于接近0.7的某个阈值时,代码可以在这个级别上进行向量化,使用更快的几个项。

  • “圣杯”是使用一个较低阶的多项式,该多项式定制以最佳逼近tan()。在其他好的答案中,这里以及上面回答了许多这样的缩减集。

// https://www.wolframalpha.com/input?i=taylor+tanx
static const double tan_coeff[] = { //
    1,                      // (1 x)
    0.33333333333333333,    // (1 x^3)/3
    0.13333333333333333,    // (2 x^5)/15
    0.053968253968253968,   // (17 x^7)/315
    0.021869488536155203,   // (62 x^9)/2835
    0.0088632355299021966,  // (1382 x^11)/155925
    0.0035921280365724810,  // (21844 x^13)/6081075
    0.0014558343870513183,  // (929569 x^15)/638512875
    0.00059002744094558598, // (6404582 x^17)/10854718875
    0.00023912911424355248, // (443861162 x^19)/1856156927625
    9.6915379569294503e-05, // (18888466084 x^21)/194896477400625
    3.9278323883316834e-05, // (113927491862 x^23)/2900518163668125
    1.5918905069328965e-05, // (58870668456604 x^25)/3698160658676859375
    6.4516892156554308e-06, // (8374643517010684 x^27)/1298054391195577640625
    2.6147711512907546e-06, // (689005380505609448 x^29)/263505041412702261046875
    1.0597268320104654e-06, // (129848163681107301953 x^31)/122529844256906551386796875
    4.2949110782738059e-07, // (1736640792209901647222 x^33)/4043484860477916195764296875
    1.7406618963571648e-07, //
    7.0546369464009683e-08, //
    2.8591366623052539e-08, //
    1.1587644432798852e-08, //
    4.6962953982309016e-09, //
    1.9033368339312759e-09, //
    7.7139336353590623e-10, //
    3.1263395458920870e-10, //
    1.2670576930305401e-10, //
    5.1351914080393677e-11, //
    2.0812146867700474e-11, //
    8.43484541909434E-12, //
    3.41851408681116E-12, //
    1.38547157429485E-12,
};

#define tan_coeff_N (sizeof tan_coeff / sizeof tan_coeff[0])

double my_tan(double x) {
  double xx = x*x;
  double xpower = x;
  double psum[tan_coeff_N];
  for (size_t i = 0; i < tan_coeff_N; i++) {
    psum[i] = xpower * tan_coeff[i];
    xpower *= xx;
  }

  // Summing from smallest to greatest improves results by about 4 ULP!
  double sum = 0.0;
  for (int i = tan_coeff_N; i-- > 0; ) {
    sum += psum[i];
  }
  return sum;
}

关于“通过随机测试,最坏情况下的误差小于2.6个ULP”的问题:2.6个ULP是随机测试实际发现的最坏情况,还是根据随机测试发现的误差进行的推测?弄清楚存在但你没有看到的误差可能类似于德国坦克问题,尽管概率分布更加复杂。 - undefined
@EricPostpischil 确实这是一个很好的问题,但是好的测试需要时间(在我看来,比tan_alt()要花费更多的时间/精力)。顺便说一句,这是十亿个在double范围内的随机值,范围是+/-pi/4。关键是,对于这个替代方案和OP的最坏情况都使用了相同的方法,结果是2.6比3.9更好。也许更深入的测试会揭示最坏的ULP错误,但我不太愿意认为2.6:3.9的比例会有太大变化。也许以后会有更多信息。 - undefined
改进的测试将产生类似的结果。 - undefined
@EricPostpischil,样本测试案例:x:+0.78536049660000373,+0x1.921ac560ef469p-1 y(高精度):+0x1.fff62051b87eba82p-1 y(tan_alt):+0x1.fff62051b87e9p-1 差异:-2.65674 ulp - undefined
@chux 我回到Hart的表格上生成了一个最佳的3,4次多项式TAN4286,并按照njuffa的建议将其与64位浮点数的C-W评估结合起来。结果相当令人鼓舞,尽管在64位浮点数中我的目标精度似乎难以达到,但这里提出的各种技巧对我帮助很大。谢谢! - undefined

5
简而言之,对于在[-π/4, π/4]范围内的tan(x)的一个精确的四舍五入近似,同时满足提问者提到的所有其他约束条件似乎是不可能的。
1934年,苏联数学家E. Remez发表了一个简单的数值算法,用于计算选择的函数的最小最大多项式逼近的系数[1,2]。随后,各种作者对这个算法进行了扩展,用于有理逼近,到了20世纪80年代初基本上完成了相关工作。Remez算法的"引擎"是解线性方程组。在概念层面上,这是以无限精度进行的,可以得到无限精确的系数。在实际应用中,通常使用任意精度库。例如,在我的工作中,我通常使用1000位的精度。即使如此,一旦超出简单的初等函数,极端病态、退化解和收敛缓慢的情况仍然可能发生。
使用Remez算法的结果与有限精度计算立即产生问题。将计算得到的系数四舍五入到最接近的工作精度表示会破坏极小极大性质,有时甚至严重破坏。在2000年代初,研究人员通过观察到对于一种机器高效的近似,我们基本上是在一个离散格点中寻找最接近Remez解的解决方案,努力修复了这个问题。他们在基本的Remez算法之后添加了一个启发式搜索,作为后处理步骤搜索格点。这对于多项式近似效果很好(通常能够很大程度上恢复极小极大性质),并且通过开源Sollya工具fpminimax命令可以轻松地提供给计算机公众使用。我不知道是否有现成的解决方案适用于有理近似,但我已经有将近十年没有查看Sollya了。
然而,即使在选择最佳的机器精度系数时,我们仍然没有考虑到近似评估也是以有限精度进行的。自2014年以来,我一直在尝试基于模拟退火的启发式搜索来解决这个问题,在单精度近似方面通常取得了有利的结果。至于文献方面,我已经找到了一些初步的探索,但对于这个问题还没有明确的解决方案。
在函数近似的其他部分相等的情况下,有理近似与多项式近似相比有一个显著的缺点:它们不是处理一个多项式累积的舍入误差,而是处理两个多项式加上一个除法的舍入误差。根据我的经验,形式为x*P(x^2)/Q(x^2)的近似值,其中P和Q是多项式,因此最大误差至少为2.5 ulps。常见的解决方法是使用扩展精度数据类型进行中间计算,或者局部使用准扩展精度技术,这在FDLIBM中经常使用。通过以下代数变换可以获得一些缓解:
       (a x² + b) x² + c               (((a+d) x² + (b+e)) x² + (c+f)) x² + g
x + x³ --------------------------  = x --------------------------------------
       ((d x² + e) x² + f) x² + g      ((d x² + e) x² + f) x² + g

这里的左侧被称为Cody-Waite形式的有理逼近,这是在两位推广者之后得名的,尽管据我所知,它最早是由H. Kuki在1960年代开创的。这是有限精度浮点计算中的一个通用设计原则的应用,即如果我们要计算f(x) ≈ x,我们应该将其计算为f(x) = x + g(x),因为只要|g(x)| < |x|/2,计算g(x)时累积的误差将在加法的对齐步骤中(部分地)消除。
因此,对于tan(x)的数值评估,可以使用左侧,而对于任何额外的分析工作,可以使用右侧。通过使用Remez算法中的系数进行大量的随机测试向量进行简单测试,立即可以观察到Cody-Waite排列具有明显的准确性优势。
/* Compute tan(a) on [-PI/4, PI/4]. When testing with 200M random test vectors
   the largest error observed was 2.70173 ulps @ 0.72217781398140413.
*/
double my_tan_rat (double a)
{
    double s, p, q, r;
    s = a * a;
    p =            -0x1.f637dce500dc3p-18; // -7.4836345662662039e-6
    p = fma (p, s,  0x1.6fc6fdce72346p-9); //  2.8059181997587583e-3
    p = fma (p, s, -0x1.06b97be3700b3p-3); // -1.2828347003779114e-1
    p = fma (p, s,  0x1.0000000000000p+0); //  1.0000000000000000e+0
    q =            -0x1.b525b03bf7422p-13; // -2.0844803827815931e-4
    q = fma (q, s,  0x1.7e7b68ac32acfp-6); //  2.3344852656729750e-2
    q = fma (q, s, -0x1.d8b213470d57cp-2); // -4.6161680337112165e-1
    q = fma (q, s,  0x1.0000000000000p+0); //  1.0000000000000000e+0
    r = (p / q) * a;
    return r;
}

/* Compute tan(a) on [-PI/4, PI/4]. When testing with 200M random test vectors
   the largest error observed was 1.28187 ulps @ 0.78428746796240467.
*/
double my_tan_rat_cw (double a)
{
    double s, p, q, r;
    s = a * a;
    p =             0x1.3c10f60c2ddbfp-11; //  6.0284853818520074e-4
    p = fma (p, s, -0x1.f8c1b5a66d60ap-5); // -6.1615805421222872e-2
    p = fma (p, s,  0x1.0000000000000p+0); //  1.0000000000000000e+0
    q =            -0x1.47d5d61f8c6e1p-11; // -6.2529620840320540e-4
    q = fma (q, s,  0x1.1edb29111bc43p-4); //  7.0033226411156099e-2
    q = fma (q, s, -0x1.62855c3ace0adp+0); // -1.3848474162642035e+0
    q = fma (q, s,  0x1.8000000000028p+1); //  3.0000000000000178e+0
    r = fma (p / q, s * a, a);
    return r;
}

显然,多项式逼近也可以从科迪-韦特形式中获得数值上的好处,除了上述有关有理逼近的自然数值优势之外。
/* Compute tan(a) on [-PI/4, PI/4]. When testing with 400M random test vectors
   the largest error observed was 1.07164 ulps @ 0.78163299220007276
*/
double my_tan_poly_cw (double a)
{
    double s, p, r;
    s = a * a;
    p =             2.0850971295510711e-5;  //  0x1.5dd23d63cc663p-16
    p = fma (p, s, -4.4825533934454909e-5); // -0x1.780619e41f284p-15
    p = fma (p, s,  8.9357886641152145e-5); //  0x1.76cb4cd94ed4dp-14
    p = fma (p, s, -2.6283376221895816e-5); // -0x1.b8f63dc5ef7f3p-16
    p = fma (p, s,  1.3645248966084612e-4); //  0x1.1e295f60acb9ep-13
    p = fma (p, s,  2.2241542416199728e-4); //  0x1.d2705f2202d7fp-13
    p = fma (p, s,  5.9505257333405763e-4); //  0x1.37fa9abc268efp-11
    p = fma (p, s,  1.4547608886063283e-3); //  0x1.7d5b59c2a0287p-10
    p = fma (p, s,  3.5922885929182587e-3); //  0x1.d6d9340c7b3e8p-9
    p = fma (p, s,  8.8632192186200755e-3); //  0x1.226e125733432p-7
    p = fma (p, s,  2.1869489605109302e-2); //  0x1.664f49a89604cp-6
    p = fma (p, s,  5.3968253926831529e-2); //  0x1.ba1ba1b46a359p-5
    p = fma (p, s,  1.3333333333414271e-1); //  0x1.11111111182fap-3
    p = fma (p, s,  3.3333333333332782e-1); //  0x1.55555555554f2p-2
    r = fma (p, s * a, a);
    return r;
}

请记住,即使使用大量的随机测试向量也无法提供严格的误差界限,但它可以提供关于各种近似方法相对准确性的合理想法。
对于单参数单精度函数,通过穷举测试可以轻松建立精确的误差界限,但是对于双精度函数来说,这是一项更加困难的任务。误差界限可以通过(经过机械检查的)数学证明来严格建立,但是要得出紧密的上界可能需要专门的数学家数周的时间。在没有数学证明的情况下,可以使用各种启发式方法,如有针对性的随机搜索,以增加对准确性测量的信心,例如每个n个测试向量重新将搜索区间重新定位到迄今为止找到的最大误差位置,并逐渐缩小区间宽度。

1 E. Remes,"Sur un procédé convergent d'approximations successives pour déterminer les polynomes d'approximation",Comptes rendus hebdomaires des séances de l'Académie des sciences198,Jan.-Jun.1934,pp. 2063-2065(presented at the session of June 4, 1934)。

2 Eugène Remes,"Sur le calcul effectif des polynomes d'approximation de Tchebichef",Comptes rendus hebdomaires des séances de l'Académie des sciences199,Jul.-Dec.1934,pp. 337-340(presented at the session of July 23, 1934)。


最大误差:1.07 ulp --> 那是什么x值?试试 x = 0.78100684512866114,我得到的ULP误差是1.26。试图确定我们是否以类似的方式确定ULP。 - undefined
也许在一个狭窄(低精度)的格式上进行一些详尽的搜索是可行的,并且会提示可能的情况。 - undefined
@njuffa 把表达式转化为Cody-Waite形式非常有用,几乎减少了一半的误差。我将总结我进行的各种(慢速)数值模拟的结果,虽然它们太长无法放在评论中,所以不算完全是答案。我离目标越来越近,但我现在相当确信我将不得不接受稍微更高的误差。有两个明显的问题,一个是数值问题,另一个是幕后的代数问题,我希望它是两个立方体的比例的乘积。你的建议在解决数值问题上帮了很大的忙。顺便问一下,你用哪个Remez工具得到了近似值? - undefined
让我们在聊天中继续这个讨论。 - undefined

2
这并不完全回答了我的问题,但总结了我在Eric、@njuffa、@chux和其他人的帮助下取得的进展。
我首先尝试让我的优化器减少5.5e-16区间中的计数。这将计数从60ppb降低到2ppb(由于每个测试需要10^9次函数评估,所以速度相对较慢)。这也使分布更对称,并将均值降低了一个数量级,但方差却增加到了1.38e-16,尽管异常值的数量减少了。
Chux提出增加另一个术语的建议,让我重新审视了Hart表,并实现了与Hart所声称的适用于19.94个十进制数字的3,4结构,如果以适当的精度计算。不幸的是,Hart的表都是按照0-1的比例尺进行缩放的,即作为pi/4的分数。这意味着N阶的项具有系统误差*(pi_64b/pi_256b)^N。而且它们是在IEEE FP之前的,所以你必须重新优化它们以得到合适的结果。下面的初步系数非常好,应该能产生直方图(尽管我仍然希望能稍微改进一下)。我还对它们进行了重新缩放,以便与Chux的解决方案直接进行比较。找到真正的全局最优解非常困难。这是我目前使用3,4多项式和Cody-White评估得到的最好结果。
Poly 0 (1) 1 (x^2) 2 (x^4) 3 (x^6) 4 (x^8)
Pn 13863796.6635676 -1849201.71988148 47481.9479832591 -247.631071978277 0
Qn 13863796.6635676 -6470467.27440402 355798.150975585 -4322.94411363202 6.90686470740337
Pn CW 0 4621265.55452254 -308316.202992326 4075.31304165375 -6.90686470740337
Pn norm 1 -0.133383499827357 3.42488779484454E-03 -1.78617068605039E-05 0
Qn norm 1 -0.466716833160691 2.56638321817414E-02 -3.118153142704550E-04 4.981943168247540E-07
以两个多项式的比率计算的明显方式,得到的直方图与我在问题中发布的那个非常相似。当我们使用C-W形式,并且我能够改进的最佳逼近结果为零误差区间的75%时,情况变得有趣起来。
-3.3307e-16 | 0
-2.2204e-16 | 176464459
-1.1102e-16 | 0
 0.0000e+00 | 750213762
 1.1102e-16 | 64459601
 2.2204e-16 | 8294911
 3.3307e-16 | 567250
 4.4409e-16 | 17
 5.5511e-16 | 0

 Mean  -2.99957e-17   StdDev  9.52211e-17  Entropy 0.742431

均值有点偏离,但这似乎是直方图分布收紧的代价之一。重新计算我的原始3.3近似值以C_W形式也提高了准确性,但代价是多一次乘法运算。原系数对应的直方图以C-W形式进行评估如下所示。
-3.3307e-16 | 0
-2.2204e-16 | 135329031
-1.1102e-16 | 0
 0.0000e+00 | 553769720
 1.1102e-16 | 295299243
 2.2204e-16 | 15601028
 3.3307e-16 | 978
 4.4409e-16 | 0

 Mean  6.20017e-18   StdDev  1.05085e-16 Entropy 1.02306

它的意思明显更好。我不确定为什么。也许是运气吧?我想我只能接受一些异常值,其中一个现在看起来准确而且速度应该足够快了。谢谢大家的帮助,我仍然对改进准确性(或速度)的任何其他建议感兴趣。
这是在一个舍入误差起作用的范围内运行,所以如何对系数进行归一化会重新排列误差曲线,但大部分情况下保持直方图看起来相似。最终的多项式将使x^2n中的一个项重新缩放为单位以节省乘法运算。

啊!在预览模式下,x^8的系数看起来很好,但在最终形式中被截断了。应该分别为6.90686470740337和4.981943168247540E-07。 - undefined
关于“...在5.5e-16的bin中的计数”,我有些困惑。为什么没有以ulps(单位最后一位)来说明这些bin呢?我猜你应该知道相对误差和ulps误差不能互换使用(ulps误差基本上是根据binade缩放的绝对误差)。 - undefined
主要是因为我实际上是根据相对误差而不是最小单位精度(ULP)进行舍入,因为我认为这样更快。 - undefined
1
计算相对误差可能比计算ulp误差稍快,但与计算参考值的成本相比,这种差异是微不足道的。如果目标是1 ulp误差界限,跟踪相对误差的方法似乎无法生成相关数据。相反,可以使用以下方法(我在实践中除了跟踪最大ulp误差之外也使用过)来区分:(1)正确舍入结果的百分比,误差<= 0.5 ulp,(2)忠实舍入结果的百分比,误差<= 1 ulp,(3)误差大于1 ulp的结果的百分比。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接