Decimal to binary fraction conversion is the mathematical process of translating a base-10 fractional value into a base-2 sequence of bits representing negative powers of two. In a real circuit, this conversion dictates the exact step size, quantization error, and raw register values you must write to a microcontroller’s DAC (Digital-to-Analog Converter) or PWM (Pulse Width Modulation) timer to achieve a specific analog voltage or duty cycle. If you misunderstand this translation, your 50% duty cycle might actually output 49.8%, or your PID control loop will suffer from floating-point drift that causes motor oscillation.

The Math: A Worked Numeric Example

To convert the integer portion of a decimal number to binary, you repeatedly divide by 2. But for the fractional portion, the algorithm flips: you repeatedly multiply by 2 and record the integer carry (the 1 or 0 that shifts left of the decimal point).

Let’s convert the decimal fraction 0.6875 to binary. This is a common target when setting a specific bias voltage on an op-amp reference or a 68.75% PWM duty cycle for a DC motor.

Step-by-Step Conversion of 0.6875:
  • Step 1: 0.6875 × 2 = 1.375. Record the 1. Keep the 0.375.
  • Step 2: 0.375 × 2 = 0.75. Record the 0. Keep the 0.75.
  • Step 3: 0.75 × 2 = 1.5. Record the 1. Keep the 0.5.
  • Step 4: 0.5 × 2 = 1.0. Record the 1. The fraction is now 0.0, so we stop.

Reading the recorded bits top-to-bottom, 0.6875 in decimal is exactly 0.1011 in binary.

What this means for your hardware registers:
If you are writing this value to an 8-bit DAC register (which accepts values from 0 to 255), you must align this binary fraction to an 8-bit integer space. The binary fraction 0.1011 shifted into an 8-bit integer format becomes 10110000 (padding with zeros on the right). In decimal, 10110000 is 176. If you write 176 to an 8-bit DAC with a 5V reference, your output will be (176 / 255) × 5V = 3.450V, which perfectly matches 0.6875 × 5V (3.4375V) within the DAC's quantization limits.

Where You Meet Decimal to Binary Fraction Conversion in Practice

You rarely do this math by hand on the bench, but your microcontroller does it millions of times a second. Here is where fractional binary math directly impacts your physical wiring and code:

1. PWM Duty Cycle Registers (ESP32 LEDC)

When you use the ESP32’s LEDC peripheral to drive a PWM signal, you set the duty cycle using a hardware register. If your timer is configured for 10-bit resolution, the maximum value is 1023. If you want a 33.3% duty cycle (0.333 in decimal), you cannot just pass 0.333 to the C API. You must convert the fraction to the integer register space: 0.333 × 1023 = 340. The hardware then translates 340 into the binary fraction required to toggle the pin high for exactly 33.3% of the period.

2. I2C DACs (e.g., MCP4725)

The popular MCP4725 12-bit DAC accepts I2C commands to set an analog voltage. It uses a 12-bit register (0 to 4095). If your algorithm calculates a target voltage of 2.15V on a 3.3V rail, the decimal fraction is 2.15 / 3.3 = 0.6515. Converting this to the 12-bit integer space yields 0.6515 × 4095 = 2668. The MCP4725 internally converts 2668 to a binary fraction to switch its internal resistor ladder.

3. ADC Quantization Error

When an ADC reads an analog voltage, it performs the reverse operation: binary fraction to decimal. A 10-bit ADC reading a 3.3V signal has a step size (LSB) of 3.3V / 1024 = 0.00322V. Any voltage between 1.000V and 1.003V will yield the exact same binary output. Understanding the binary fraction limits of your ADC tells you exactly how much noise floor you must filter out in hardware before the signal hits the pin.

The Quantization Trap: Common Confusions

There are two massive traps that trip up hobbyists and junior firmware engineers when dealing with binary fractions.

