graph of the error function'a bakıldığında, işlevin menşe hakkında simetrik olduğunu gözlemler; Bu nedenle, yaklaşımlar önemsiz olarak pozitif yarı düzlem ile sınırlandırılabilir. Ayrıca, grafik, x = 1 yakınında bir yerde sınırlama olmak üzere iki bölüme ayrılabilir. Kökeni daha yakın olan segmentte, hata fonksiyonu oldukça doğrusal ve dikey olarak domine edilirken, segmentte orijinden daha uzakta yatay olarak baskındır ve üssel olarak çürüyen modadaki birliği asimptotik olarak ele alır.
Makul bir sonuç, x * p (x) 'in basit bir polinom yaklaşımı sıfıra yakın segment için uygunken, diğer segmentin 1 - exp (x * q (x)) ile iyi bir şekilde yakınlaştırılacağıdır. q (x) ikinci bir polinom yaklaşımıdır. fonksiyonunun Taylor serisinin genişlemesine dayanarak, orijine yakın segmentin tahmini, aslında x * p (x) formunda olmalıdır. İlk görev, iki bölüm arasındaki geçiş noktasını bulmaktır. Bunun için, 0.875'teki geçiş noktasıyla başlayan ve onu adım adım 1.0'a doğru ilerleyen deneysel bir yaklaşım kullandım. Geçiş noktasının her bir değeri için, sıfır ile Remez algorithm arasındaki geçiş noktası arasındaki hata fonksiyonuna bir başlangıç minimax yaklaşımı oluşturdum. Bunlar daha sonra, FMA işlemlerinin kullanılmasıyla Horner şeması aracılığıyla polinomun değerlendirilmesine dayanan katsayı değerleri için arama arama yöntemini kullanarak doğruluk bakımından daha da geliştirildi.Geçiş noktasının arttırılması, elde edilen yaklaşık maksimum hata 1 ulp'yi aşana kadar tekrarlandı. Bu süreçle, iki yaklaşım segmenti arasındaki optimal sınırı 0.921875 olarak belirledim. Bu, yaklaşık 1 ulp'den az bir maksimum hataya ulaşan bir x * p (x) yaklaşımıyla sonuçlanır. Remez algoritması ayrıca polinom q (x) için bir başlangıç minimax hesaplaması sağlamak amacıyla da kullanılmıştır. Q (x) ile içsel olarak içersinde kullanılan yaklaştırmanın, hatalarının birbirini güçlendirdiği veya dengelediği şekilde exp() ile arasında çok az bir etkileşim olduğu açıktır. Bu, q (x) katsayıları için en iyi seçimin, exp()
'un uygulanmasına sıkı sıkıya bağlı olacağı anlamına gelir ve başlangıç katsayı kümelerinin sezgisel arıtmasının bir parçası olarak dikkate alınmalıdır. Bu nedenle, kendimi herhangi bir kütüphane uygulamasından ayırmak için kendi uygulamamı expf()
kullanmaya karar verdim. Asgari gereklilik olarak, expf()
'un kendisinin tam anlamıyla yuvarlatılmış olması gerekir ve tam olarak ne kadar sıkı olduğunu belirlemeye çalışmadım, ancak bu yaklaşımın çalışması için biraz daha sıkı bir hataya uyması gerekir. Bu durumda, kendi uyguladığım expf()
, yeterli olduğu ortaya çıkarılan 0.87161 ulps'lik bir hata sağlar.
exp kullanımı() gerektiren kademeli yavaş yol olduğu
, bir komut-aşamalı paralellik ve böylece performansını arttırmak için polinom q (X) alt sıra terimleri için
Estrin's scheme kullanmayı tercih ettik. Bunu yapmanın doğruluğu üzerindeki etkisi ihmal edilebilir. Doğruluk nedenleriyle, Horner şeması polinomun yüksek mertebeli terimleri için kullanılmalıdır. Her iki polinomun en az anlamlı katsayılarına baktığımızda, her ikisinin de 1.128 olduğunu görüyoruz ... ve bu nedenle, katsayıyı (1 + 0.128 ...) içine bölerek, doğruluğu biraz iyileştirebiliriz. x ile son çarpma. Sonunda,
erff()
numaralı bir uygulamayı gerçekleştirebildim. Burada iki kod yolunun her biri, daha yüksek hassasiyetli bir referansa karşı kapsamlı bir testle belirlendiği üzere, 1 ulp'nin altında bir maksimum hataya ulaştı. Bu nedenle işlev sadakatle yuvarlanır. FMA kullanımı bu başarının önemli bir bileşenidir. Alet zincirine bağlı olarak, aşağıda gösterilen C99 kodu, olduğu gibi vektör edilebilir olabilir veya her ikisi de, her iki kod yolu, sonuçta istenen sonucun bir seçimiyle eşzamanlı olarak hesaplanacak şekilde manuel olarak modifiye edilebilir. Yüksek performanslı matematik kitaplıkları,
my_expf()
özel işlevim yerine kullanılması gereken
expf()
'un bir vektörleştirilebilir sürümünü içerir.
expf()
'un tüm vektör edilmiş uygulamaları, yeterli bir doğruluk sunmaz ve diğerleri için, polinom q (x) içindeki katsayıların ayarlanması gerekli olacaktır.
expf()
'un özel bir sürümü kullanılıyorsa, burada yaptığım gibi, muhtemelen bir ldexpf()
numaralı aramayı performans nedenleriyle daha hızlı makine özel kodla değiştirmek isteyebilir.
/* Compute exponential base e. Maximum ulp error = 0.87161 */
float my_expf (float a)
{
float c, f, r;
int i;
// exp(a) = exp(i + f); i = rint (a/log(2))
c = 0x1.800000p+23f; // 1.25829120e+7
r = fmaf (0x1.715476p+0f, a, c) - c; // 1.44269502e+0
f = fmaf (r, -0x1.62e400p-01f, a); // -6.93145752e-1 // log_2_hi
f = fmaf (r, -0x1.7f7d1cp-20f, f); // -1.42860677e-6 // log_2_lo
i = (int)r;
// approximate r = exp(f) on interval [-log(2)/2,+log(2)/2]
r = 0x1.6a98dap-10f; // 1.38319808e-3
r = fmaf (r, f, 0x1.1272cap-07f); // 8.37550033e-3
r = fmaf (r, f, 0x1.555a20p-05f); // 4.16689515e-2
r = fmaf (r, f, 0x1.55542ep-03f); // 1.66664466e-1
r = fmaf (r, f, 0x1.fffff6p-02f); // 4.99999851e-1
r = fmaf (r, f, 0x1.000000p+00f); // 1.00000000e+0
r = fmaf (r, f, 0x1.000000p+00f); // 1.00000000e+0
// exp(a) = 2**i * exp(f);
r = ldexpf (r, i);
// handle special cases
if (!(fabsf (a) < 104.0f)) {
r = a + a; // handle NaNs
if (a < 0.0f) r = 0.0f;
if (a > 0.0f) r = 1e38f * 1e38f; // + INF
}
return r;
}
/* compute error function. max ulp error = 0.99993 */
float my_erff (float a)
{
float r, s, t, u;
t = fabsf (a);
s = a * a;
if (t > 0.921875f) { // 0.99527 ulp
r = fmaf (0x1.222900p-16f, t, -0x1.91d2ccp-12f); // 1.72948930e-5, -3.83208680e-4
u = fmaf (0x1.fd1336p-09f, t, -0x1.8d6300p-06f); // 3.88393435e-3, -2.42545605e-2
r = fmaf (r, s, u);
r = fmaf (r, t, 0x1.b55cb0p-4f); // 1.06777847e-1
r = fmaf (r, t, 0x1.450aa0p-1f); // 6.34846687e-1
r = fmaf (r, t, 0x1.079d0cp-3f); // 1.28717512e-1
r = fmaf (r, t, t);
r = my_expf (-r);
r = 1.0f - r;
r = copysignf (r, a);
} else { // 0.99993 ulp
r = -0x1.3a1a82p-11f; // -5.99104969e-4
r = fmaf (r, s, 0x1.473f48p-08f); // 4.99339588e-3
r = fmaf (r, s, -0x1.b68bd2p-06f); // -2.67667342e-2
r = fmaf (r, s, 0x1.ce1a46p-04f); // 1.12818025e-1
r = fmaf (r, s, -0x1.8126e0p-02f); // -3.76124859e-1
r = fmaf (r, s, 0x1.06eba6p-03f); // 1.28379151e-1
r = fmaf (r, a, a);
}
return r;
}
"r = 1,0f - r" den hemen önce üst dalda nasıl bir hata var? Yani, birisi yukarıdaki işlevi "erf" yerine "erfc" yerine tamamlayıcı hata işlevi verecek şekilde uyarladıysa, sonuç hala sadakatle yuvarlanır mı? –
Yukarıdaki polinom q() olarak adlandırılan bu yaklaşım, erfcf() 'nin hesaplanması için uygun değildir. Erff (4.0f) == 1.0f' olduğu için, burada sadece [0.921875, 4] 'de doğru olan bir yaklaşıma ihtiyacımız var, ve üs hatası açısından, sağa yakın olarak bile, çok hassas olmak zorunda bile değil. son-nokta. Erfc() için, doğru bir şekilde hesaplanması gereken, çok daha zor bir sorun oluşturan uzun bir "kuyruk" var. Bu zamana kadar, sadık bir şekilde toplanmış ama hızlı bir şekilde nasıl hesaplanacağı konusunda bir fikrim yok. – njuffa
1) Bu kodu kullandığımda, bir lütuf vermek zorundayım 2) 'Float' için en iyi sonucu hesaplamak için kullanılan ayarın, 'çifte' (belki de 'uzun çifte') en iyi sonuçları elde etmek için ayarlanması ayrıca gönderilebilir. – chux