Main Archive Specials Wiki | FAQ Links Submit Forum

 Reasonably accurate/fastish tanh approximationReferences : Posted by FuzzpilzNotes : Fairly obvious, but maybe not obvious enough, since I've seen calls to tanh() in code snippets here. It's this, basically: tanh(x) = sinh(x)/cosh(x) = (exp(x) - exp(-x))/(exp(x) + exp(-x)) = (exp(2x) - 1)/(exp(2x) + 1) Combine this with a somewhat less accurate approximation for exp than usual (I use a third-order Taylor approximation below), and you're set. Useful for waveshaping. Notes on the exp approximation: It only works properly for input values above x, but since tanh is odd, that isn't a problem. exp(x) = 1 + x + x^2/(2!) + x^3/(3!) + ... Breaking the Taylor series off after the third term, I get 1 + x + x^2/2 + x^3/6. I can save some multiplications by using 6 + x * (6 + x * (3 + x)) instead; a division by 6 becomes necessary, but is lumped into the additions in the tanh part: (a/6 - 1)/(a/6 + 1) = (a - 6)/(a + 6). Accuracy: I haven't looked at this in very great detail, but it's always <= the real tanh (>= for x<0), and the greatest deviation occurs at about +/- 1.46, where the result is ca. .95 times the correct value. This is still faster than tanh if you use a better approximation for the exponential, even if you simply call exp. There are probably additional ways of improving parts of this, and naturally if you're going to use it you'll want to figure out whether your particular application offers additional ways of simplifying it, but it's a good start.Code : /* single precision absolute value, a lot faster than fabsf() (if you use MSVC++ 6 Standard - others' implementations might be less slow) */ float sabs(float a) { int b=(*((int *)(&a)))&0x7FFFFFFF; return *((float *)(&b)); } /* approximates tanh(x/2) rather than tanh(x) - depending on how you're using this, fixing that could well be wasting a multiplication (though that isn't much, and it could be done with an integer addition in sabs instead) */ float tanh2(float x) { float a=sabs(x); a=6+a*(6+a*(3+a)); return ((x<0)?-1:1)*(a-6)/(a+6); /* instead of using <, you can also check directly whether the sign bit is set ((*((int *)(&x)))&0x80000000), but it's not really worth it */ }