2016-09-29 34 views
4

的(呈指數)縮放互補誤差函數,通過erfcx通常被指定,在數學上被定義爲erfcx(X):= E X ERFC(x)的。它經常發生在物理學和化學的擴散問題中。儘管一些數學環境(如MATLABGNU Octave)提供此功能,但它不在C標準數學庫中,它僅提供erf()erfc()縮放互補誤差函數的精確計算,erfcx()

雖然有可能實現自己erfcx()直接基於數學定義,在一個有限的輸入域這隻作品,因爲在正半平面erfc()下溢中等大小的參數,而exp()溢出,如前所述例如,在this question中。

與C一起使用時,可以修改一些erfcx()開源實現,如Faadeeva package中的開源實現,如this question的響應所指出的那樣。但是,這些實現通常不會爲給定的浮點格式提供完整的準確性。例如,使用測試向量的測試顯示由Faadeeva軟件包提供的最大誤差爲正半平面8.41ulp,負半平面511.68ulp。

準確執行的合理範圍是4 ulps,對應於LA配置文件Intel's Vector Math庫中的數學函數的精度界限,我發現這對於非平凡的數學函數實現來說是一個合理的界限,需要良好的精度和良好的性能。

erfcx()和相應的單精度版本erfcxf()如何在僅使用C標準數學庫並且不需要外部庫的情況下準確實現?我們可以假設C的floatdouble類型被映射到IEEE 754-2008 binary32binary64浮點類型。可以假設硬件支持融合乘加操作(FMA),因爲目前所有主流處理器架構都支持這一功能。

回答

2

我發現迄今是基於以下紙張用於erfcx()實現最好的方法:

MM牧羊犬和JG Laframboise,「切比雪夫逼近的(1 + 2×)EXP(X ) erfc x in 0≤x <∞。「計算,第36卷,第153號,1981年1月,第的數學249-253 (online)

本文提出使得所述經縮放互補誤差函數映射到一個緊界輔助函數是適合於簡單的多項式巧妙變換近似。爲了表現,我已經嘗試過變化的變化,但所有這些都對準確性有負面影響。在變換(x - K)/(x + K)中常數K的選擇與核心近似的精度之間存在着非明顯的關係。我憑經驗確定了與文章不同的「最佳」值。

核心近似和中間結果參數的轉換回到erfcx結果會產生額外的舍入誤差。爲了減輕他們對準確性的影響,我們需要採取補償步驟,我在之前的question & answer regarding 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的最大誤差。

請注意,儘管代碼包含兩個可能較慢操作的分區,但它們以互補的特殊形式出現,因此可以在許多平臺上使用快速互易近似。只要倒數近似值可靠地取整,基於實驗,對精度的影響似乎可以忽略不計。即使是稍大的錯誤,例如在快速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.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; 
}