Arithmetic Edge Cases

This guide helps you understand all the tricky, often misunderstood arithmetic behaviors in C, especially relevant for firmware development where correctness and efficiency matter.
1. Unsigned Integer Overflow (Wraparound)
uint8_t a = 250;
uint8_t b = 10;
uint8_t c = a + b; // c = 4
Why?
- uint8_t can store 0 to 255.
- 250 + 10 = 260, but 260 % 256 = 4 (wraps around).
- If we check it binary representation of 260= 1 0000 0100
- If you check last 8-bits, it's value is 4, and uint8_t can hold only last last 8 bits, so it shows the value as 4 only.
2. Signed Integer Overflow (Undefined Behavior)
int8_t a = 120;
int8_t b = 10;
int8_t c = a + b; // UB
Why?
- int8_t max = 127. 120 + 10 = 130, which can't be represented.
- As int8_t can hold value from (0 to +127) & (-1 to -127), where MSB bit (7th-bit) defines the sign. (1= negative, 0= positive)
- C says signed overflow = undefined behavior.
3. Unsigned Integer Underflow (Wraparound)
uint8_t a = 5;
uint8_t b = 10;
uint8_t c = a - b; // c = 251
Why?
- 5 - 10 = -5, but for unsigned: 256 - 5 = 251 (wraps backwards).
4. Signed Integer Underflow (Undefined Behavior)
int8_t a = -120;
int8_t b = 20;
int8_t c = a - b; // UB
Why?
- -120 - 20 = -140, which is less than -128. Undefined behavior.
5. Signed/Unsigned Comparison Trap
int a = -1;
unsigned int b = 1;
if (a < b) // FALSE!
Why?
- a is promoted to unsigned: becomes 4,294,967,295 (on 32-bit)
- So a > b is actually true!
6. Mixing Signed and Unsigned in Addition
uint8_t a = 255;
int8_t b = -1;
int result = a + b; // 254
Why?
- Both are promoted to int: 255 + (-1) = 254.
- Safe here, but not always — be cautious.
7. Promotion in Arithmetic (Truncation)
uint8_t a = 250, b = 10;
uint8_t sum = a + b; // sum = 4
Why?
- a + b = 260 (promoted to int), but assigned back to 8-bit → wraps to 4.
8. Division by Zero
int a = 5, b = 0;
int c = a / b; // Undefined
Why?
- Cannot divide by zero. Causes crash or exception.
9. Modulo by Zero
int a = 10, b = 0;
int c = a % b; // Undefined
Why?
- Same as division. Compiler may not catch it.
10. Negative Modulo Behavior
int a = -7, b = 3;
int r = a % b; // -1
Why?
- Result follows sign of dividend (a = -7), so result = -1.
11. Invalid Shift Amounts
int a = 1;
int b = -1;
a << b; // Undefined
1 << 40; // Undefined on 32-bit
Why?
- Shifting by negative or >= width is undefined.
12. Left Shift Overflow (Signed)
int8_t a = 64;
a <<= 2; // UB
Why?
- 64 << 2 = 256 → cannot be stored in 8-bit signed → UB.
13. Right Shift on Negative Values
int8_t a = -4;
int8_t b = a >> 1; // May be -2 or 126
Why?
- Depends on whether it's arithmetic or logical shift. Implementation-defined.
14. Unsigned Loop Trap
uint8_t i;
for (i = 3; i >= 0; i--) // Infinite loop!
Why?
- i never becomes < 0, it wraps to 255.
15. Pre/Post-Increment Chaos
int i = 1;
int x = i++ + ++i; // UB
Why?
- Multiple writes to i without sequence point. Result is undefined.
16. Float to Int Precision Loss
float f = 3.9;
int x = f; // x = 3
Why?
- Truncation happens — no rounding.
17. Integer Division Truncates
int a = 5, b = 2;
int result = a / b; // 2
Why?
- Fractions are discarded (5/2 = 2.5 → 2).
18. Casting Negative to Unsigned
int8_t a = -1;
uint8_t b = (uint8_t)a; // 255
Why?
- Two's complement conversion. -1 becomes 0xFF (255).
19. Float to Int Conversion
float f = -2.8;
int x = f; // x = -2
Why?
- Truncates toward zero.
20. Overflow in Compound Expressions
uint8_t a = 250, b = 10;
uint8_t c = (a + b) / 2; // c = 2
Why?
- a + b = 260 → wraps to 4 → 4 / 2 = 2
✔ Fix:
uint16_t temp = a + b;
uint8_t c = temp / 2; // Correct: 130
Concept understood? Let's apply and learn for real