[英]Accurate computation of scaled complementary error function, erfcx()
通常由erfcx
指定的(指數)縮放互補誤差函數在數學上定義為 erfcx(x) := e x 2 erfc(x)。 它經常出現在物理和化學中的擴散問題中。 雖然某些數學環境(例如MATLAB和GNU Octave )提供了此函數,但 C 標准數學庫中沒有它,它只提供erf()
和erfc()
。
雖然可以直接根據數學定義實現自己的erfcx()
,但這僅適用於有限的輸入域,因為在正半平面erfc()
對於中等幅度的參數會下溢,而exp()
溢出,例如,如本問題所述。
為了與 C 一起使用,可以調整一些erfcx()
開源實現,例如Faadeeva 包中的實現,如對此問題的回答所指出的那樣。 但是,這些實現通常不能為給定的浮點格式提供完全的准確性。 例如,使用 2 32 個測試向量的測試顯示 Faadeeva 包提供的erfcx()
的最大誤差為正半平面中的 8.41 ulps 和負半平面中的 511.68 ulps。
准確實現的合理界限是 4 ulps,對應於英特爾矢量數學庫的 LA 配置文件中數學函數的精度界限,我發現這是需要兩者的非平凡數學函數實現的合理界限良好的准確性和良好的性能。
erfcx()
和相應的單精度版本erfcxf()
如何在僅使用 C 標准數學庫且不需要外部庫的情況下准確實現? 我們可以假設 C 的float
nad double
類型映射到 IEEE 754-2008 binary32
和binary64
浮點類型。 可以假設對融合乘加運算 (FMA) 的硬件支持,因為此時所有主要處理器架構都支持這一點。
到目前為止,我發現的erfcx()
實現的最佳方法基於以下論文:
MM Shepherd 和 JG Laframboise,“(1 + 2 x) exp(x 2 ) erfc x 在 0 ≤ x < ∞ 中的切比雪夫近似。” 計算數學,第 36 卷,第 153 期,1981 年 1 月,第 249-253 頁(在線)
該論文提出了巧妙的轉換,將縮放的互補誤差函數映射到一個緊有界的輔助函數,該函數適合直接的多項式近似。 為了性能,我嘗試了各種變換,但所有這些都對准確性產生了負面影響。 變換(x-K)/(x+K)中常數K的選擇與核近似的精度有不明顯的關系。 我憑經驗確定了與論文不同的“最佳”值。
將參數轉換為核心近似值並將中間結果轉換回erfcx
結果會導致額外的舍入誤差。 為了減輕它們對准確性的影響,我們需要應用補償步驟,我在之前關於erfcf
問答中詳細概述了這些步驟。 FMA 的可用性大大簡化了這項任務。
生成的單精度代碼如下所示:
/*
* Based on: M. M. Shepherd and J. G. Laframboise, "Chebyshev Approximation of
* (1+2x)exp(x^2)erfc x in 0 <= x < INF", Mathematics of Computation, Vol. 36,
* No. 153, January 1981, pp. 249-253.
*
*/
float my_erfcxf (float x)
{
float a, d, e, m, p, q, r, s, t;
a = fmaxf (x, 0.0f - x); // NaN-preserving absolute value computation
/* Compute q = (a-2)/(a+2) accurately. [0,INF) -> [-1,1] */
m = a - 2.0f;
p = a + 2.0f;
#if FAST_RCP_SSE
r = fast_recipf_sse (p);
#else
r = 1.0f / p;
#endif
q = m * r;
t = fmaf (q + 1.0f, -2.0f, a);
e = fmaf (q, -a, t);
q = fmaf (r, e, q);
/* Approximate (1+2*a)*exp(a*a)*erfc(a) as p(q)+1 for q in [-1,1] */
p = 0x1.f10000p-15f; // 5.92470169e-5
p = fmaf (p, q, 0x1.521cc6p-13f); // 1.61224554e-4
p = fmaf (p, q, -0x1.6b4ffep-12f); // -3.46481771e-4
p = fmaf (p, q, -0x1.6e2a7cp-10f); // -1.39681227e-3
p = fmaf (p, q, 0x1.3c1d7ep-10f); // 1.20588380e-3
p = fmaf (p, q, 0x1.1cc236p-07f); // 8.69014394e-3
p = fmaf (p, q, -0x1.069940p-07f); // -8.01387429e-3
p = fmaf (p, q, -0x1.bc1b6cp-05f); // -5.42122945e-2
p = fmaf (p, q, 0x1.4ff8acp-03f); // 1.64048523e-1
p = fmaf (p, q, -0x1.54081ap-03f); // -1.66031078e-1
p = fmaf (p, q, -0x1.7bf5cep-04f); // -9.27637145e-2
p = fmaf (p, q, 0x1.1ba03ap-02f); // 2.76978403e-1
/* Divide (1+p) by (1+2*a) ==> exp(a*a)*erfc(a) */
d = a + 0.5f;
#if FAST_RCP_SSE
r = fast_recipf_sse (d);
#else
r = 1.0f / d;
#endif
r = r * 0.5f;
q = fmaf (p, r, r); // q = (p+1)/(1+2*a)
t = q + q;
e = (p - q) + fmaf (t, -a, 1.0f); // residual: (p+1)-q*(1+2*a)
r = fmaf (e, r, q);
if (a > 0x1.fffffep127f) r = 0.0f; // 3.40282347e+38 // handle INF argument
/* Handle negative arguments: erfcx(x) = 2*exp(x*x) - erfcx(|x|) */
if (x < 0.0f) {
s = x * x;
d = fmaf (x, x, -s);
e = expf (s);
r = e - r;
r = fmaf (e, d + d, r);
r = r + e;
if (e > 0x1.fffffep127f) r = e; // 3.40282347e+38 // avoid creating NaN
}
return r;
}
此實現在負半平面中的最大誤差將取決於標准數學庫的expf()
實現的准確性。 使用英特爾編譯器版本 13.1.3.198 並使用/fp:strict
編譯,我在詳盡測試中觀察到正半平面中的最大誤差為 2.00450 ulps,負半平面中的最大誤差為 2.38412 ulps。 目前我能說的最好的是, expf()
的忠實圓潤實現將導致小於 2.5 ulps 的最大誤差。
請注意,雖然代碼包含兩個可能是緩慢運算的除法,但它們以倒數的特殊形式出現,因此適合在許多平台上使用快速倒數近似。 根據實驗,只要倒數近似值忠實地四舍五入,對erfcxf()
准確性的影響似乎可以忽略不計。 即使是稍大的錯誤,例如在快速 SSE 版本(最大錯誤 < 2.0 ulps)中,似乎也只有很小的影響。
/* Fast reciprocal approximation. HW approximation plus Newton iteration */
float fast_recipf_sse (float a)
{
__m128 t;
float e, r;
t = _mm_set_ss (a);
t = _mm_rcp_ss (t);
_mm_store_ss (&r, t);
e = fmaf (0.0f - a, r, 1.0f);
r = fmaf (e, r, r);
return r;
}
雙精度版本erfcx()
在結構上與單精度版本erfcxf()
,但需要具有更多項的極小極大多項式近似。 這在優化核心近似時提出了一個挑戰,因為當搜索空間非常大時,許多啟發式方法會失效。 下面的系數代表了我迄今為止最好的解決方案,肯定有改進的余地。 使用英特爾編譯器和/fp:strict
構建,並使用 2 32 個隨機測試向量,觀察到的最大誤差在正半平面中為 2.83788 ulps,在負半平面中為 2.77856 ulps。
double my_erfcx (double x)
{
double a, d, e, m, p, q, r, s, t;
a = fmax (x, 0.0 - x); // NaN preserving absolute value computation
/* Compute q = (a-4)/(a+4) accurately. [0,INF) -> [-1,1] */
m = a - 4.0;
p = a + 4.0;
r = 1.0 / p;
q = m * r;
t = fma (q + 1.0, -4.0, a);
e = fma (q, -a, t);
q = fma (r, e, q);
/* Approximate (1+2*a)*exp(a*a)*erfc(a) as p(q)+1 for q in [-1,1] */
p = 0x1.edcad78fc8044p-31; // 8.9820305531190140e-10
p = fma (p, q, 0x1.b1548f14735d1p-30); // 1.5764464777959401e-09
p = fma (p, q, -0x1.a1ad2e6c4a7a8p-27); // -1.2155985739342269e-08
p = fma (p, q, -0x1.1985b48f08574p-26); // -1.6386753783877791e-08
p = fma (p, q, 0x1.c6a8093ac4f83p-24); // 1.0585794011876720e-07
p = fma (p, q, 0x1.31c2b2b44b731p-24); // 7.1190423171700940e-08
p = fma (p, q, -0x1.b87373facb29fp-21); // -8.2040389712752056e-07
p = fma (p, q, 0x1.3fef1358803b7p-22); // 2.9796165315625938e-07
p = fma (p, q, 0x1.7eec072bb0be3p-18); // 5.7059822144459833e-06
p = fma (p, q, -0x1.78a680a741c4ap-17); // -1.1225056665965572e-05
p = fma (p, q, -0x1.9951f39295cf4p-16); // -2.4397380523258482e-05
p = fma (p, q, 0x1.3be1255ce180bp-13); // 1.5062307184282616e-04
p = fma (p, q, -0x1.a1df71176b791p-13); // -1.9925728768782324e-04
p = fma (p, q, -0x1.8d4aaa0099bc8p-11); // -7.5777369791018515e-04
p = fma (p, q, 0x1.49c673066c831p-8); // 5.0319701025945277e-03
p = fma (p, q, -0x1.0962386ea02b7p-6); // -1.6197733983519948e-02
p = fma (p, q, 0x1.3079edf465cc3p-5); // 3.7167515521269866e-02
p = fma (p, q, -0x1.0fb06dfedc4ccp-4); // -6.6330365820039094e-02
p = fma (p, q, 0x1.7fee004e266dfp-4); // 9.3732834999538536e-02
p = fma (p, q, -0x1.9ddb23c3e14d2p-4); // -1.0103906603588378e-01
p = fma (p, q, 0x1.16ecefcfa4865p-4); // 6.8097054254651804e-02
p = fma (p, q, 0x1.f7f5df66fc349p-7); // 1.5379652102610957e-02
p = fma (p, q, -0x1.1df1ad154a27fp-3); // -1.3962111684056208e-01
p = fma (p, q, 0x1.dd2c8b74febf6p-3); // 2.3299511862555250e-01
/* Divide (1+p) by (1+2*a) ==> exp(a*a)*erfc(a) */
d = a + 0.5;
r = 1.0 / d;
r = r * 0.5;
q = fma (p, r, r); // q = (p+1)/(1+2*a)
t = q + q;
e = (p - q) + fma (t, -a, 1.0); // residual: (p+1)-q*(1+2*a)
r = fma (e, r, q);
/* Handle argument of infinity */
if (a > 0x1.fffffffffffffp1023) r = 0.0;
/* Handle negative arguments: erfcx(x) = 2*exp(x*x) - erfcx(|x|) */
if (x < 0.0) {
s = x * x;
d = fma (x, x, -s);
e = exp (s);
r = e - r;
r = fma (e, d + d, r);
r = r + e;
if (e > 0x1.fffffffffffffp1023) r = e; // avoid creating NaN
}
return r;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.