C/C++ Types, Units and Integer Arithmetic
Choose numeric types carefully and avoid overflow, truncation and unit-conversion mistakes.
A variable is a container with a declared size and interpretation. Changing its label does not automatically give it more room or fractional precision.
A type determines the values an object can represent and the operations performed on it. An int is not guaranteed to have the same width on every microcontroller. Fixed-width types such as uint16_t are useful when available and when an exact width is part of a protocol or hardware interface. Keep physical units in variable names to prevent mixing milliseconds with seconds.
Integer division discards the fractional part toward zero. Assigning 5/2 to a float still gives 2.0 because the division happened before the assignment. Use a floating operand, such as 5.0f/2.0f, when fractional arithmetic is intended. For integer scaling, multiply in a sufficiently wide type before dividing, and decide whether truncation or rounding is appropriate.
Overflow can happen in an intermediate expression even when the final variable is wide. On a platform with a 16-bit int, multiplying two promoted small integers may overflow before assignment to a 32-bit result. Convert an operand to a sufficiently wide unsigned type before multiplication and verify that the maximum product fits.
C and C++ are separate languages with overlapping syntax. C++ adds stronger abstractions such as classes and standard containers; code valid in one language is not automatically valid in the other. The example below is C++17. It uses a 32-bit intermediate for a millivolt estimate and makes its endpoint-based ADC scaling convention explicit.
Choose numeric types carefully and avoid overflow, truncation and unit-conversion mistakes.
Core ideas
- The type of the expression matters before assignment.
- Document units and numeric ranges.
- Do not assume int is always 32 bits.
Integer 5 / 2 = 2Endpoint estimate: mV = code × Vref_mV / maximum_codeWork through a practical example
1Assume a 10-bit ADC code of 512 and a 3300 mV reference.
2Use a 32-bit multiplication: 512 × 3300 = 1,689,600.
3Divide by 1023 using this endpoint-estimate convention: result is 1651 mV after truncation.
4Actual quantizer step size is Vref/1024; transfer-function conventions and reference accuracy must be checked separately.
Casting only the final result after overflow or integer division has already occurred.
Code example
C++17 — standalone program; expected output: 1651 mV
#include <cstdint>
#include <cstdio>
int main() {
const std::uint16_t code = 512;
const std::uint32_t mv =
static_cast<std::uint32_t>(code) * 3300u / 1023u;
std::printf("%lu mV\n", static_cast<unsigned long>(mv));
}Read and run this example in the stated environment. Code is displayed here; it does not execute in your browser.