16-bit Accumulator Wrap in an ADC Average
A production firmware team collects 100 samples from a 12-bit ADC. Each sample is stored in a uint16_t array; values range from 1000 to 1200. A developer writes:
uint16_t sum = 0;
for (int i = 0; i < 100; i++) sum += samples[i];
uint16_t avg = sum / 100;
printf("avg=%u\n", avg);
The reported average is about 440 instead of about 1100. A second routine compares a signed index with an unsigned length:
int idx = -1;
size_t len = 512;
if (idx < len) printf("in range\n");
The team also sees a compiler warning about comparing signed and unsigned. Changing sum to uint32_t fixes the average. A reviewer asks for a precise explanation rather than a trial-and-error patch, and wants the comparison expression made safe without changing the meaning of a failed search (idx == -1).
1. Explain why the printed average is about 440 rather than about 1100, and why changing `sum` to `uint32_t` fixes it. Show the arithmetic and state where the conversion happens.
Read the worked answer
uint16_t is an unsigned 16-bit type with range 0..65535. In sum += samples[i], both operands are promoted to int for the addition (on this target int is 32-bit), the addition is done in int, and the assignment back to sum converts the result modulo 65536. The true sum is approximately 100 × 1100 = 110000. 110000 − 65536 = 44464, which is the stored value. Integer division 44464 / 100 = 444, matching the reported ~440. The wrap is not undefined behavior because the destination is unsigned; it is a defined modulo conversion. With uint32_t sum, the range is 0..4294967295, so 110000 (and even 100 × 1200 = 120000) fits and no wrap occurs. For portability, if int could be 16-bit, the int intermediate could overflow; accumulate with sum += (uint32_t)samples[i]; so the addition is unsigned 32-bit. The final average still fits uint16_t.
2. The comparison `if (idx < len)` with `int idx = -1; size_t len = 512;` is false. Explain the conversion and give a safe condition that still treats `idx == -1` as not found.
Read the worked answer
size_t is unsigned. In the usual arithmetic conversions, because size_t has rank at least as high as int and is unsigned, idx is converted to size_t. Converting −1 to an unsigned type is defined modulo 2^N, so it becomes SIZE_MAX (for example 18446744073709551615 on a 64-bit size_t), which is greater than 512; the condition is false. This is a defined conversion but the wrong test. A safe condition is if (idx >= 0 && (size_t)idx < len). The && short-circuits: the cast is only evaluated when idx is nonnegative, so −1 is rejected before conversion. Alternatively, store indices in ptrdiff_t or use a separate boolean found flag. Do not write (size_t)idx < len alone.
3. The same source prints `sizeof(samples)` with `%d` and `sum` with `%u`. Identify the format issues and give portable corrections.
Read the worked answer
sizeof yields size_t, an unsigned type; %d expects int. Passing size_t to %d is a type mismatch and undefined behavior even if the bit pattern happens to print. Use %zu: printf("%zu\n", sizeof samples);. For uint16_t, default argument promotion converts it to int on this target, but %u expects unsigned int; the types differ. Cast explicitly: printf("%u\n", (unsigned)sum);, or use the inttypes.h macro PRIu16 with a cast to the promoted type. The lesson is to match the conversion specifier to the promoted argument type, not to the declared variable type.