Converting a decimal fraction to binary is the process of translating a base-10 value between 0 and 1 into a base-2 sequence using negative powers of two. In physical circuits and firmware, this mathematical translation directly dictates your Analog-to-Digital Converter (ADC) quantization steps, Pulse Width Modulation (PWM) duty cycle granularity, and how microcontrollers like the ESP32 allocate memory for floating-point sensor variables. Most makers confuse this radix-point conversion with Binary-Coded Decimal (BCD) or attempt to convert the fraction as if it were an integer, resulting in catastrophic precision loss during PID tuning, sensor calibration, or digital signal filtering.
The Core Algorithm: Multiplying by Two
Unlike integer conversion, which relies on repeated division by 2, converting the fractional part of a decimal number requires repeated multiplication by 2. The algorithm isolates the integer part of the product at each step to form the binary sequence from left to right (most significant bit to least significant bit) immediately following the radix point.
- Step 1: 0.6875 × 2 = 1.375. The integer part is 1. Carry the 0.375 forward.
- Step 2: 0.375 × 2 = 0.75. The integer part is 0. Carry the 0.75 forward.
- Step 3: 0.75 × 2 = 1.5. The integer part is 1. Carry the 0.5 forward.
- Step 4: 0.5 × 2 = 1.0. The integer part is 1. The fractional part is now 0, so we stop.
Reading the integer parts from top to bottom, the binary fraction is 0.1011. You can verify this by summing the negative powers of two: (1 × 2-1) + (0 × 2-2) + (1 × 2-3) + (1 × 2-4) = 0.5 + 0 + 0.125 + 0.0625 = 0.6875.
This method works perfectly for numbers that are exact sums of negative powers of two. However, as we will see in the precision section, many common decimal fractions do not resolve this cleanly.
Where You Meet This in Practice
Understanding binary fractions is not just an academic exercise; it maps directly to hardware registers and peripheral configurations in embedded systems.
ADC Resolution and Step Size
When you call Arduino analogRead() on an ATmega328P, you are using a 10-bit ADC referenced to 5V. The ADC maps the continuous analog voltage into 1024 discrete binary steps. The voltage resolution per step is a decimal fraction: 5V / 1024 = 0.0048828125V. In binary, this step size dictates the least significant bit (LSB) weight of your reading. If you are measuring a thermistor and need 0.01V precision, a 10-bit ADC is mathematically insufficient because its binary fraction step size is larger than your required tolerance.
PWM Duty Cycle Granularity
The Espressif ESP32 LEDC peripheral allows you to set PWM resolution from 1 to 14 bits. If you configure an 8-bit resolution, you have 256 steps (0-255). A 50% duty cycle requires a decimal fraction of 0.5, which maps cleanly to the binary integer 128 (half of 256). But if you need a 33.3% duty cycle (0.333 decimal), the binary hardware register must truncate this to the nearest integer step (85 out of 255), introducing a 0.1% hardware-level error that you must account for in motor control loops.
Precision Limits and Quantization Error
Think of binary fractions like a ruler that only has marks at 1/2, 1/4, 1/8, and 1/16 of an inch. If you need to measure exactly 1/10 of an inch, you have to approximate it to the nearest 1/16 mark, introducing a quantization error. This is the fundamental reality of floating-point math in microcontrollers.
The most notorious example in embedded C++ is the decimal fraction 0.1. When you apply the multiply-by-two algorithm to 0.1, it never reaches a fractional part of zero. It results in an infinitely repeating binary fraction: 0.00011001100110011...
| Decimal Fraction | Exact Binary Representation | Truncated 4-Bit Binary | Decimal Value of Truncated | Quantization Error |
|---|---|---|---|---|
| 0.5 | 0.1 | 0.1000 | 0.5 | 0.0 |
| 0.25 | 0.01 | 0.0100 | 0.25 | 0.0 |
| 0.1 | 0.000110011... (repeating) | 0.0001 | 0.0625 | -0.0375 |
| 0.3 | 0.0100110011... (repeating) | 0.0100 | 0.25 | -0.05 |
Because microcontrollers use the IEEE 754 standard for floating-point numbers (allocating 23 bits for the mantissa in single-precision float variables), the repeating binary fraction of 0.1 is eventually truncated. This is why evaluating 0.1 + 0.2 == 0.3 in Arduino C++ returns false. The binary approximations of 0.1 and 0.2 sum to a binary value that is infinitesimally different from the binary approximation of 0.3. For precise financial or timing calculations in firmware, you must multiply your decimal fractions by 100 or 1000 and use 32-bit integers instead of floats.
Frequently Asked Questions
Why does my decimal fraction to binary conversion repeat infinitely?
A decimal fraction will repeat infinitely in binary if its denominator (when expressed as a fraction in lowest terms) has prime factors other than 2. For example, 0.5 is 1/2 (prime factor 2), so it terminates cleanly as 0.1 in binary. However, 0.1 decimal is 1/10. Because 10 has a prime factor of 5, it cannot be expressed as a finite sum of negative powers of two, resulting in an infinitely repeating binary sequence. This is a mathematical certainty, not a calculation error.
How do I convert a mixed number like 5.625 to binary?
Split the number into its integer and fractional parts, convert them separately, and recombine them. For 5.625, convert the integer 5 using repeated division by 2 to get 101. Then, convert the fraction 0.625 using the repeated multiplication by 2 method outlined above to get 0.101. Combine them at the radix point to yield the final binary number: 101.101.
What is the difference between binary fractions and Binary-Coded Decimal (BCD)?
Binary fractions represent the actual mathematical value using base-2 positional weighting (e.g., 0.101 means 1/2 + 1/8). Binary-Coded Decimal (BCD) is an encoding scheme where each decimal digit is stored in its own 4-bit binary nibble. For example, the decimal number 0.45 in BCD is stored as 0000 . 0100 0101. BCD avoids the repeating-fraction quantization errors of pure binary but requires significantly more memory and specialized arithmetic logic, which is why it is rarely used in modern 32-bit microcontrollers like the STM32 or ESP32, favoring IEEE 754 floats instead.
How does this affect my Arduino analogRead() values?
When you map an analogRead() value (0-1023) to a voltage (0.0-5.0V), you are multiplying an integer by a decimal fraction (5.0 / 1024.0). If you store this result in a standard 32-bit float, the binary truncation of the multiplier introduces a tiny error. While this error (usually around 0.0000001V) is far below the electrical noise floor of an Arduino Uno's PCB, it can cause erratic behavior if you use exact equality checks (==) in your if statements. Always use a small epsilon range (e.g., abs(voltage - 2.5) < 0.01) when comparing floating-point ADC readings in your firmware.






