A binary fraction is a base-2 numerical representation of a value less than one, using negative powers of two (0.5, 0.25, 0.125) positioned to the right of a binary point. While desktop applications rely on hardware floating-point units (FPUs) to handle decimals, embedded microcontrollers often lack an FPU or suffer severe performance penalties when using software-emulated floats. Understanding binary fractions dictates how you map real-world analog voltages to digital registers, directly changing the exact voltage step size in your DAC outputs and the precision limit of your ADC readings when you bypass floating-point libraries.
The Math: Converting Decimal to Binary Fractions
To use binary fractions in firmware, you need to know how to convert a base-10 decimal into base-2. The standard method for the fractional part is repeated multiplication by 2. Let us look at a worked numeric example converting 0.6875 into a binary fraction.
- Multiply 0.6875 by 2 = 1.375. Record the integer part (1). Keep the fractional part (0.375).
- Multiply 0.375 by 2 = 0.75. Record the integer (0). Keep the fraction (0.75).
- Multiply 0.75 by 2 = 1.5. Record the integer (1). Keep the fraction (0.5).
- Multiply 0.5 by 2 = 1.0. Record the integer (1). The fraction is now 0, so we stop.
Reading the recorded integers from top to bottom, the binary fraction for 0.6875 is 0.1011. You can verify this by summing the negative powers of two: $0.5 (2^{-1}) + 0 (2^{-2}) + 0.125 (2^{-3}) + 0.0625 (2^{-4}) = 0.6875$.
In embedded C, we rarely write '0.1011'. Instead, we shift the binary point to the right and treat it as an integer, a technique known as fixed-point math. If we shift 0.1011 four places to the right, we get the integer 1011 (11 in decimal). We have now represented a fraction using a standard 16-bit or 32-bit integer variable, completely eliminating the need for an FPU.
Where You Meet Binary Fractions in Practice
You will encounter binary fractions whenever you interface digital logic with analog physics. Here are the three most common jobsite and bench scenarios:
1. ADC Step Size and Voltage Mapping
When reading an analog sensor with a 12-bit ADC on an ESP32 referenced to 3.3V, the ADC returns an integer from 0 to 4095. The step size (least significant bit) is $3.3V / 4095$, which equals 0.8058 mV. If your ADC reads 2048, the exact voltage is $2048 \times 0.0008058V$. In binary fraction terms, 2048 out of 4096 is exactly 0.5 (binary 0.1), meaning the input is exactly half the reference voltage (1.65V).
2. PWM Duty Cycle Registers
Microcontroller timers use binary fractions to set PWM duty cycles. If an STM32 timer has a 16-bit auto-reload register (max value 65535), setting a 37.5% duty cycle requires calculating $0.375 \times 65535 = 24575$. Notice that 0.375 is a clean binary fraction: $0.25 + 0.125$, or 0.011 in binary. Because it terminates cleanly in base-2, the hardware timer can achieve this exact duty cycle without jitter or rounding errors.
3. Digital Signal Processing (DSP) Filters
When writing a low-pass FIR filter on a Cortex-M0 or an Arduino Uno (which lacks a hardware FPU), multiplying floating-point coefficients (like 0.15) takes hundreds of clock cycles. By converting coefficients to binary fractions (Q-format), you replace slow floating-point multiplication with fast integer bit-shifting.
Common Confusions: Binary Fractions vs. Floating-Point vs. BCD
Makers frequently conflate binary fractions with other numerical formats. Clearing this up prevents catastrophic precision bugs in your firmware.
- Binary Fractions vs. IEEE 754 Floating-Point: A binary fraction is strictly a fixed-point representation (the 'mantissa' without an exponent). Floating-point uses a complex structure of sign bit, exponent, and mantissa to handle massive dynamic ranges. Think of a binary fraction like a digital caliper fixed to a 0-100mm range with 0.01mm steps, whereas floating-point is a laser measure that can read 2mm or 2 kilometers, but might lose sub-millimeter precision at long distances.
- Binary Fractions vs. BCD (Binary Coded Decimal): BCD uses 4 bits to represent each base-10 digit (e.g., 0.5 is stored as
0101). Binary fractions use the entire register as a single base-2 mathematical value. BCD is used for driving 7-segment displays; binary fractions are used for math and hardware registers. - The '0.1' Trap: In decimal, 0.1 is a clean fraction. In binary, 0.1 is an infinitely repeating fraction (
0.0001100110011...). This is why0.1 + 0.2 == 0.3evaluates to false in standard floating-point C code. If you need exact decimal tenths (like currency or exact millimeter steps), do not use raw binary fractions; scale your integers (use millivolts instead of volts).
Decision Tree: Choosing the Right Fractional Math for Your MCU
Selecting the wrong math format leads to either bloated code (using floats on an 8-bit AVR) or precision loss (using poorly scaled integers). Use this decision path to pick your implementation.
| Application Scenario | MCU Architecture | Recommended Math Format | Concrete Implementation Pick |
|---|---|---|---|
| Simple ADC voltage display (e.g., LCD readout) | 8-bit (Arduino Uno / ATmega328P) | Integer Scaling (Milli-units) | Read 10-bit ADC, multiply by 5000, divide by 1024. Print with decimal point inserted via string formatting. |
| PID Control Loop / Motor Commutation | 32-bit ARM Cortex-M4 (STM32 / Teensy 4.1) | Q-Format Fixed-Point (Binary Fractions) | Use Q15 format (1 sign bit, 15 fractional bits). Multiply using 32-bit MAC (Multiply-Accumulate) instructions. |
| Audio DSP / FFT / Complex Trigonometry | 32-bit with Hardware FPU (ESP32 / Cortex-M4F) | IEEE 754 Single-Precision Float | Use standard float (32-bit). Ensure compiler flags -mfpu=fpv4-sp-d16 (or equivalent) are active to prevent software emulation. |
| High-Precision GPS / Scientific Logging | Any 32-bit or 64-bit | IEEE 754 Double-Precision | Use double (64-bit). Avoid on ESP8266 (software emulated and extremely slow). |
FAQ: Binary Fraction Edge Cases
Why does my DAC output voltage 'staircase' instead of smoothing out?
A DAC outputs discrete voltage steps based on its binary fraction resolution. An 8-bit DAC on a 5V reference has 256 steps, meaning each LSB (least significant bit) change is roughly 19.5 mV. If your multimeter reads 2.519V and then jumps to 2.538V, you are witnessing the physical manifestation of the binary fraction step size. To smooth this, you must add an analog RC low-pass filter on the output pin, or upgrade to a 12-bit/16-bit DAC (like the MCP4725) which reduces the step size to microvolts.
How do I handle negative binary fractions in C code?
Standard two's complement applies. If you are using a 16-bit signed integer to represent a binary fraction (Q15 format), the range is -1.0 to roughly +0.999. The binary 1000000000000000 represents -1.0. When multiplying two Q15 numbers, the result requires a 32-bit register, and you must right-shift the 32-bit result by 15 bits to restore the binary point to the correct position.
Can I use binary fractions for time delays?
Yes, but it is usually unnecessary. Hardware timers operate on integer clock ticks. If your system clock is 80 MHz, one tick is 12.5 nanoseconds. Instead of using a binary fraction of a second (e.g., 0.0001s), calculate the exact integer tick count ($80,000,000 \times 0.0001 = 8000$ ticks) and load that integer directly into the timer compare register.
Mastering binary fractions bridges the gap between abstract circuit theory and the raw register-level reality of microcontrollers. By treating fractional values as scaled integers or Q-format variables, you eliminate FPU bottlenecks, guarantee deterministic timing in your control loops, and ensure your ADC and DAC mappings are mathematically exact.






