The Fixed-Point Binary Multiplication Formula
To calculate a physical product using binary registers, we use the Q-format scaling equation. This formula maps raw binary register values back to real-world engineering units (Volts, Amps, Watts) by accounting for the implicit binary point.Formula:
$$ P_{phys} = \frac{A_{reg} \times B_{reg}}{2^{(Q_A + Q_B)}} \times K_{scale} $$
| Symbol | Definition | Typical Bench Value |
|---|---|---|
| $P_{phys}$ | Physical Product Value (the real-world engineering result) | 250.5 W |
| $A_{reg}$ | Integer binary representation of Variable A (e.g., ADC reading) | 2048 (12-bit) |
| $B_{reg}$ | Integer binary representation of Variable B (e.g., scaling factor) | 16384 (Q14 format) |
| $Q_A$ | Number of fractional bits assigned to Variable A | 0 (pure integer) |
| $Q_B$ | Number of fractional bits assigned to Variable B | 14 |
| $K_{scale}$ | Physical unit scaling constant (maps binary full-scale to physical units) | 3.3 / 4095 (V/LSB) |
Rearranged Forms for Variable Isolation
On the bench, you often need to reverse-engineer a required register value or determine the necessary fractional bit depth to prevent overflow. Here are the rearranged forms solving for each primary variable:- Solve for $A_{reg}$ (Required Input Register):
$$ A_{reg} = \frac{P_{phys} \times 2^{(Q_A + Q_B)}}{B_{reg} \times K_{scale}} $$
Use case: Determining the exact ADC count needed to trigger a threshold when multiplied by a known constant. - Solve for $Q_B$ (Required Fractional Bit Depth):
$$ Q_B = \log_2\left(\frac{A_{reg} \times B_{reg} \times K_{scale}}{P_{phys}}\right) - Q_A $$
Use case: Designing a lookup table where you must guarantee the output resolution fits within a 16-bit PWM register. - Solve for $K_{scale}$ (Calibration Constant):
$$ K_{scale} = \frac{P_{phys} \times 2^{(Q_A + Q_B)}}{A_{reg} \times B_{reg}} $$
Use case: Calibrating a current shunt monitor where the physical multimeter reading differs from the raw binary calculation.
Solved Problems with Unit Tracking
Abstract math fails on the workbench. Here are two worked problems tracking both the binary operations and the physical engineering units.Problem 1: DC Power Calculation via Q-Format
Scenario: Calculate the power ($P = V \times I$) of a solar string. Voltage is read via a 12-bit ADC ($Q_A = 0$). Current is stored as a Q12 fractional constant ($Q_B = 12$) representing 1.5A. $K_{scale}$ for the voltage ADC is 0.000805 V/LSB (3.3V / 4095). The current scaling factor is 1.0 A/full-scale.
- Identify Raw Values: $V_{ADC}$ reads 2800. $I_{Q12}$ for 1.5A is $1.5 \times 2^{12} = 6144$.
- Multiply Registers: $2800 \times 6144 = 17,203,200$ (Raw binary product).
- Apply Binary Shift: Divide by $2^{(0 + 12)} = 4096$.
$17,203,200 / 4096 = 4200$ (Shifted binary product). - Apply Physical Scaling ($K_{scale}$): $4200 \times 0.000805 \text{ V/LSB} \times 1.0 \text{ A/full-scale} = 3.381 \text{ W}$.
- Verify Magnitude: Physical check: $V_{phys} = 2800 \times 0.000805 = 2.254\text{V}$. $2.254\text{V} \times 1.5\text{A} = 3.381\text{W}$. The binary math matches the physical reality.
Problem 2: PWM Duty Cycle Scaling
Scenario: You need to scale a 10-bit sensor reading ($0-1023$) to a 16-bit PWM timer register ($0-65535$) using binary multiplication to avoid slow floating-point division.
- Define the Multiplier: We need a binary constant $B_{reg}$ that scales 1023 to ~65535. Ratio is $65535 / 1023 \approx 64.06$. We use $64$, which is exactly $2^6$.
- Set Q-Formats: Sensor is integer ($Q_A = 0$). Multiplier is integer ($Q_B = 0$). But conceptually, we are shifting left.
- Execute Binary Multiply: If sensor reads 500, $500 \times 64 = 32,000$.
- Unit Tracking: $500 \text{ LSB}_{10-bit} \times 64 \text{ (shift factor)} = 32,000 \text{ LSB}_{16-bit}$.
Physical duty cycle: $32,000 / 65,535 = 48.8\%$. Sensor physical: $500 / 1023 = 48.8\%$. Perfect match.
Bench Scenario: ESP32 Power Calculation Overflow
Setup: A developer is building a telemetry node using an ESP32-WROOM-32 to monitor a 48V battery bank. They are reading voltage and current via external 16-bit ADCs (ADS1115) over I2C. To calculate instantaneous power, they multiply the two 16-bit signed integers in C++.
Numbers: Voltage register reads 28,000 (representing ~42V). Current register reads 15,000 (representing ~20A). The code uses standard 32-bit signed integers (`int32_t`) for the math.
Outcome: The expected physical power is roughly 840W. However, the serial monitor outputs a power reading of -84,967,296 or random garbage, and the system occasionally triggers a watchdog reset due to anomalous math faults in the DSP pipeline.
What Went Wrong: The developer forgot intermediate register sizing. Multiplying two 16-bit numbers yields a 32-bit result. $28,000 \times 15,000 = 420,000,000$. While this fits in a standard 32-bit signed integer (max ~2.14 billion), the subsequent scaling operations and accumulated summations pushed the intermediate calculations past the 31-bit positive limit, causing a signed integer overflow into the negative bit. Furthermore, if they had been using higher-resolution 32-bit ADCs, the initial multiply would have instantly overflowed a 32-bit register.
The Fix: Always cast to a double-width register before multiplying. The corrected C++ line is:
int64_t power_raw = (int64_t)adc_v * (int64_t)adc_i;
This forces the Espressif ESP-IDF compiler to allocate 64 bits for the intermediate product, preserving the magnitude before the binary right-shift is applied.
Application Boundaries, Assumptions, and Magnitudes
When the Formula Applies and Its Assumptions
Use this binary multiplication framework when working with microcontrollers lacking hardware FPUs (like the base STM32F1 series or AVR ATmega328P), or when writing high-speed interrupt service routines (ISRs) where floating-point math introduces unacceptable jitter. The formula assumes two's complement arithmetic for signed numbers and assumes that the physical quantities being multiplied are strictly linear. It also assumes you are managing the binary point manually; the hardware multiplier only sees raw bits and does not know where the decimal point belongs.
Unit Mistakes That Break the Math
The most catastrophic mistake is mixing Q-formats without adjusting the shift. If you multiply a Q15 number by a Q8 number, the result is inherently Q23. If your code blindly right-shifts by 15 (assuming the output should be Q15), your physical result will be off by a factor of $2^8$ (256x).
A secondary mistake is confusing binary shifts with base-10 decimal scaling. Dividing by 100 to convert centimeters to meters in binary math requires a costly division instruction or a complex reciprocal multiplication. In binary fixed-point, you design your hardware scaling so that unit conversions align with powers of 2 (e.g., scaling sensors to output in millivolts, which naturally align with binary shifts when mapped to 3.3V references).
Realistic Answer Magnitudes
A realistic answer magnitude must respect the silicon limits of your target register. For a 16-bit system, your final shifted product must not exceed 65,535 (unsigned) or 32,767 (signed). If your physical calculation yields a number that requires 18 bits to represent, and you store it in a 16-bit variable, the most significant bits will silently truncate, wrapping the value and destroying your data. Always verify that $P_{phys} \times 2^{Q_{target}}$ is less than the maximum integer value of your destination variable type. For deeper architectural rules on managing these bit-widths, refer to the Texas Instruments TMS320C28x Fixed-Point Math guide or standard fixed-point arithmetic principles.






