Skip to content

Second-order sections

Split a high-order IIR filter into biquads, pair its poles with nearby zeros, order the sections, and see why one long direct form breaks.

Before this21.1 · 6 more
Chapter 21 · Lesson 2 of 4

First, the picture

A long IIR filter is safest run as a chain of small filters, each with two poles and two zeros. Below, an order-5 filter is split into its sections. Watch each section’s gain peak on its own, while the whole filter never rises above 0 dB.

Pair each pole pair with its nearest zeros

20.6's elliptic filter of order 5 as a first-order section and two biquads (SciPy zpk2sos, 'nearest').

Five poles and five zeros of the elliptic filter, to be split into sections.

section 2 peak
not yet
section 3 peak
not yet
Pairing
0.00 / 15.00 s
Describe this picture

Two panels for 20.6’s elliptic filter of order 5 as a first-order section and two biquads (SciPy’s zpk2sos, “nearest”). The plane has the real part across and the imaginary part up, both from −1.2 to 1.2, and the unit circle; the poles are crosses and the zeros circles. As each section forms, a dashed loop encloses its roots, labelled 1, 2 or 3; section 1’s label also gives its gain, 0.022. The gain panel plots the gain from −60 to 25 dB against frequency from 0 to 4000 Hz. Each section’s gain is a thin dashed curve, and the whole filter is a thick solid curve labelled “whole filter”. Two readouts, the peaks of sections 2 and 3, read “not yet” until their section is drawn.

The clip lasts 15 s. It starts with the five poles and five zeros of the elliptic filter. Then section 3’s loop draws: the pole pair nearest the circle, 0.953∠±45.6°, with the zeros nearest it, at ±57.4°. At 4.75 s section 3 peaks at 14.00 dB, at 1000 Hz. Next comes section 2: the next pair, 0.806∠±35.0°, with the remaining zeros, at ±76.4°. Then section 1 forms: the real pole, the zero at −1, and the gain. At 9.75 s section 2 peaks at 15.72 dB, and section 1, with the gain 0.022, stays below −17 dB. Last the whole filter draws: the sections’ dB add up to it, and it never exceeds 0 dB. After the clip two buttons in a group named “Pairing” choose the nearest or the swapped pairing; the choice is kept in the link, as pairs.p.

Pair each pole pair with its nearest zeros

In Filter structures (21.1) we wired up one biquad in several ways, and saw that a long filter can be a cascade of them. This page asks how to split a long filter into biquads, and why it is worth doing. I’ll use the elliptic filter of Choosing FIR or IIR (20.6), which has order 5: five poles and five zeros.

Transfer functions, poles & zeros (16.3) wrote such a filter as a gain KK times one factor for each root:

H(z)=K ∏k(1−zkz−1)∏k(1−pkz−1).H(z)=K\,\frac{\prod_k(1-z_kz^{-1})}{\prod_k(1-p_kz^{-1})}.

Multiply the factors back together in small groups, and each group is a small filter. Run the small filters one after another, and by 21.1 the cascade is H(z)H(z) again.

Which groups? A complex pole p=rejθp=re^{j\theta} comes with its mirror p∗=re−jθp^*=re^{-j\theta}, and their two factors multiply to

(1−pz−1)(1−p∗z−1)=1−(p+p∗)z−1+pp∗z−2=1−2rcos⁡θ z−1+r2z−2.\begin{aligned} &(1-pz^{-1})(1-p^*z^{-1})\\ &\quad=1-(p+p^*)z^{-1}+pp^*z^{-2}\\ &\quad=1-2r\cos\theta\,z^{-1}+r^2z^{-2}. \end{aligned}

Both coefficients are real. In the same way, a pair of zeros e±jθe^{\pm j\theta} on the unit circle gives 1−2cos⁡θ z−1+z−21-2\cos\theta\,z^{-1}+z^{-2}.

One pole pair over one zero pair is the biquad of Audio equalisers and biquads (20.5). Used as one piece of a longer filter, I call it a second-order section. Each section has its own five coefficients:

b0+b1z−1+b2z−21+a1z−1+a2z−2.\frac{b_0+b_1z^{-1}+b_2z^{-2}}{1+a_1z^{-1}+a_2z^{-2}}.

An odd order leaves one real pole over. It goes with a real zero into a first-order section, (b0+b1z−1)/(1+a1z−1)(b_0+b_1z^{-1})/(1+a_1z^{-1}). So our order-5 filter becomes one first-order section and two biquads.

Which zeros go with which poles? That choice is the pairing. The arrows of 16.3 show why it matters. Where the walk round the circle passes close to a pole, that pole’s arrow is short and the gain climbs. A zero close by has a short arrow too, and it pulls the gain back down.

