4.06 Float vs Double Array Footprint & Q15/Q31 Fixed-Point Math on Hardware without FPU
Executive Summary: Converting temperature sensor readings stored in arrays. We explore the memory difference between float (32-bit IEEE 754) and double (64-bit), and show how to implement fixed-point arithmetic (Q-format) for microcontrollers without a hardware Floating Point Unit (FPU).
💻 1. Annotated Source Code
#include <iostream> #include <array> using namespace std; int main() { const int NUM_DAYS = 7; array<double, NUM_DAYS> fahrenheitTemps; for (int i = 0; i < NUM_DAYS; i++) { cout << "Enter the temperature in Fahrenheit for day " << (i + 1) << ": "; cin >> fahrenheitTemps[i]; } cout << "\nHere are the temperatures converted to Celsius:"<<endl; for (double tempF : fahrenheitTemps) { double tempC = (tempF - 32) * 5.0 / 9; cout << "F: " << tempF << " -> C:" << tempC << endl; } return 0; }
📐 2. Architecture & UML Class Model
<<compilation-unit>>
TemperatureConverter
Scaling Pipeline
Attributes / Data Members
-fahrenheitTemps[5] : double
-celsiusTemps[5] : double
Operations / Methods
+convertFtoC(f: double) : double
+batchConvert(fArr: const double*, cArr: double*, len: size_t) : void
📚 3. Core C++ Concepts Deep-Dive
1. IEEE 754 Floating-Point Sizing
float: 32 bits (1 sign bit, 8 exponent bits, 23 mantissa bits) $\approx 7$ decimal digits precision.double: 64 bits (1 sign bit, 11 exponent bits, 52 mantissa bits) $\approx 15-17$ decimal digits precision.
2. Float Literal Suffix
In C++, floating literals without a suffix (e.g. 32.0) default to double. Writing 32.0f ensures single-precision operations.
⚡ 4. Embedded Systems & Hardware Reality
1. Software Emulation vs Hardware FPU
Microcontrollers like ARM Cortex-M0, M0+, and M3 lack a hardware FPU. Floating-point operations pull in software emulation library routines (__aeabi_fadd, __aeabi_fmul), adding 4KB-10KB of Flash bloat and taking 20-100 clock cycles per operation.
2. Q-Format Fixed-Point Arithmetic
Fixed-point representation uses standard integer registers to represent fractions with deterministic, single-cycle integer instructions.
💡 5. Production-Ready Embedded Refactoring
Q8.8 Fixed-Point Temperature Representation (Single-Cycle Arithmetic):
💡 Production-Ready Refactor
#include <cstdint> // Fixed-Point Q8.8 (16-bit integer: 8 bits integer, 8 bits fraction) struct FixedQ8_8 { int16_t raw; static constexpr FixedQ8_8 from_float(float val) noexcept { return FixedQ8_8{static_cast<int16_t>(val * 256.0f)}; } constexpr int16_t to_celsius_int() const noexcept { return raw >> 8; // Fast single-cycle bit shift } }; // Celsius to Fahrenheit in Q8.8 fixed-point: (C * 9/5) + 32 constexpr FixedQ8_8 celsiusToFahrenheit(FixedQ8_8 c) noexcept { int32_t intermediate = (static_cast<int32_t>(c.raw) * 9) / 5; return FixedQ8_8{static_cast<int16_t>(intermediate + (32 << 8))}; }
📝 Knowledge Verification Quiz
Test your understanding of the C++ concepts and embedded microcontroller trade-offs covered in this guide. Click any option for instant feedback.
Q1. What happens when floating-point math is executed on a microcontroller without a hardware FPU (e.g. Cortex-M0)?
Detailed Explanation:
Without an FPU, floating-point operations are emulated in software via math routines, consuming extra Flash space and hundreds of clock cycles.
Q2. Why is 'float x = 5.0;' suboptimal on a 32-bit MCU with single-precision FPU?
Detailed Explanation:
Unadorned floating literals default to
double (64-bit). On MCUs with only a single-precision (32-bit) FPU, this invokes slow software double-precision emulation.
Q3. What does Q8.8 fixed-point format represent?
Detailed Explanation:
Q8.8 uses an integer variable where the radix point is fixed: 8 bits for integer magnitude and 8 bits for fractional precision ($1/256$ resolution).
Q4. How fast is a fixed-point division by 256 compared to floating-point division on a Cortex-M0?
Detailed Explanation:
Dividing by $2^8 = 256$ in fixed-point is a single-cycle bit shift (
ASR #8), whereas software float division takes dozens of cycles.