EmbeddedRelated.com

Fixed Point

Category: Architecture | Also known as: fixed-point arithmetic

Fixed-point arithmetic is a method of representing fractional numbers using integers, where a scaling factor (the position of an implied binary or decimal point) is fixed at compile time rather than stored with each value. It gives developers precise control over numerical range and resolution without requiring a hardware floating-point unit.

In practice

Fixed-point is the default choice for DSP and control work on MCUs that lack an FPU, such as the ARM Cortex-M0/M0+, most 8-bit PIC and AVR devices, and many MSP430 variants. Even on cores with an FPU, fixed-point is sometimes preferred because integer operations have deterministic, single-cycle latency on those pipelines, while floating-point latency varies by operation and operand. The tradeoff is that the developer must track the binary point manually across every operation.

The most common notation is Qm.n (or just Qn on some toolchains), where m bits hold the integer part and n bits hold the fractional part in a signed or unsigned integer register. For example, a Q15 value in a 16-bit signed integer represents numbers in [-1, 1) with a resolution of about 3e-5. Choosing the right Q format for a given algorithm requires understanding both the expected signal range (to avoid overflow) and the required precision (to limit quantization error). The blog post "Data Types for Control & DSP" covers these tradeoffs in detail.

Multiplication is the main pitfall: multiplying two Qn values produces a result in Q2n, so you must right-shift to restore the intended format. On many architectures, a 16x16 multiply produces a 32-bit result, which commonly makes the rescaling step straightforward, though whether the full product is directly accessible depends on the ISA and register width. Dividing the result correctly -- and deciding whether to truncate or round -- is discussed in "Round Round Get Around: Why Fixed-Point Right-Shifts Are Just Fine." Accumulation can silently overflow if intermediate sums exceed the register width, so many DSP-oriented cores (such as the Cortex-M4 and Cortex-M33) include saturating arithmetic instructions (QADD, QSUB) and DSP extensions with wider accumulation support to mitigate this.

PI and PID controllers are a canonical use case in embedded work, because the integrator state naturally requires more integer bits than the proportional path. The two-part series "How to Build a Fixed-Point PI Controller That Just Works" walks through selecting Q formats for each state variable and handling the accumulated error term without overflow. For non-MCU targets such as FPGAs, fixed-point remains common because it maps directly to hardware multiplier widths; "Square root in fixed point VHDL" illustrates this for a specific arithmetic primitive.

Discussed on EmbeddedRelated

Frequently asked

When should I choose fixed-point over floating-point?
Use fixed-point when your MCU has no FPU and software floating-point is too slow or too large, when you need deterministic execution time for a control loop, or when you are targeting an FPGA where you control the hardware multiplier width directly. On Cortex-M4/M7/M33 parts with a single-precision FPU, floating-point is often simpler and fast enough; profile first before switching to fixed-point for performance reasons alone.
What does Q15 actually mean, and how do I convert a real number to it?
Q15 uses a 16-bit signed integer where the implied binary point is 15 places from the right, giving a range of [-1, 1) and a resolution of 1/32768 (~3.05e-5). To convert a real value x to Q15, compute round(x * 32768) and clamp to [-32768, 32767]. To convert back, divide the integer by 32768.0. More generally, for a Qn format multiply by 2^n to encode and divide by 2^n to decode.
How do I handle multiplication without losing precision or overflowing?
When you multiply two Qn 16-bit values, store the product in a 32-bit integer -- the result is in Q2n format. Right-shift by n bits to return to Qn, ideally after rounding (add 1 << (n-1) before shifting) to reduce truncation bias. If your architecture provides a multiply-accumulate instruction with wider accumulation support, such as the SMLAL instruction available on ARMv7-M and related architectures, use it to accumulate multiple products before rescaling.
How do I prevent overflow in an integrator or accumulator?
Use a wider integer type for the accumulator than for the individual terms -- for example, accumulate in 32 bits while inputs are 16-bit Q15 values. Alternatively, use saturating arithmetic where the hardware supports it: Cortex-M4 and M33 have QADD16/QADD32 instructions that clamp at the int min/max instead of wrapping. In software, check for overflow before each add or clamp after, and choose your Q format to give the integrator headroom for the worst-case sum.
Is fixed-point the same as integer arithmetic?
Fixed-point uses integer storage and integer machine instructions, but it is not the same concept. Plain integer arithmetic tracks whole numbers; fixed-point arithmetic uses an implicit scale factor to represent fractions. The distinction is entirely in how the programmer interprets the bits -- the CPU executes the same ADD or MUL instruction either way. This means the compiler gives you no automatic type-checking on Q formats; mismatched formats silently produce wrong results.

Differentiators vs similar concepts

Fixed-point is often contrasted with floating-point. Floating-point (IEEE 754 single, double) stores the binary point position in an exponent field alongside the significand, giving a wide dynamic range with no manual scaling. Fixed-point sacrifices dynamic range for simplicity, predictable bit-exact behavior, and suitability for integer-only hardware. A less common point of confusion is with integer arithmetic proper: integers have no fractional part by definition, while fixed-point uses integers to represent fractional values through an implicit scale -- the hardware sees no difference, but the interpretation and the required overflow/rounding discipline are distinct.