HuntsvilleEngineers mark
HE Reference Shelf — huntsvilleengineers.com
The Reference Shelf · Discrete Math & Computation

Fixed-point arithmetic

Doing real-number math with plain integers by agreeing, ahead of time, where the binary point sits — the number in the register is always the real value times a fixed power of two.

Also known as: Q format · scaled integers · fractional binary

The formula

The whole system is one convention:

x = K / 2ⁿ

The stored integer K represents the real value x; the scale factor 2ⁿ is fixed at design time and never stored anywhere. It lives in the engineer's head and, if the team is disciplined, in a comment.

The standard shorthand is Q notation. A signed Qm.n number has one sign bit, m integer bits, and n fractional bits:

range = −2ᵐ … +2ᵐ − 2⁻ⁿ        resolution = 2⁻ⁿ

The workhorse is Q15 (TI notation, 16-bit word, m = 0, n = 15): range −1.0 to +0.999969482421875, step size 2⁻¹⁵ ≈ 3.052·10⁻⁵. One warning up front — ARM documentation counts the sign bit inside m, so TI's Q15 is ARM's Q1.15. Same bits, different label. [1]

Picking the format is a two-line design calculation:

m ≥ ceil( log₂(max |x|) )        — integer bits to cover the range
n = word length − 1 − m          — whatever is left becomes precision

Every bit of range you reserve is a bit of resolution you gave up. That trade is the entire discipline.

Addition is just integer addition, provided both operands share the same Q format. Multiplication is where the bookkeeping lives:

Qa.b × Qc.d = Q(a+c).(b+d)     — fractional bits add

So a Q15 × Q15 product is a 32-bit value with 30 fractional bits, and you shift right by 15 to get back to Q15. Concretely: 0.5 → 16384, 0.75 → 24576, product = 402 653 184, >> 15 gives 12288 = 0.375. Exact, no floating-point unit involved.

