![](/img/trans.png)
[英]How can I rotate a 2d array by LESS than 90°, to the best approximation?
[英]Best non-trigonometric floating point approximation of tanh(x) in 10 instructions or less
對於沒有內置浮點三角函數的機器,我需要一個相當准確的快速雙曲正切,例如,通常的tanh(x) = (exp(2x) - 1) / (exp(2x) + 1)
公式正在運行需要exp(2x)
的近似值。
所有其他指令,如加法、減法、乘法、除法,甚至 FMA(= MUL+ADD in 1 op)都存在。
現在我有幾個近似值,但在准確性方面沒有一個是令人滿意的。
[評論更新:]
trunc()
/ floor()
的指令可用准確度:±1.2% 絕對誤差, 見這里。
偽代碼(A = 累加器寄存器,T = 臨時寄存器):
[1] FMA T, 36.f / 73.f, A, A // T := 36/73 + X^2
[2] MUL A, A, T // A := X(36/73 + X^2)
[3] ABS T, A // T := |X(36/73 + X^2)|
[4] ADD T, T, 32.f / 73.f // T := |X(36/73 + X^2)| + 32/73
[5] DIV A, A, T // A := X(36/73 + X^2) / (|X(36/73 + X^2)| + 32/73)
准確度:±0.9% 絕對誤差, 見這里。
偽代碼(A = 累加器寄存器,T = 臨時寄存器):
[1] FMA T, 3.125f, A, A // T := 3.125 + X^2
[2] DIV T, 25.125f, T // T := 25.125/(3.125 + X^2)
[3] MUL A, A, 0.1073f // A := 0.1073*X
[4] FMA A, A, A, T // A := 0.1073*X + 0.1073*X*25.125/(3.125 + X^2)
[5] MIN A, A, 1.f // A := min(0.1073*X + 0.1073*X*25.125/(3.125 + X^2), 1)
[6] MAX A, A, -1.f // A := max(min(0.1073*X + 0.1073*X*25.125/(3.125 + X^2), 1), -1)
准確度:±0.13% 絕對誤差, 見這里。
偽代碼(A = 累加器寄存器,T = 臨時寄存器):
[1] FMA T, 14.f, A, A // T := 14 + X^2
[2] FMA T, -133.f, T, T // T := (14 + X^2)^2 - 133
[3] DIV T, A, T // T := X/((14 + X^2)^2 - 133)
[4] FMA A, 52.5f, A, A // A := 52.5 + X^2
[5] MUL A, A, RSQRT(15.f) // A := (52.5 + X^2)/sqrt(15)
[6] FMA A, -120.75f, A, A // A := (52.5 + X^2)^2/15 - 120.75
[7] MUL A, A, T // A := ((52.5 + X^2)^2/15 - 120.75)*X/((14 + X^2)^2 - 133)
[8] MIN A, A, 1.f // A := min(((52.5 + X^2)^2/15 - 120.75)*X/((14 + X^2)^2 - 133), 1)
[9] MAX A, A, -1.f // A := max(min(((52.5 + X^2)^2/15 - 120.75)*X/((14 + X^2)^2 - 133), 1), -1)
有沒有更好的東西可以適合 10 個非三角 float32 指令?
經過大量探索性工作,我得出結論,方法 2 是最有希望的方向。 由於在提問者的平台上除法非常快,因此有理近似很有吸引力。 應該積極利用平台對 FMA 的支持。 下面我展示了 C 代碼,它在七次操作中實現了一個快速的tanhf()
並實現了小於 3.3e-3 的最大絕對誤差。
我使用 Remez 算法計算有理逼近的系數,並使用啟發式搜索將這些系數減少到盡可能少的位,這可能有利於一些能夠將浮點數據合並到通常的立即域中的處理器架構使用浮點指令。
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
/* Fast computation of hyperbolic tangent. Rational approximation with clamping.
Maximum absolute errror = 3.20235857e-3 @ +/-3.21770620
*/
float fast_tanhf_rat (float x)
{
const float n0 = -8.69873047e-1f; // -0x1.bd6000p-1
const float n1 = -8.78143311e-3f; // -0x1.1fc000p-7
const float d0 = 2.72656250e+0f; // 0x1.5d0000p+1
float x2 = x * x;
float num = fmaf (n0, x2, n1);
float den = x2 + d0;
float quot = num / den;
float res = fmaf (quot, x, x);
res = fminf (fmaxf (res, -1.0f), 1.0f);
return res;
}
int main (void)
{
double ref, err, maxerr = 0;
float arg, res, maxerrloc = INFINITY;
maxerr = 0;
arg = 0.0f;
while (arg < 0x1.0p64f) {
res = fast_tanhf_rat (arg);
ref = tanh ((double)arg);
err = fabs ((double)res - ref);
if (err > maxerr) {
maxerr = err;
maxerrloc = arg;
}
arg = nextafterf (arg, INFINITY);
}
arg = -0.0f;
while (arg > -0x1.0p64f) {
res = fast_tanhf_rat (arg);
ref = tanh ((double)arg);
err = fabs ((double)res - ref);
if (err > maxerr) {
maxerr = err;
maxerrloc = arg;
}
arg = nextafterf (arg, -INFINITY);
}
printf ("maximum absolute error = %15.8e @ %15.8e\n", maxerr, maxerrloc);
return EXIT_SUCCESS;
}
考慮到 asker 預算多達 10 次操作,我們可以將分子和分母多項式的次數都增加 1 以實現快速tanhf()
實現,該實現包括九個操作,最大絕對誤差顯着降低:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
/* Fast computation of hyperbolic tangent. Rational approximation with clamping.
Maximum absolute error = 7.01054378e-5 @ +/-2.03603077
*/
float fast_tanhf_rat2 (float x)
{
const float n0 = -9.48005676e-1f; // -0x1.e56100p-1
const float n1 = -2.61142578e+1f; // -0x1.a1d400p+4
const float n2 = -2.33942270e-3f; // -0x1.32a200p-9
const float d0 = 3.41303711e+1f; // 0x1.110b00p+5
const float d1 = 7.84101563e+1f; // 0x1.39a400p+6
float x2 = x * x;
float num = fmaf (fmaf (n0, x2, n1), x2, n2);
float den = fmaf (x2 + d0, x2, d1);
float quot = num / den;
float res = fmaf (quot, x, x);
res = fminf (fmaxf (res, -1.0f), 1.0f);
return res;
}
int main (void)
{
double ref, err, maxerr = 0;
float arg, res, maxerrloc = INFINITY;
maxerr = 0;
arg = 0.0f;
while (arg < 0x1.0p32f) {
res = fast_tanhf_rat2 (arg);
ref = tanh ((double)arg);
err = fabs ((double)res - ref);
if (err > maxerr) {
maxerr = err;
maxerrloc = arg;
}
arg = nextafterf (arg, INFINITY);
}
arg = -0.0f;
while (arg > -0x1.0p32f) {
res = fast_tanhf_rat2 (arg);
ref = tanh ((double)arg);
err = fabs ((double)res - ref);
if (err > maxerr) {
maxerr = err;
maxerrloc = arg;
}
arg = nextafterf (arg, -INFINITY);
}
printf ("maximum absolute error = %15.8e @ %15.8e\n", maxerr, maxerrloc);
return EXIT_SUCCESS;
}
Nic Schraudolph是描述該答案的先前版本使用的指數近似的論文的作者,他提出了以下建議。 它的誤差為 0.5%。
Java 實現(用於便攜式鑽頭):
public class Tanh {
private static final float m = (float)((1 << 23) / Math.log(2));
private static final int b = Float.floatToRawIntBits(1);
private static float tanh(float x) {
int y = (int)(m * x);
float exp_x = Float.intBitsToFloat(b + y);
float exp_minus_x = Float.intBitsToFloat(b - y);
return (exp_x - exp_minus_x) / (exp_x + exp_minus_x);
}
public static void main(String[] args) {
double error = 0;
int end = Float.floatToRawIntBits(10);
for (int i = 0; i <= end; i++) {
float x = Float.intBitsToFloat(i);
error = Math.max(error, Math.abs(tanh(x) - Math.tanh(x)));
}
System.out.println(error);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.