C++ Part I · Module 4 · Expressions and Operators
Integer Division and Remainder
Two of the arithmetic operators behave least like the arithmetic you learned at school, and Chapter 4.2 set them aside for this one. Dividing two integers does not give you a fraction and does not round — it throws the fractional part away, which means the answer to seven divided by two is three, and is not three and a half or four. The remainder operator is tied to that behaviour, and with negative operands it does something most people guess wrong: the sign of the result follows the value being divided rather than always being positive. Neither of these is arbitrary. Both fall out of a single identity that the standard guarantees, and once you have that identity you can derive the behaviour instead of memorising four cases. The chapter ends with the one arithmetic operation in C++ that has no defined result at all, and with the detection that finds it.
Foundation14 min readC++Part IFundamentalsOperators
Part I · Module 4 · Chapter 4.3 · Integer Division and Remainder
1. Division Between Integers Throws Away the Fraction
int cycles = 7;
int transfers = 2;
int per = cycles / transfers; // 3Not 3.5, and not 4. The fractional part is discarded.
This follows from Chapter 4.1 §2 and Chapter 4.2 §3: cycles / transfers is an expression whose operands are both int, so the result type is int. An int cannot hold 3.5. The division produces a whole number by dropping what will not fit.
If you want the fraction, one operand must be floating point:
double per = cycles / 2.0; // 3.5Chapter 4.2 §4's usual arithmetic conversions do the work: int meets double, the int is converted, and the division happens in floating point.
2. The Remainder Operator
% gives what is left over:
7 % 2 // 1It works only on integers. 7.5 % 2 does not compile — there is no remainder operator for floating-point types in C++.
The everyday uses are worth naming because they are so common:
- Is a value a multiple of another?
count % 4 == 0. - Wrap an index into a range.
index % buffer_sizekeeps an index inside a circular buffer. - Extract a digit or a low field.
value % 16gives the value modulo 16.
That last one has a better tool for hardware work, and Chapter 4.8 introduces it.
3. Negative Operands: Where the Guesses Fail
Now the part that is worth the chapter. Here are all four sign combinations, measured:
#include <iostream>
int main()
{
std::cout << " 7 / 2 = " << 7 / 2 << " 7 % 2 = " << 7 % 2 << '\n';
std::cout << "-7 / 2 = " << -7 / 2 << " -7 % 2 = " << -7 % 2 << '\n';
std::cout << " 7 / -2 = " << 7 / -2 << " 7 % -2 = " << 7 % -2 << '\n';
std::cout << "-7 / -2 = " << -7 / -2 << " -7 % -2 = " << -7 % -2 << '\n';
return 0;
}Expected output:
7 / 2 = 3 7 % 2 = 1
-7 / 2 = -3 -7 % 2 = -1
7 / -2 = -3 7 % -2 = 1
-7 / -2 = 3 -7 % -2 = -1Two common guesses are both wrong:
"Division rounds down." It does not. -7 / 2 is −3, not −4. Rounding down would give −4 because −3.5 is below −3. C++ truncates toward zero — it drops the fraction, which moves the result toward zero regardless of sign.
"A remainder is never negative." It can be. -7 % 2 is −1, not 1.
Look at the pattern in the remainder column: −1, 1, −1 for dividends −7, 7, −7. The sign of % follows the left operand — the dividend — and ignores the divisor.
4. One Identity Explains Both
Rather than memorising four rows, learn the rule they come from. The standard guarantees:
(a / b) * b + a % b == aIn words: multiply the quotient back by the divisor, add the remainder, and you get the original value.
Verified for a = -7, b = 2:
(a/b)*b + a%b = -7 (a = -7)Now derive the behaviour instead of remembering it. Given that division truncates toward zero, -7 / 2 is −3. The identity then forces the remainder:
(-3) * 2 + r == -7
-6 + r == -7
r == -1The remainder has to be −1. It is not a separate rule — it is what keeps the identity true once truncation is fixed.
That is the whole chapter in one line: truncation toward zero is the decision; the sign of the remainder is the consequence.
5. Division by Zero
One arithmetic operation in C++ has no defined result:
#include <iostream>
int main()
{
int transfers = 8;
int divisor = 0;
std::cout << transfers / divisor << '\n'; // undefined behaviour
return 0;
}Integer division by zero is undefined behaviour, and so is % with a zero right operand. Following the practice this curriculum has used since Chapter 3.3 §5, no result is quoted — there is no result to expect.
What is worth showing is the detection:
$ c++ -std=c++17 -Wall -Wextra -Wpedantic divzero.cpp -o dz
$ ← no diagnostic at all
$ c++ -std=c++17 -fsanitize=undefined divzero.cpp -o dz && ./dz
divzero.cpp:6:28: runtime error: division by zeroZero warnings from the compiler, a precise report from the sanitizer. The same pattern as signed overflow in Chapter 3.3 §5: the compiler cannot generally know the divisor at compile time, so the check has to happen while the program runs.
The defence is a guard, and Chapter 4.5 shows the form that makes it safe:
if (divisor != 0 && transfers / divisor > threshold) { /* ... */ }6. Two Patterns Worth Knowing
Rounding up instead of down. Integer division rounds toward zero, and sometimes you need the other direction — how many 64-byte bursts are needed to move 200 bytes?
const int bytes = 200;
const int burst_bytes = 64;
const int bursts_down = bytes / burst_bytes; // 3
const int bursts_up = (bytes + burst_bytes - 1) / burst_bytes; // 4Three bursts move 192 bytes and leave 8 behind, so the answer is 4. The second form is the standard idiom for rounding up with integers, and it works by pushing the value just past the next multiple before truncating. It assumes both values are positive, which for a byte count they are.
Splitting a value into quotient and remainder. Both operators on the same pair:
const int total_cycles = 199;
const int per_frame = 16;
const int whole_frames = total_cycles / per_frame; // 12
const int leftover = total_cycles % per_frame; // 7Twelve complete frames with 7 cycles left over, and 12 * 16 + 7 is 199 — the identity from §4 doing useful work.
7. Mistakes Worth Making Once
Expecting 7 / 2 to be 3.5. Both operands are int, so the result is an int.
Converting the result instead of an operand. double per = cycles / transfers; divides first and converts a value that has already lost its fraction.
Assuming integer division rounds down. It truncates toward zero. -7 / 2 is −3, not −4.
Assuming % is never negative. -7 % 2 is −1. The sign follows the dividend.
Testing for odd with x % 2 == 1. It fails for negative values, which give −1. Use x % 2 != 0.
Carrying % semantics from another language. Python's answers for negatives differ from C++'s, and both are internally consistent.
Guarding a division after performing it. The check has to come first, and Chapter 4.5 explains why the && form is what makes that reliable.
Treating floating-point division by zero as the same bug. It produces infinity or NaN rather than being undefined behaviour — a different problem needing a different check.
8. What You Should Be Able to Say Now
- Integer division discards the fractional part because the result type is an integer.
- To get a fraction, one operand must be floating point — converting the result is too late.
%gives the remainder and works on integers only; there is no floating-point%.- Division truncates toward zero, not downward. Measured:
-7 / 2is −3. - The remainder takes the sign of the dividend. Measured:
-7 % 2is −1 and7 % -2is 1. - One identity explains both:
(a / b) * b + a % b == a. Truncation is the decision; the remainder's sign is the consequence. - Other languages make the opposite choice and are equally consistent — do not carry the model across.
x % 2 == 1is a broken test for oddness because negatives give −1. Usex % 2 != 0.- Integer division or remainder by zero is undefined behaviour. Measured: no compiler warning;
-fsanitize=undefinedreported "division by zero". - Floating-point division by zero is different — infinity or NaN under IEEE-754, not undefined behaviour.
- Round up with
(a + b - 1) / bfor positive values. /and%together split a value into whole units and a leftover, and the identity guarantees they agree.
9. Exercises
Exercise 1 — predict eight results. Without running anything: 9 / 4, 9 % 4, -9 / 4, -9 % 4, 9 / -4, 9 % -4, -9 / -4, -9 % -4.
Exercise 2 — derive the remainder. Given that -9 / 4 truncates toward zero, use the identity from §4 to derive -9 % 4 without memorising a rule. Show the arithmetic.
Exercise 3 — fix the average. int total = 7, count = 2; double average = total / count; gives 3.0. Give two different corrections and say what each changes about when the conversion happens.
Exercise 4 — the broken odd test. Explain why if (offset % 2 == 1) fails to identify odd values when offset can be negative, and give the correct test.
Exercise 5 — bursts. A DMA engine moves data in 32-byte bursts. Write the expression for the number of bursts needed to move n bytes, explain why plain division is wrong, and state the assumption your expression makes about n.
Exercise 6 — classify the failure. A program crashes on one input and not others, with no compiler warning. The expression involved is total / active_channels. Name the likely cause, say which tool would identify it, and give the guard that prevents it.
Exercise 7 — two kinds of zero. Explain the difference between 7 / 0 where both are int and 7.0 / 0.0, and say which one a guard clause is protecting against.
10. Summary
- Integer division discards the fraction because the result type is an integer. Make an operand floating point to keep it.
%is integer-only and gives the leftover.- Division truncates toward zero —
-7 / 2is −3, not −4. - The remainder takes the dividend's sign —
-7 % 2is −1. - One identity explains both:
(a / b) * b + a % b == a. Truncation is the choice; the remainder follows. - Other languages chose differently and are self-consistent; do not port the intuition.
x % 2 == 1is a broken oddness test for negatives.- Integer division by zero is undefined behaviour — no compiler warning, caught by
-fsanitize=undefined. - Floating-point division by zero is not — it gives infinity or NaN.
(a + b - 1) / brounds up for positive values;/and%together split a value cleanly.
The operators so far have all produced numbers. The next group produces answers — true or false — and they are where Chapter 3.3 finally gets the explanation it deferred twice. A comparison between a signed and an unsigned value is not merely surprising; it is a direct consequence of the conversion rule Chapter 4.2 §4 established, and seeing the two chapters connect is what turns a memorised gotcha into something you can predict. Chapter 4.4 also covers the comparison that looks like ordinary mathematics and compiles into something else entirely.
Standards & specifications
- Governing standard
- ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)
Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C++ Programming curriculum.