So give each pole pair the zeros nearest it, and no section climbs very high. It is like seating dinner guests next to the people they know, so that no table gets loud. SciPy’s zpk2sos(z, p, k, pairing='nearest') does it in this order:

  1. Take the pole pair nearest the unit circle, and give it the two zeros nearest to it.
  2. Repeat with the poles and zeros that are left.
  3. A real pole takes a real zero, and the gain KK goes into the first section’s bb‘s. (SciPy’s k is our KK.)

The cascade gives the same H(z)H(z) in any order, but SciPy still picks one. This is the ordering: the sections with poles nearest the circle go last. I’ll show why further down.

The picture at the top of the page splits 20.6’s filter this way, one section at a time, and draws each section’s gain. Notice that both biquads peak far above 0 dB on their own. Their peaks sit at different frequencies, and section 1 stays below −17 dB everywhere. Gains in a cascade multiply, so their dB add (20.5), and the sum never rises above 0 dB.

After the clip, choose the swapped pairing with its button: the loops redraw around the new groups, and the section curves change. The pole pair nearest the circle now gets the far zeros, and its section peaks at 22.99 dB; the other drops to 10.17 dB. The whole filter is unchanged.

The arrows of 16.3 explain the jump. At 1000 Hz the walk is at 45° on the circle, next to the poles of section 3. Their own zeros are 0.217 away, and the far zeros 0.541, two and a half times as far. Swapped, nothing near the poles pulls the gain down, and the peak rises by 9.0 dB.

Now the ordering. It does not change H(z)H(z), but it changes the signals between the sections. In SciPy’s order the largest gain from the input to the end of section 1 is −17.43 dB, to the end of section 2 it is −4.11 dB, and to the output 0 dB. The sharp sections come last, so the signal reaches them already made smaller by the others.

Run the same three sections in the opposite order. After two of them, a 981 Hz sine comes out 24.47 dB louder than it went in, about 17 times. In floating point that does no harm. Finite word-length effects (21.3) shows what a stored value that large does in fixed point.

Raise the order until the direct form breaks

Why not skip the sections and run the whole filter as one difference equation, a direct form of 21.1? On paper the answer is the same. But a computer rounds every coefficient, and the direct form is far more sensitive to that rounding.

Most computers store a number in floating point: a sign, the first 24 binary digits of the number, and a power of 2 that says where the point goes. This is single precision, float32 in NumPy, and many DSP chips and audio plug-ins use it. 24 binary digits are about 7 decimal digits.

Rounding to single precision is the rounding to a grid of levels of Quantization & noise (11.1), with one difference: the step grows with the number. The error is at most 2−24≈6×10−82^{-24}\approx6\times10^{-8} of the number’s size. NumPy’s usual float64, double precision, keeps 53 binary digits, about 16 decimal digits.

My test filter is a Butterworth low-pass from Analog prototype filters (20.1), with cutoff 100 Hz at fs=8f_s=8 kHz. 100 Hz is a small slice of the circle, 4.5°, so all its poles crowd near z=1z=1.

I write the direct form’s denominator as A(z)=1+a1z−1+⋯+aNz−NA(z)=1+a_1z^{-1}+\dots+a_Nz^{-N}, with NN the order. For order 6 the coefficients are 1, −5.696561, 13.528499, −17.144063, 12.227073, −4.653138 and 0.738190. Put z=1z=1, and A(1)A(1) is their sum. By the factor form, it is also the product of the six small numbers 1−pk1-p_k.

So the coefficients, up to 17 in size, add up to A(1)=2.0×10−7A(1)=2.0\times10^{-7}. Rounding −17.14406324 to single precision gives −17.14406395, a change of 7.1×10−77.1\times10^{-7}. That one change is larger than A(1)A(1) itself, so every digit of the coefficients matters.

The instrument below rounds the coefficients of this filter to single precision in two ways, as one direct form and as second-order sections. Then it finds the poles of what was rounded. The filtering itself runs in double precision, so the rounded coefficients are the only cause of what you see.

Raise the order until the direct form breaks

Butterworth low-pass, cutoff 100 Hz at f_s = 8 kHz, coefficients rounded to single precision: one direct form against second-order sections.

Order 2: both versions put the poles where they belong, radius 0.9460.

order N
2
direct form: largest
0.9460
sections: largest
0.9460
0.00 / 13.00 s
Describe this picture

