Notes / Data Structures & Algorithms
Fixed-point arithmetic: fractions with integers
The Q format for calculating fractions quickly on an MCU without an FPU. Scaling, shifting after multiplication, and saturation.
A Cortex-M0 or an 8-bit MCU has no floating-point unit (FPU), so a single float multiplication takes tens of clocks. Fixed point adds an agreement to an integer, "the binary point is here", and handles fractions at integer speed.
Q format
Q15 uses the lower 15 bits of a 16-bit integer as the fraction. Real value = integer value / 2¹⁵, so it represents −1.0 up to just under 1.0.
| Real value | Q15 integer |
|---|---|
| 0.5 | 16384 |
| -0.25 | -8192 |
| 0.999 | 32735 |
Multiplication
Multiplying two Q15 values gives Q30, so shift right by 15 bits to bring it back. Hold the intermediate result in 32 bits.
int16_t q15_mul(int16_t a, int16_t b) {
int32_t r = (int32_t)a * b;
return (int16_t)(r >> 15);
}
Cautions
- Overflow: if a sum goes out of range, the sign flips. Use saturating arithmetic, which stops at the maximum.
- Precision: multiplying small values repeatedly loses the low bits. Give generous bits to variables that keep accumulating, such as an integrator.
- Document the scale: record which Q format each variable uses in its name or a comment, to prevent mixing them later.
With an FPU, as on a Cortex-M4, float can be both easier and faster, so choose by looking at the target MCU.