Thats really interesting. I would thank Christian Budde for the plotsmistertoast wrote:It's interesting how you can get close to the shape of the curve but still not match the higher harmonics at all.
Tanh approximations
-
- KVRist
- 194 posts since 2 May, 2007 from Swiss
TAL Software GmbH
https://tal-software.com/
https://tal-software.com/
-
Fire Sledge - Ohm Force Fire Sledge - Ohm Force https://www.kvraudio.com/forum/memberlist.php?mode=viewprofile&u=46
- KVRist
- 121 posts since 2 Nov, 2000 from 404 - Not found
Some times ago I quickly designed a tanh() function approximation :
or a = 1.41645 to minimize the maximum relative error (1.7 %)
I haven't checked the harmonics accuracy. Anyway, derivates are continuous up to the third one. It is 12 times faster than the standard tanh() function and almost 30 times faster with SSE packed data. And I'm sure the polynomial can be greatly refined at a small CPU cost (but I'm too lazy to do it).
- tanh(x) ~ sign(x) * (1 - 1 / (1 + |x| + x^2 + a * x^4))
or a = 1.41645 to minimize the maximum relative error (1.7 %)
I haven't checked the harmonics accuracy. Anyway, derivates are continuous up to the third one. It is 12 times faster than the standard tanh() function and almost 30 times faster with SSE packed data. And I'm sure the polynomial can be greatly refined at a small CPU cost (but I'm too lazy to do it).
Code: Select all
double approx_tanh (double x)
{
const double xa = fabs (x);
const double x2 = x * x;
const double x4 = x2 * x2;
const double poly = xa + x2 + x4;
const double resa = poly / (1 + poly);
const double result = (x < 0) ? -resa : resa;
return (result);
}
Code: Select all
__m128 approx_tanh (__m128 x)
{
const __m128 mask_abs = _mm_load_ps (_mask_abs); // 4x 0x7FFFFFFF
const __m128 one = _mm_load_ps (_one); // 4x 1.0f
const __m128 xa = _mm_and_ps (mask_abs, x);
const __m128 xs = _mm_andnot_ps (mask_abs, x);
const __m128 x2 = _mm_mul_ps (x, x);
const __m128 x4 = _mm_mul_ps (x2, x2);
const __m128 poly = _mm_add_ps (_mm_add_ps (xa, x2), x4);
const __m128 resa = _mm_div_ps (poly, _mm_add_ps (one, poly));
const __m128 result = _mm_or_ps (resa, xs);
return (result);
}
-
Christian Budde Christian Budde https://www.kvraudio.com/forum/memberlist.php?mode=viewprofile&u=25572
- KVRAF
- Topic Starter
- 1538 posts since 14 May, 2004 from Europe
Hi Fire Sledge,
the function generates higher harmonics than necessary for an original tanh. This probably leads to more alias then necessary and such. Comparing both versions the version with 1 instead of 1.41645 performs better from a harmonic point of view.
It's pretty fast as all the approximations based on a rational polynomial function, but not that accurate.
Christian
the function generates higher harmonics than necessary for an original tanh. This probably leads to more alias then necessary and such. Comparing both versions the version with 1 instead of 1.41645 performs better from a harmonic point of view.
It's pretty fast as all the approximations based on a rational polynomial function, but not that accurate.
Christian
-
Fire Sledge - Ohm Force Fire Sledge - Ohm Force https://www.kvraudio.com/forum/memberlist.php?mode=viewprofile&u=46
- KVRist
- 121 posts since 2 Nov, 2000 from 404 - Not found
I did some spectrum measurements this morning and came with a better approximation based on the form I proposed yesterday.
The previous function looked like this:
This is the spectrum of a 2*sin(t) signal passed through the function. The green curve is the original tanh(), and the blue one the approximation.
So I changed the function like this :
The shape in the time domain suffered a bit but the extra-harmonics are better than the first one (same slope, but more than 30 dB of improvement).
EDIT : typo in the formula
The previous function looked like this:
This is the spectrum of a 2*sin(t) signal passed through the function. The green curve is the original tanh(), and the blue one the approximation.
So I changed the function like this :
- tanh(x) ~ sign(x) * (1 - 1 / (1 + |x| + x^2 + 0.66422417311781 * |x|^3 + 0.36483285408241 * x^4))
The shape in the time domain suffered a bit but the extra-harmonics are better than the first one (same slope, but more than 30 dB of improvement).
EDIT : typo in the formula
- KVRAF
- 2554 posts since 4 Sep, 2006 from 127.0.0.1
hi Fire Sledge, i entered some tanh() data into CurveExpertFire Sledge - Ohm Force wrote:I did some spectrum measurements this morning and came with a better approximation based on the form I proposed yesterday.
The previous function looked like this:
This is the spectrum of a 2*sin(t) signal passed through the function. The green curve is the original tanh(), and the blue one the approximation.
So I changed the function like this :giving the following results:
- tanh(x) ~ sign(x) * (1 - 1 / (1 + |x| + x^2 + 0.66422417311781 * |x|^3 + 0.36483285408241 * x^4))
The shape in the time domain suffered a bit but the extra-harmonics are better than the first one (same slope, but more than 30 dB of improvement).
EDIT : typo in the formula
i used only the positive part, from 0 to where it hits 1.0
used your approximitation as a start, and added one more "complication there"
..left CurveExpert to find the perfect coefficients
here's my result:
User-Defined Model: y=(1 - 1 / (1 + x + x^2 + a * x^3 + b * x^4 + c * x^7))
Coefficient Data:
a = 0.58576695
b = 0.55442112
c = 0.057481508
so that's the positive part up to 1.0
the sign(x) and abs(x) things should be used
the input might need clipping so that it doesn't "escape" out of the approximitation.. blah
matches quite close, i can't test the CPU Usage..
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!
irc.libera.chat >>> #kvr
..as long as it has BASS and it's LOUD!
irc.libera.chat >>> #kvr
- KVRAF
- 2554 posts since 4 Sep, 2006 from 127.0.0.1
and a pseudo function..
double tanh_approx2(double x)
{
double xa = abs(x);
double x2 = xa * xa;
double x3 = xa * x2;
double x4 = x2 * x2;
double x7 = x3 * x4;
double res = (1.0 - 1.0 / (1.0 + xa + x2 + 0.58576695 * x3 + 0.55442112 * x4 + 0.057481508 * x7));
return (x > 0 ? res : -res);
}
double tanh_approx2(double x)
{
double xa = abs(x);
double x2 = xa * xa;
double x3 = xa * x2;
double x4 = x2 * x2;
double x7 = x3 * x4;
double res = (1.0 - 1.0 / (1.0 + xa + x2 + 0.58576695 * x3 + 0.55442112 * x4 + 0.057481508 * x7));
return (x > 0 ? res : -res);
}
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!
irc.libera.chat >>> #kvr
..as long as it has BASS and it's LOUD!
irc.libera.chat >>> #kvr