This talk
- Design custom math approximation functions
- Focus
- Also
- Taylor polynomials, Padé approximants
- Tools
- Python & C++
- Claude assistance
About me
- Ryan Robinson
- Independent developer
- Southern Oregon
- Building apps & plug-ins since 2017
- AutoPad
- Audio plug-ins at Sketch Audio, LLC
- Spent five years teaching high school math
The plan
- Motivation
- Build a \sin x approximation
- Taylor, Padé
- Chebyshev polynomials
- Other functions
- \tan x
- \log_2 x, 2^x
- \tanh x
- Along the way
- Error metrics
- Benchmarks
- Floating-point representation
- SIMD compatibility
Problem
- In audio, we use a lot of special math functions
- Oscillators:
sin, cos
- Filters:
tan
- Nonlinearities:
tanh
- Unit conversions:
exp, log
- Standard implementations
- Must handle any input
- Full precision expected
- Performance secondary to other requirements
- Other considerations
- Inlinability
- Vectorizability
Solution
- Find a polynomial that approximates f on [a, b]
- May also use a rational function
- Benefits
- Pure arithmetic
- Can control precision
- Considerations
- Precondition: inputs must stay in [a, b]
\begin{aligned}
p(x) &= p_0 + p_1 x + p_2 x^2 + \cdots + p_n x^n \\[14pt]
R(x) &= \frac{p(x)}{q(x)} = \frac{p_0 + p_1 x + \cdots + p_n x^n}{q_0 + q_1 x + \cdots + q_m x^m}
\end{aligned}
Building a \sin x approximation
Taylor polynomials
Adding terms
Each term improves the fit
f(x) = \sin x
p_1(x) = x
p_3(x) = x - \frac{x^3}{3!}
p_5(x) = x - \frac{x^3}{3!} + \frac{x^5}{5!}
p_7(x) = x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!}
Converges to \sin x
in the limit
From math to code
Taylor polynomial, degree 7
\sin x \approx x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!}
Factor out x
\sin x \approx \htmlClass{plain}{\htmlData{m=t1}{\htmlData{fly=f,drop=1}{x}} \htmlData{m=s3}{{}-{}} \htmlData{m=t3}{\frac{\htmlData{fly=f}{x}^{3}}{3!}} \htmlData{m=s5}{{}+{}} \htmlData{m=t5}{\frac{\htmlData{fly=f}{x}^{5}}{5!}} \htmlData{m=s7}{{}-{}} \htmlData{m=t7}{\frac{\htmlData{fly=f}{x}^{7}}{7!}}}
\sin x \approx \htmlClass{term}{\htmlData{m=t1}{\htmlData{fly=f,drop=1}{x}} \htmlData{m=s3}{{}-{}} \htmlData{m=t3}{\frac{\htmlData{fly=f}{x}^{3}}{3!}} \htmlData{m=s5}{{}+{}} \htmlData{m=t5}{\frac{\htmlData{fly=f}{x}^{5}}{5!}} \htmlData{m=s7}{{}-{}} \htmlData{m=t7}{\frac{\htmlData{fly=f}{x}^{7}}{7!}}}
\sin x \approx \htmlData{m=f}{x} \left(\htmlData{m=t1}{1} \htmlData{m=s3}{{}-{}} \htmlData{m=t3}{\frac{x^2}{3!}} \htmlData{m=s5}{{}+{}} \htmlData{m=t5}{\frac{x^4}{5!}} \htmlData{m=s7}{{}-{}} \htmlData{m=t7}{\frac{x^6}{7!}}\right)
Left with a polynomial in x^2
\sin x \approx x\,P(x^2)
P(u) = 1 - \frac{u}{3!} + \frac{u^2}{5!} - \frac{u^3}{7!}, \qquad u = x^2
From math to code, continued
Rewrite the polynomial for evaluation with Horner's method
P(u) = 1 - \frac{u}{3!} + \frac{u^2}{5!} - \frac{u^3}{7!}
Factor u out of the rest, again and again
P(u) = 1 \htmlClass{plain}{\htmlData{m=s3}{{}-{}} \htmlData{m=t3}{\frac{\htmlData{fly=f1,drop=1}{u}}{3!}} \htmlData{m=s5}{{}+{}} \htmlData{m=t5}{\frac{\htmlData{fly=f1}{u}^{2}}{5!}} \htmlData{m=s7}{{}-{}} \htmlData{m=t7}{\frac{\htmlData{fly=f1}{u}^{3}}{7!}}}
P(u) = 1 \htmlClass{term}{\htmlData{m=s3}{{}-{}} \htmlData{m=t3}{\frac{\htmlData{fly=f1,drop=1}{u}}{3!}} \htmlData{m=s5}{{}+{}} \htmlData{m=t5}{\frac{\htmlData{fly=f1}{u}^{2}}{5!}} \htmlData{m=s7}{{}-{}} \htmlData{m=t7}{\frac{\htmlData{fly=f1}{u}^{3}}{7!}}}
P(u) = 1 + \htmlData{m=f1}{u}\left(\htmlData{m=s3}{-}\htmlData{m=t3}{\frac{1}{3!}} \htmlClass{plain}{\htmlData{m=s5}{{}+{}} \htmlData{m=t5}{\frac{\htmlData{fly=f2,drop=1}{u}}{5!}} \htmlData{m=s7}{{}-{}} \htmlData{m=t7}{\frac{\htmlData{fly=f2}{u}^{2}}{7!}}}\right)
P(u) = 1 + \htmlData{m=f1}{u}\left(\htmlData{m=s3}{-}\htmlData{m=t3}{\frac{1}{3!}} \htmlClass{term}{\htmlData{m=s5}{{}+{}} \htmlData{m=t5}{\frac{\htmlData{fly=f2,drop=1}{u}}{5!}} \htmlData{m=s7}{{}-{}} \htmlData{m=t7}{\frac{\htmlData{fly=f2}{u}^{2}}{7!}}}\right)
P(u) = 1 + \htmlData{m=f1}{u}\left(\htmlData{m=s3}{-}\htmlData{m=t3}{\frac{1}{3!}} \htmlData{m=s5}{{}+{}} \htmlData{m=f2}{u}\left(\htmlData{m=t5}{\frac{1}{5!}} \htmlClass{plain}{\htmlData{m=s7}{{}-{}} \htmlData{m=t7}{\frac{\htmlData{fly=f3,drop=1}{u}}{7!}}}\right)\right)
P(u) = 1 + \htmlData{m=f1}{u}\left(\htmlData{m=s3}{-}\htmlData{m=t3}{\frac{1}{3!}} \htmlData{m=s5}{{}+{}} \htmlData{m=f2}{u}\left(\htmlData{m=t5}{\frac{1}{5!}} \htmlClass{term}{\htmlData{m=s7}{{}-{}} \htmlData{m=t7}{\frac{\htmlData{fly=f3,drop=1}{u}}{7!}}}\right)\right)
P(u) = \htmlClass{hl h4 c-orange}{1 + \htmlData{m=f1}{u}\left(\htmlClass{hl h3 c-orange}{\htmlData{m=s3}{-}\htmlData{m=t3}{\frac{1}{3!}} \htmlData{m=s5}{{}+{}} \htmlData{m=f2}{u}\left(\htmlClass{hl h2 c-orange}{\htmlData{m=t5}{\frac{1}{5!}} + \htmlData{m=f3}{u}\left(\htmlClass{hl h1 c-orange}{\htmlData{m=s7}{-}\htmlData{m=t7}{\frac{1}{7!}}}\right)}\right)}\right)}
Evaluate from the inside out
Implementation
Putting it all together
Implementation, continued
Raising the degree
Measuring error
- Absolute error
- |p(x) - f(x)|
- In dB: 20 \log_{10} |p(x) - f(x)|
- Relative error
- |p(x) - f(x)| \,/\, |f(x)|
- Bits of precision: -\log_2(\text{relative error})
- ULP distance
- |p(x) - f(x)| \,/\, \operatorname{ulp}(f(x))
- How many representable floats from the true value
- Perceptual error
How did we do?
Taylor polynomial, degree 13, in float, on [-\pi, \pi]
Scoreboard
Max over every normal float in [-\pi, \pi]
Measuring speed
Three loops
Random inputs, sorted or unsorted
std::sin from Apple's libm
Estrin's scheme buys back in feedback
Padé approximants
- Set the Taylor series equal to a rational function
{\textcolor{#2a78d6}{c_0 + c_1 x + c_2 x^2 + \cdots}} = \frac{\textcolor{#eb6834}{p_0 + p_1 x + \cdots + p_m x^m}}{\textcolor{#1baf7a}{1 + q_1 x + \cdots + q_n x^n}} + O\!\left(x^{m+n+1}\right)
(\textcolor{#1baf7a}{1 + q_1 x + \cdots + q_n x^n})\,(\textcolor{#2a78d6}{c_0 + c_1 x + \cdots}) = \textcolor{#eb6834}{p_0 + p_1 x + \cdots + p_m x^m} + O\!\left(x^{m+n+1}\right)
- Multiply out and match coefficients
- Linear system
- m + n + 1 equations and unknowns
\begin{alignedat}{2}
\color{#8a8985}{x^0:}&\quad & p_0 &= c_0 \\
\color{#8a8985}{x^1:}&\quad & p_1 &= c_1 + q_1 c_0 \\
\color{#8a8985}{x^2:}&\quad & p_2 &= c_2 + q_1 c_1 + q_2 c_0 \\
& & &\;\;\vdots \\
\color{#8a8985}{x^{m+1}:}&\quad & 0 &= c_{m+1} + q_1 c_m + \cdots + q_n c_{m+1-n}
\end{alignedat}
Padé vs Taylor
Padé in the wild
- JUCE's
FastMathApproximations header
sin: Padé [7/6]
tanh: Padé [7/6]
How did we do?
Padé approximant [7/6], in float, on [-\pi, \pi]
Improving worst-case error
- Problem: Taylor and Padé match f at one point
- The error gets large near the edges of the input interval
- Goal: the error spread evenly across [a, b]
- Idea: sample f at N points across the interval
- N points determine a polynomial of degree \le N - 1
- Fit a polynomial through them
- Called an interpolant
- Lagrange interpolation
- The error forms an oscillating curve through f
Equally spaced points
Equally spaced angles
Space the points evenly around a half circle, then project onto the x-axis
Move the points
Chebyshev nodes
Of the first kind
\begin{aligned} x_k &= \cos\theta_k \\[2pt] \theta_k &= \frac{(k + \frac12)\,\pi}{N} \\[7pt] k &= 0, \dots, N - 1 \end{aligned}
Chebyshev polynomials
T_n(\htmlClass{hl theta c-blue}{\cos\theta}) = \htmlClass{hl ntheta c-orange}{\cos(n\theta)}
x position on
unit circle
at
\theta
x position on
unit circle
at
n\theta
T_1(x) = x
T_2(x) = 2x^2 - 1
T_3(x) = 4x^3 - 3x
T_4(x) = 8x^4 - 8x^2 + 1
T_5(x) = 16x^5 - 20x^3 + 5x
Adding Chebyshev terms
Each term improves the fit
Converges to \sin x
on [-\pi, \pi]
Chebyshev series
- Any well-behaved f on [-1, 1]
can be written as
f(x) = \sum_{k=0}^{\infty} a_k\, T_k(x)
- Find coefficients with the DCT
- Signal: the function to approximate
- Spectrum: the Chebyshev coefficients
- Like audio, aliasing is an issue
Implementation
- Find the Chebyshev coefficients
- Convert to powers of x
Chebyshev \sin x
Degree 13 on [-\pi, \pi], oversampled
How did we do?
Chebyshev, degree 13, in float, on [-\pi, \pi]
Factor out the zeros
What the polynomial has to approximate
Zero at -\pi, 0 and \pi
\sin x \approx P(x)
Factor out the zero at 0
\sin x \approx x\,P(x^2)
Factor out the zeros at \pm\pi
\sin x \approx x\,\htmlClass{hl pifactors c-orange}{(x - \pi)(x + \pi)}\,P(x^2)
Let's zoom in
The zero at \pi
\begin{aligned}
\text{Math: } & (x - \pi) \\
\text{Code: } & (x - \mathrm{f32}(\pi))
\end{aligned}
Our curve crosses zero at the wrong place!
Splitting \pi
x - \pi \;\approx\; (x - \pi_{hi}) - \pi_{lo}
Now it crosses zero at \pi
Split \pi in C++
Factored zeros and split π: Colin, "Approximating sin(x) to 5 ULP with Chebyshev polynomials," mooooo.ooo (2017)
How did we do?
Split \pi, degree 13, in float, on [-\pi, \pi]
Scoreboard
Max over every normal float input
Effects of degree
Bonus: turns
\sin 2\pi x for x in [0, 1]
\sin 2\pi x \approx -u\,x\,(x - 1)\,P(u^2)
u = x - \tfrac{1}{2}
- Zeros 0, ½, 1 are exact floats
Rounding out the toolkit
- \tan x: poles
- \log_2 x and 2^x: bit hacking
- \tanh x: asymptotes
Building a \tan x approximation
\tan x
- Zero at 0
- Poles at \pm\pi/2
Dealing with the poles
Factor out the zero
\tan x \approx x\,P(x^2)
Divide out the poles
\tan x \approx \frac{x\,P(x^2)}{(\tfrac{\pi}{2} - x)(\tfrac{\pi}{2} + x)}
Put x in units of \pi
\tan \pi x \approx \frac{x\,Q(x^2)}{(\tfrac{1}{2} - x)(\tfrac{1}{2} + x)}
Implementation
\tan \pi x \approx \frac{x\,Q(x^2)}{(\tfrac{1}{2} - x)(\tfrac{1}{2} + x)}
Scoreboard: \tan \pi x
Every normal float cutoff below Nyquist, at 48 kHz
Bonus: degree 5
Building \log_2 x and 2^x approximations
\log_2 x
- Zero at 1
- Challenges
- No symmetry, no periodicity
- Large domain, large range
Floating-point representation
- Normal floats: scientific notation in base 2
x = \pm\,10^{e}\,m, \quad 1 \le m < 10 \qquad 440 = 10^{2} \times 4.4
x = \pm\,2^{e}\,m, \quad 1 \le m < 2 \qquad 440 = 2^{8} \times 1.71875
The log of a float
x = 2^{e}\,m, \quad 1 \le m < 2
\log_2 x = \log_2 \big(\htmlClass{term}{2^{e}\,m}\big)
\log_2 x = \htmlClass{term}{\log_2 2^{e}} + \log_2 m
\log_2 x = e + \log_2 m
e: an integer, from the exponent bits
\log_2 m: only need to approximate it on [1, 2)
Approximation strategy
- Take e and m from the bits
- \log_2 m: a polynomial on [1, 2)
- Write the polynomial in m - 1
- \log_2 m = u\,P(u), u = m - 1
- P(u) = \dfrac{\log_2 (1 + u)}{u} = \dfrac{\ln (1 + u)}{u \ln 2}
\log_2 x in C++
Degree 7 on [1, 2)
Split the float
std::bit_cast and the IEEE layout
How did we do?
Degree 7 on [1, 2), in float; max over every positive float
Move the octave
Any [c, 2c) will do
x = 2^e\,m, \quad m \in [1, 2)
m \ge 1.5:
\quad m \to m / 2
\quad e \to e + 1 \qquad 2^{e+1}\,\tfrac{m}{2} = 2^{e}\,m
m \in [0.75, 1.5)
Folding at 1.5: David Goldberg, "Fast Approximate Logarithms," eBay Tech Blog (2015)
\log_2 x in C++
Degree 7, folded at 1.5
How did we do?
Folded at 1.5, degree 7, in float; max over every positive float
Scoreboard: \log_2 x
Every positive normal float
Bonus: 2^x in C++
Degree 5 on [-\tfrac{1}{2}, \tfrac{1}{2}]
Building a \tanh x approximation
\tanh x
- Zero at 0
- Asymptotes at \pm 1
Polynomials and rationals run off
Polynomial, degree 9
Fits only on \pm
Padé [7/6]
Passes 1 at
Off by at 9
Further out
It keeps climbing
Build in the asymptote
\htmlClass{hl t1 c-blue}{\tanh x = \frac{\sinh x}{\sqrt{1 + \sinh^2 x}}}
\htmlClass{hl t2 c-blue}{y = \frac{s}{\sqrt{1 + s^2}}, \quad s = x\,R(x^2)}
\htmlClass{hl t3 c-blue}{R(x^2) = 1 + x^2\,Q(x^2)}
Comparison
Scoreboard: \tanh x
Every normal float
Recap
- \sin x
- Taylor, Padé, Chebyshev
- Error metrics and benchmarks
- Floating-point considerations
- \tan x
- \log_2 x and 2^x
- Bit hacking, in
float and double
- \tanh x
Lessons
- The toolkit
- Padé approximants
- Chebyshev polynomials
- Remez algorithm
- Know your function
- Know your requirements
- What is your domain?
- How much accuracy? Does ULP matter?
- Know your floats
- Measure
- Worst-case error
- Realistic DSP scenarios
Acknowledgements
- Lloyd N. Trefethen, Approximation Theory and Approximation Practice
- Jason Sachs, "Chebyshev Approximation…"
- embeddedrelated.com/showarticle/152.php
- Colin, "Approximating sin(x) to 5 ULP…"
- mooooo.ooo/chebyshev-sine-approximation
- David Goldberg, "Fast Approximate Logarithms," parts I–III (eBay)
- Raph Levien, "A Few of My Favorite Sigmoids"
- raphlinus.github.io/audio/2018/09/05/sigmoid.html
- Jatin Chowdhury,
math_approx
- github.com/Chowdhury-DSP/math_approx
Thank you
functions.rygrob.com
github.com/rygrob/fun-with-functions