Two panels for a Butterworth low-pass, cutoff 100 Hz at fs=8f_s=8 kHz, with its coefficients rounded to single precision: one direct form against second-order sections. The first is the plane close to z=1z=1, the real part from 0.85 to 1.1 and the imaginary part from −0.12 to 0.12, with an arc of the unit circle. The true poles are faint crosses, the direct form’s rounded poles are crosses, and the sections’ rounded poles are small rings. The second is the impulse response h[n]h[n], from −0.04 to 0.04, against the sample nn from 0 to 400: the direct form is a solid line and the sections a dashed line. Where the direct form leaves the panel, an open triangle at the edge is labelled “grows without bound”. The readouts are the order NN and each version’s largest pole radius, to four decimals, followed by “unstable” above 1.

The clip lasts 13 s, with a blank caption while the order changes. At order 2 both versions put the poles where they belong, radius 0.9460. At 5.75 s, order 4: rounding moves the direct form’s poles a little, 0.9702 instead of 0.9704, and its gain at 0 Hz is off by 0.065 dB, while the sections’ poles stay at 0.9704. At the end, order 6: the direct form’s rounded coefficients have a pole at 1.0417, outside the circle, and its impulse response grows past 1 by n=141n=141; the same poles as three sections, rounded the same way, stay at 0.9799. After the clip a slider named “Order N” takes the even orders from 2 to 12; the arrow keys move it by 2, and Home and End jump to 2 and 12. Its value reads like “6: direct form unstable”. At orders 2, 4 and 6 the caption is the clip’s; at order 8 it reads “Order 8: the direct form’s largest pole is 1.1368, unstable; the sections’ 0.9848.” From order 8 that pole lies beyond the plane’s edge at 1.1, so it is drawn as an open triangle on the edge, pointing outward and labelled with its radius. The order is kept in the link, as break.N.

Notice that the rings stay on the true poles at every order. The direct form’s crosses drift at order 4 and leave the circle at order 6.

After the clip, set the order yourself with the slider. Try 12: the direct form’s largest pole is 1.5037, while the sections’ is 0.9898.

Order 6 breaks in a way you can see in the numbers. After rounding, the coefficients add up to A(1)=−1.19×10−6A(1)=-1.19\times10^{-6}: the sign has flipped. Multiply A(z)A(z) by z6z^6, and you have a polynomial in zz. Far out on the real axis it is positive, and at z=1z=1 it is now negative.

So it crosses zero somewhere beyond z=1z=1. That crossing is a real pole outside the circle, the 1.0417 of the readout. By Stability and causality (16.4), one pole outside the circle is enough to make the filter unstable.

Why the direct form is so sensitive

Write P(z)=zNA(z)P(z)=z^NA(z), the same polynomial in powers of zz, so that P(z)=∏k(z−pk)P(z)=\prod_k(z-p_k). Rounding the coefficients adds a tiny polynomial to PP. Near one root pip_i, PP is close to a straight line, and its slope there is the product of the distances from pip_i to the other N−1N-1 roots.

The root moves by about the change in PP at pip_i divided by that slope. When the roots crowd together, that slope is tiny. For order 6, the pole nearest the circle is 0.039, 0.075, 0.106, 0.131 and 0.149 away from the other five, and the product is only 6.0×10−66.0\times10^{-6}.

The rounding changes PP there by 1.3×10−61.3\times10^{-6}, so the estimate is a move of about 0.2. That pole is 0.020 inside the circle, ten times closer than that. The estimate only holds for small moves, but it says clearly that the pole will not stay put.

A biquad’s two roots are moved only by its own two coefficients, and the slope at each root is the single distance to its mirror. In the order-6 design that distance is 0.149 for the section nearest the circle, so its poles move by 2.1×10−72.1\times10^{-7}.

Double precision has 29 more binary digits, so it lasts longer, but it breaks too. The same Butterworth design as one direct form keeps every pole inside the circle up to order 11. At order 12 its largest pole is at 1.0241, with no rounding to single precision at all.

So I keep an IIR filter above order 2 or 3 as sections, from the design to the filtering. SciPy’s butter documentation says the same: the [b, a] form can have numerical problems “even for N >= 4”, and it recommends second-order sections, output='sos'.

The maths behind it · ill-conditioned eigenvalues

The roots of a polynomial are the eigenvalues of its companion matrix, which is how NumPy’s roots finds them. When a non-symmetric matrix has eigenvalues crowded together, they can be badly conditioned: a tiny change in the entries moves them far. Wilkinson’s polynomial is the classic case. Factoring into biquads is choosing a better-conditioned way to store the same filter.

Worked example

Let’s redo the page’s numbers, by hand where we can and with SciPy where we can’t.

1. The elliptic filter in sections. ellip(5, 0.4455, 40, 1000, fs=8000, output='sos') returns one row per section, with b0,b1,b2,1,a1,a2b_0,b_1,b_2,1,a_1,a_2. Each row is one section of the form above, and the filter is their product, H(z)=H1(z)H2(z)H3(z)H(z)=H_1(z)H_2(z)H_3(z).

