Binary numbers with decimal points—technically called binary points or radix points—are digital representations of fractional values where the point's position is either fixed by the hardware architecture or dynamically scaled by an exponent. In the physical world, we measure 3.3V, 1.25A, or 22.5°C. Inside a microcontroller, there are no physical dots or decimal characters; there are only 1s and 0s. How a processor interprets those raw bits as fractions dictates your code execution speed, memory footprint, and ultimately your choice of silicon. What this changes in a real circuit is your interrupt latency and firmware size: relying on fractional math on a microcontroller without a dedicated hardware Floating Point Unit (FPU) will bloat your flash memory and cause severe timing jitter in fast control loops.

Fixed-Point vs. Floating-Point: The Architecture Table

When you write float voltage = 3.3; in C++, the compiler translates that human-readable decimal into a binary format. The 'binary point' is never actually stored in the MCU's RAM. Instead, the compiler and the Arithmetic Logic Unit (ALU) agree on where the point should be based on the data type you declared. According to standard fixed-point arithmetic principles, you can handle fractions using pure integer math by implicitly shifting the binary point, or you can use the IEEE 754 standard for floating-point representation.

Before writing a single line of math for your next project, review how these formats compare at the hardware level.

Representation Format Hardware Requirement Execution Speed (Typical) Precision Limit Best Application
Integer Scaling (e.g., millivolts) Basic Integer ALU 1–5 clock cycles Limited by bit-width (e.g., 16-bit = ±32,767) Sensor readings, simple UI displays
Fixed-Point (Q-Format) Integer ALU + Bit-shift instructions 5–15 clock cycles Fixed fractional resolution (e.g., 1/256) Motor control PID loops, audio DSP on FPGAs
Floating-Point (IEEE 754 Single) Hardware FPU (Single-precision) 1–3 clock cycles (with FPU) ~7 decimal digits (24-bit significand) GPS calculations, complex physics, 3D graphics
Floating-Point (IEEE 754 Double) Hardware FPU (Double-precision) 100+ cycles (if emulated in software) ~15 decimal digits (53-bit significand) Scientific instrumentation, financial math

A Worked Numeric Example: Converting 5.625 to Binary

To understand how the binary point functions, let's convert the decimal number 5.625 into binary. We split the number into its integer and fractional parts.

1. The Integer Part (5):
5 divided by 2 is 2 remainder 1.
2 divided by 2 is 1 remainder 0.
1 divided by 2 is 0 remainder 1.
Reading the remainders bottom-up, the integer 5 is 101 in binary.

2. The Fractional Part (0.625):
Instead of dividing, we multiply the fraction by 2 and record the integer part of the result.
0.625 × 2 = 1.25 (Record 1, carry 0.25)
0.25 × 2 = 0.50 (Record 0, carry 0.50)
0.50 × 2 = 1.00 (Record 1, carry 0.00 - we are done)
Reading top-down, the fractional part is .101.

Combining them across the binary point, 5.625 in pure binary is 101.101.

The Q-Format Hardware Trick: Processors don't store the dot. If we map 101.101 into a 16-bit register using a Q12.4 format (12 integer bits, 4 fractional bits), we pad it to 0000 0101 1010 0000. The hardware just sees the integer 90 (in decimal). To get the real value back, the software simply divides by 2^4 (16). 90 / 16 = 5.625. This allows an 8-bit or 16-bit microcontroller to perform 'fractional' math using only lightning-fast integer instructions.

Where You Meet This in Practice: Microcontrollers and DSP

You will encounter binary point management the moment you try to read an analog sensor or tune a PID controller. The most common scenario is reading a 10-bit Analog-to-Digital Converter (ADC) on a 5V Arduino Uno (ATmega328P). The ADC returns an integer from 0 to 1023. To get the actual voltage, beginners often write:

float voltage = adc_value * (5.0 / 1024.0);

Because the ATmega328P lacks a hardware FPU, the Arduino float documentation notes that all floating-point math is emulated in software. That single line of code takes roughly 120 to 150 clock cycles to execute. If you are running a 10kHz motor control interrupt service routine (ISR), that float multiplication will consume your entire timing budget and crash your system.

The Professional Fix: Drop the binary point and use integer scaling. If you need millivolts instead of volts, multiply by 5000 instead of 5.0:
int millivolts = (adc_value * 5000) / 1024;
This integer math executes in about 15 clock cycles. You display '3300' on your screen and mentally place the decimal point to read '3.300V'. The binary point exists only in your head and your UI formatting string, keeping the MCU's ALU running at maximum speed.

Conversely, if you upgrade to an ESP32, the silicon includes a dedicated hardware FPU for single-precision (32-bit) floats. On an ESP32, float math executes in 1 or 2 cycles. However, the ESP32's FPU does not support double-precision (64-bit) floats natively; using double forces the ESP32 back into slow software emulation. Knowing where the hardware draws the line between fixed and floating-point execution is what separates a hobbyist script from a production-grade firmware.

Common Confusions and Troubleshooting Math Errors

What people commonly confuse binary points with is the assumption that the 'point' is a physical character stored in memory alongside the bits, or that floating-point variables offer infinite mathematical precision. Both assumptions lead to bizarre bugs in embedded systems.

Why does my float variable stop counting accurately at 16,777,216?

This is the most infamous floating-point trap. An IEEE 754 single-precision float (standard float in C/C++) uses 32 bits total, but only 24 bits are allocated to the significand (the actual number data). 2^24 is exactly 16,777,216. If you try to add 1 to 16,777,216 using a single-precision float, the hardware lacks the bits to represent 16,777,217, so it simply rounds back to 16,777,216. Your loop will run forever. If you need to count high-resolution encoder pulses over long durations, use a 64-bit integer (uint64_t), not a float.

Is fixed-point Q-math always better than floating-point?

No. Fixed-point is superior for deterministic timing (like DSP audio filters or motor commutation) because the execution time is constant and predictable. However, fixed-point suffers from a limited dynamic range. If your sensor reads 0.001V one second and 400V the next, a fixed-point register will either truncate the small value or overflow on the large value. Floating-point dynamically shifts the binary point via its exponent, handling massive range variations effortlessly at the cost of higher power consumption and silicon area.

Do I need to worry about the binary point when using I2C sensors?

Usually, no. Most modern digital sensors (like the BME280 or MPU6050) handle the binary point internally. They transmit raw 16-bit or 24-bit integers over I2C. The datasheet will provide a scaling factor (e.g., 'divide raw value by 16384 to get degrees Celsius'). You can apply that scaling using the integer math tricks mentioned above, or cast to a float only at the very end when sending the data over WiFi or UART to a display.

Mastering binary numbers with decimal points means recognizing that the 'point' is an abstraction. By choosing the right format—integer scaling for simple UI, Q-format for high-speed control loops, and IEEE 754 floats for complex geometry—you ensure your circuits operate efficiently, predictably, and without hidden latency traps.