Confusion #1: Dividing instead of multiplying.
People commonly confuse fractional conversion with integer conversion. To convert decimal integers to binary, you divide by 2 and record the remainder. To convert decimal fractions, you multiply by 2 and record the integer carry. Mixing these up yields completely inverted bitstreams.

Confusion #2: Assuming all decimal fractions terminate in binary.
This is the root cause of the infamous floating-point drift in Arduino and ESP32 code. In base-10, the fraction 0.1 is clean and terminating. But in base-2, 0.1 is a repeating, infinite fraction: 0.0001100110011... Because microcontrollers use the IEEE 754 standard for 32-bit floats, they must truncate this infinite sequence. This is why 0.1 + 0.2 == 0.3 evaluates to false in C++. The truncated binary fractions don't perfectly sum to the truncated binary fraction of 0.3. When this math is used to increment a PWM duty cycle in a tight loop, the error accumulates, causing visible flickering in LED arrays or audible whining in motor drivers.

Decision Tree: Handling Fractions in Embedded Firmware

When writing firmware that interacts with analog hardware, you must decide how to handle the math between your high-level logic (which uses decimals/floats) and your hardware registers (which demand binary integers). Use this decision path to choose your approach:

Condition / Requirement Recommended Approach Implementation Example
Simple UI inputs (e.g., user sets 50% brightness via serial) Standard Float-to-Int Cast uint16_t pwm = (uint16_t)(user_percent * 10.23);
High-speed PID control loops or DSP filtering Fixed-Point Integer Math (Q-format) Scale all inputs by 2^16; use 32-bit integer math; bit-shift down at output.
Precision voltage synthesis (e.g., lab power supply control) Pre-calculated Integer Lookup Tables Map exact decimal voltages to DAC hex values in a const uint16_t array.
Accumulating small fractional steps over time (e.g., ramping a motor) Bresenham-style Error Accumulation Track the integer remainder and add it to the next step to prevent drift.
The Default Recommendation:
If you are unsure which method to pick, always use integer scaling (Fixed-Point Math). Never pass a raw float or double directly into a hardware register API. Always multiply your decimal fraction by the maximum register value (e.g., 255 for 8-bit, 4095 for 12-bit) and cast the result to an unsigned integer (uint16_t or uint32_t) before writing to the peripheral. This eliminates floating-point drift at the hardware boundary and executes drastically faster on microcontrollers lacking a hardware FPU (Floating Point Unit), like the base Arduino Uno (ATmega328P).

FAQ: Binary Fractions and Hardware Registers

Why does my ESP32 PWM output 49.9% when I ask for 50%?
Because of binary fraction quantization. If your ESP32 LEDC timer is set to an 8-bit resolution (0-255), 50% requires a value of 127.5. Since the register only accepts integers, it truncates to 127. The output becomes 127 / 255 = 49.80%. To fix this, increase the LEDC timer resolution to 10-bit or 12-bit, which provides finer binary fraction steps (e.g., 50% of 1023 is 511.5, truncating to 511 yields 49.95%, much closer to ideal).

Can I use floating-point math on an Arduino Uno?
You can, but the ATmega328P lacks a hardware FPU. Every floating-point multiplication or division is handled by software libraries, consuming hundreds of clock cycles and bloating your flash memory. By converting your decimal fractions to scaled integers (e.g., representing 1.5V as 1500 mV), you use the native hardware ALU, speeding up execution by an order of magnitude.

How do I convert a repeating binary fraction back to a physical voltage?
You don't. The DAC hardware inherently truncates the repeating fraction at the limit of its resistor ladder (e.g., 8, 12, or 16 bits). Your job as the designer is to calculate the quantization error (1 LSB) and ensure your analog low-pass filter or downstream op-amp circuit can tolerate that specific voltage ripple.

Understanding decimal to binary fraction conversion bridges the gap between abstract math and physical electrons. By respecting the binary limits of your hardware registers and defaulting to integer scaling in your firmware, you eliminate phantom bugs, reduce CPU overhead, and achieve precise analog control.