SectionNumerator coefficientsDenominator coefficients
1, first-order0.022446, 0.022446, 01, −0.665916, 0
21, −0.470440, 11, −1.319331, 0.649273
31, −1.076389, 11, −1.332241, 0.907593

Check section 3 against its roots. The poles have radius 0.952677 and angles ±45.636°, so 2rcos⁡θ=1.3322412r\cos\theta=1.332241 and r2=0.907593r^2=0.907593. The zeros are at ±57.439°, so 2cos⁡θ=1.0763892\cos\theta=1.076389.

2. The dB add up. At 0 Hz, z=1z=1, so each section’s gain is the sum of its bb‘s over the sum of its aa‘s. Section 1 gives 0.044892/0.334084=0.1343740.044892/0.334084=0.134374, which is −17.43 dB. Section 2 gives 1.529560/0.329942=4.63581.529560/0.329942=4.6358, 13.32 dB, and section 3 gives 0.923611/0.575352=1.60530.923611/0.575352=1.6053, 4.11 dB.

The sum is −17.43+13.32+4.11=0.00-17.43+13.32+4.11=0.00 dB, the gain of the whole filter at 0 Hz.

3. The swapped pairing at 0 Hz. Section 2 now has section 3’s zeros: 0.923611/0.329942=2.79930.923611/0.329942=2.7993, 8.94 dB. Section 3 has section 2’s zeros: 1.529560/0.575352=2.65851.529560/0.575352=2.6585, 8.49 dB. Together they give 17.43 dB, the same as 13.32+4.1113.32+4.11.

So at 0 Hz the swap is hard to see. The cost is near 1 kHz, where the swapped section 3 peaks at 22.99 dB instead of 14.00.

4. Coefficient sizes. The order-6 Butterworth denominator at 100 Hz has coefficients up to 17.14 in size, and the order-12 one up to 681. Rounding to single precision changes each by at most 2−24≈6×10−82^{-24}\approx6\times10^{-8} of its size. Here the largest such change is 4.8×10−84.8\times10^{-8} at order 6 and 5.3×10−85.3\times10^{-8} at order 12.

5. Where double precision breaks. With no rounding to single precision, the direct form’s largest pole is 0.9876 at order 10 (true 0.9878) and 0.9944 at order 11 (true 0.9889). At order 12 it is 1.0241, and the impulse response first passes 1 at n=504n=504.

6. In SciPy. Design straight into sections with butter(6, 100, fs=8000, output='sos'), and filter with sosfilt(sos, x), which runs each section as the transposed direct form of 21.1. If you have poles and zeros, zpk2sos(z, p, k) pairs and orders them.

tf2sos(b, a) also exists, but it starts from the polynomial, so it cannot undo rounding already done to bb and aa. Applied to the single-precision coefficients of order 6, it gives sections with the same pole at 1.0417.

Where you’ll meet this

Running IIR filters as cascades of biquads is the usual practice. SciPy has sosfilt and sosfiltfilt, and MATLAB has zp2sos and sosfilt. Arm’s CMSIS-DSP library for microcontrollers has the arm_biquad_cascade functions. The Web Audio API’s BiquadFilterNode is one section, and you chain several for a higher order.

The parametric equalisers of 20.5 are already cascades, one biquad for each band. How many bits each coefficient and stored value needs is Finite word-length effects (21.3). Running the sections sample by sample, against a clock, is Real-time processing (21.4).

The maths behind it · sensitivity and error propagation

Rounding errors in the coefficients behave like small random changes. How far a small change moves a root is a sensitivity, a derivative: one over the slope of PP at the root. Error-propagation formulas use the same quantity to carry measurement error through a calculation.

Reference card

QuantityRuleNotes
Second-order sectionb0+b1z−1+b2z−21+a1z−1+a2z−2\dfrac{b_0+b_1z^{-1}+b_2z^{-2}}{1+a_1z^{-1}+a_2z^{-2}}one pole pair, one zero pair
Pole pair re±jθre^{\pm j\theta}1−2rcos⁡θ z−1+r2z−21-2r\cos\theta\,z^{-1}+r^2z^{-2}real coefficients
Odd orderone first-order sectiona real pole, a real zero
Pairingpole pair nearest the circle with the zeros nearest it, repeatzpk2sos(pairing='nearest')
Orderingpoles nearest the circle lastSciPy’s default
Cascadegains multiply, dB addsosfilt
Single precision24 binary digits, about 7 decimal digitserror at most 2−242^{-24} of the size
Why sectionsa direct form’s crowded roots move far when roundedorder 6 breaks in single precision here, order 12 in double

End of lesson 21.2

Where to go next.

Phasorium
LibraryEvery lesson, in order

Parts

About Phasorium
Look