Where you meet it

  • Motor-control firmware review. A field-oriented control loop on a Cortex-M0 or a C2000-class part runs current, speed, and position loops entirely in Q formats — phase currents in Q15 of full-scale amps, angles as a 16-bit turn (65 536 counts per revolution, and wrap-around comes free from two's-complement overflow). When the review board asks "what's the scaling on this gain?", the answer had better be a Q format, not a shrug.
  • DSP code on the bench. FIR and IIR filter coefficients from a MATLAB design get quantized to Q15 before they touch the chip. The filter that was stable in double precision can ring, bias, or limit-cycle after quantization — you find out with a spectrum analyzer on the output, not from the design tool.
  • Reading raw instrument data. A 16-bit ADC register, a 4–20 mA loop scaled to counts, a resolver-to-digital converter word — all of it is fixed-point whether the datasheet says so or not. The "counts to engineering units" line in every test procedure is a Q-format conversion.
  • The interface control document. When two boxes exchange a 16-bit field defined as "LSB = 0.01 degrees," that is fixed-point arithmetic across an organizational boundary. Most telemetry decom errors trace to one side misreading the agreed scale.

How it works

Fixed point trades dynamic range for speed, determinism, and exactness. A Q15 add takes one integer instruction and one clock. There is no rounding surprise in addition — values on the grid stay on the grid — and execution time never depends on the data, which is why control-loop timing analysis is easier than with a shared FPU.

The costs come in four flavors, and every one has bitten a real program:

Overflow. The range is hard-walled. In plain two's complement, 32767 + 1 wraps to −32768 — a full-scale positive command becomes a full-scale negative one, which in a motor loop means the torque command reverses. DSPs provide saturating arithmetic (clamp at the rail instead of wrapping) precisely because wrap-around in a control loop is catastrophic and clipping is merely bad. The one multiplication that can overflow even Q15 × Q15 is (−1) × (−1): (−32768)² >> 15 = 32768, one count past the maximum, so saturating multipliers special-case it to 32767.

Quantization. Each stored value carries an error up to ±q/2 where q = 2⁻ⁿ. Treated as uniform noise, its variance is q²/12, which for a full-scale sine gives the familiar SNR ≈ 6.02·N + 1.76 dB — about 98 dB for 16 bits, 74 dB for 12. Two subtleties: truncation (a bare right shift) is not rounding — it biases every result by an average of q/2 toward negative, and in a feedback loop that DC creep accumulates. And decimal constants don't exist on the binary grid: 0.1 in Q15 is 3277/32768 = 0.100006…, close but not equal, forever.

Headroom bookkeeping. Every add can grow the result by one bit; summing 256 Q15 samples needs log₂ 256 = 8 guard bits. This is why fixed-point DSPs carry 32- or 40-bit accumulators behind 16-bit data paths: do the whole dot product at full width, shift once at the end. Shifting after every operation throws away precision for no benefit.

Division and everything nonlinear. Fixed-point divide is slow and hemorrhages precision, so production code avoids it: divide-by-constant becomes multiply-by-reciprocal plus a shift, and sin, sqrt, and atan2 become lookup tables with interpolation or a CORDIC iteration. A Q15 sine table with 256 entries plus linear interpolation is accurate to a few counts, costs a few hundred bytes, and runs in constant time — which is why it is still the standard move in commutation code forty years after the first DSP shipped.

The mistake people make is losing track of the scale factor — adding a Q12 to a Q15 without aligning them, or reusing Ariane 4 scaling on an Ariane 5 trajectory. The compiler will not catch it; the numbers are all just integers to the toolchain. The units and the binary point live in documentation and discipline, which is exactly why teams either build a scaled-integer type system or eventually wish they had.

A note on tooling: standard C has no fixed-point type — everything is int16_t and shift operators, which is why the scale factor is invisible to the compiler. Some embedded toolchains support the _Fract/_Accum types from the ISO TR 18037 embedded-C extension, and MATLAB and Simulink will generate and verify Q-format code directly, which on a big program is worth real money in avoided scaling bugs.

Rule of thumb for the design review: fixed point wins when the signal range is known and bounded (sensor outputs, unit-amplitude coefficients, angles), the processor has no FPU or the loop rate leaves no cycles to spare, or bit-exact reproducibility across builds and processors is a requirement. Floating point wins when dynamic range is wide or unknown. Plenty of flight code uses both — floats for guidance upstream, Q15 where the PWM gets written.

History

The history runs backwards from what you'd guess: floating point came first. Konrad Zuse's Z3, running on 2 600 telephone relays in Berlin in May 1941, already used 22-bit binary floating point [2][3]. Five years later, Burks, Goldstine, and von Neumann looked at that idea for the IAS machine and rejected it. Their 1946 report argued that a floating point wastes memory that "could otherwise be used for carrying more digits per word," and that the human time spent arranging scale factors "is a very small percentage of the total time we will spend in preparing an interesting problem" [4]. So the first generation of stored-program machines did fixed-point arithmetic, and programmers spent the early 1950s doing exactly the scaling drudgery von Neumann had waved off — by most accounts it was their single biggest burden, and it drove the industry to hardware floating point within a decade [5].

Fixed point never left the embedded world, though, and it came roaring back on April 8, 1983, when Texas Instruments introduced the TMS32010 — a $500 chip that multiplied two 16-bit fixed-point numbers in 200 ns and became the ancestor of the DSP family inside modems, disk drives, and half the defense electronics of the era [6][7]. TI's documentation conventions for that family are where the Qm.n notation engineers still write in code comments was standardized [1].

The cautionary chapter came on June 4, 1996. Ariane 5's maiden flight reused inertial-reference software scaled for Ariane 4's trajectory; 37 seconds in, a horizontal-velocity value exceeded what a 16-bit signed integer could hold, the unprotected 64-bit-float-to-integer conversion raised an operand error, both redundant units shut down, and the vehicle broke up [8][9]. The inquiry board's report is the shortest, clearest argument ever published for treating scale-factor assumptions as requirements: the arithmetic was fine — the range assumption around it was ten years stale.

Related tools

  • /tools/adc-resolution — counts, LSB size, and full-scale range: the front door where analog signals become fixed-point integers
  • /tools/snr-enob — the 6.02·N + 1.76 dB quantization-noise relationship, worked both directions
  • /tools/db-converter — for translating quantization noise floors and headroom margins into dB

Sources

  1. https://en.wikipedia.org/wiki/Q_(number_format)
  2. https://en.wikipedia.org/wiki/Z3_(computer)
  3. https://mathshistory.st-andrews.ac.uk/Biographies/Zuse/
  4. https://www.cs.unc.edu/~adyilie/comp265/vonNeumann.html
  5. https://gab.wallawalla.edu/~curt.nelson/cptr380/textbook/history/section_3.11.pdf
  6. https://spectrum.ieee.org/chip-hall-of-fame-texas-instruments-tms32010-digital-signal-processor
  7. https://en.wikipedia.org/wiki/TMS320
  8. http://sunnyday.mit.edu/nasa-class/Ariane5-report.html
  9. https://people.computing.clemson.edu/~steve/Spiro/arianesiam.htm

Written by HE in our own words from the cited sources — engineering judgment included, your stamp still required. All entries →

★ The Reference Shelf

Reading is free. The shelf is for cardholders.

Your library card is an email address: pin it to your shelf, print the card, take the FE/PE quick-reference pack, read the Huntsville history. The shelf remembers what you reach for.

Already on the list? Enter with your subscribed email →