This talk

  • Design custom math approximation functions
  • Focus
    • Chebyshev polynomials
  • 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
      • iOS & Android
    • Audio plug-ins at Sketch Audio, LLC
  • Spent five years teaching high school math
Ryan Robinson

The plan

  • Motivation
  • Build a \sin x approximation
    • Taylor, Padé
    • Chebyshev polynomials
      • Refinement
  • 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

  • Match f and its first n derivatives at a
p_n(x) = \htmlClass{hl sum c-blue}{\sum_{k=0}^{n}} \htmlClass{hl coef c-blue}{\frac{f^{(k)}(a)}{k!}}\,\htmlClass{hl power c-blue}{(x - a)^k}
  • For \sin x about 0
\htmlClass{hl series c-blue}{\sin x \approx x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!}}

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]

Absolute error

Relative error

ULP distance

Harmonics

Binned max over samples

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)
  • Bring up the denominator
(\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]

Absolute error

Relative error

ULP distance

Harmonics

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

f(x) = \sin x
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

\sin x on [-\pi, \pi]

Looks like a spectrum!

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]

Absolute error

Relative error

ULP distance

Harmonics

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]

Absolute error

Relative error

ULP distance

Harmonics

Scoreboard

Max over every normal float input

Effects of degree

Max absolute error

Max ULP

Speedup over std::sin

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

\tan x \approx P(x)

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)}
  • Python
  • C++

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
  • Binary representation

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

Absolute error

Relative error

ULP distance

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

Absolute error

Relative error

ULP distance

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
    • Exactly 1 in float past

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

Polynomial, clamp 2.80

Padé [7/6], clamp

Sigmoid, clamp 9

Scoreboard: \tanh x

Every normal float

Recap

  • \sin x
    • Taylor, Padé, Chebyshev
    • Error metrics and benchmarks
    • Floating-point considerations
  • \tan x
    • Divide out the poles
  • \log_2 x and 2^x
    • Bit hacking, in float and double
  • \tanh x
    • Sigmoid structure

Lessons

  • The toolkit
    • Padé approximants
    • Chebyshev polynomials
    • Remez algorithm
  • Know your function
    • Zeros, poles, asymptotes
  • 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