6.xChapter 6 summary and quiz
This was the chapter where five chapters of operator credit got settled: every symbol you'd been using on faith now has rules you've seen stated. The review, some predictions, and a program about money.
Quick review
Precedence ranks the operators (arithmetic above comparisons above && above ||, assignment dead last) and associativity breaks ties between equals, almost always left-to-right. You memorize neither: parenthesize anything non-obvious instead, the one rule worth keeping. Comparisons have no associativity, so chains like 1 <= x <= 10 are compile errors with the && fix in the help text. Precedence governs grouping only; operands themselves evaluate left to right, guaranteed by the language, function arguments included.
The arithmetic operators behave like math class until types intervene. Integer division truncates toward zero (7 / 2 is 3, -7 / 2 is -3), float division doesn't, and one stray integer literal can truncate the accuracy out of a float formula. The remainder % reports what's left after truncating division, takes its sign from the left operand (so test evenness with != 0, not == 1), and rem_euclid exists when math-class modulo is wanted. Dividing by zero is a compile error when the compiler can prove it and a panic (debug and release) when it can't. There is no exponent operator: .pow() for integers (overflow rules per lesson 4.4), .powi()/.powf() for floats, and ^ is XOR, silently computing 8 from 2 ^ 10 for anyone who guesses otherwise.
Compound assignment (+=, -=, *=, /=, %=, plus bitwise cousins) folds an operation into a mut variable without naming it twice, evaluates to () like all Rust assignment, and stands in for the ++/-- that Rust deliberately omitted; x++ gets a custom compiler error with the fix attached, while --x legally double-negates and does nothing, the one C habit to actively hunt down.
The six comparison operators produce bools from same-typed operands, with no == true redundancies wanted. Float ordering is dependable when values are far apart; float equality is not dependable at all once values have been computed, since 0.1 + 0.2 == 0.3 is false. The professional tool is epsilon comparison: absolute tolerance fails across magnitudes, relative tolerance (Knuth) fails near zero, so approx_equal checks absolute first and relative second. f64::EPSILON is machine epsilon, not a general-purpose tolerance, and NaN answers false to every comparison, so use .is_nan().
The logical operators && and || combine bools, with each side required to be a bool (the C-classic x == 'y' || 'Y' is a type error here). Both short-circuit: a decided left side skips the right entirely, which makes count != 0 && total / count > n a real guard, and makes side effects on the right side unreliable. AND outranks OR, so parenthesize when they mix, and De Morgan's laws flip the connector when a ! pushes through: !(a && b) is !a || !b, with each inner comparison flipping too.
(The optional lesson, 6.6: the same symbols &, |, ^, !, plus shifts, applied per-bit; masks built from 1 << n; set with |=, clear with &= !MASK, toggle with ^=, test with & MASK != 0.)
Quiz time
Question #1
Add the parentheses the compiler implies:
a) 2 * 3 + 4 % 5
b) x > 0 && x % 2 == 0
c) a || !b && c
Show solution
a) (2 * 3) + (4 % 5) (* and % share the top arithmetic rank; + waits)
b) (x > 0) && ((x % 2) == 0) (arithmetic, then comparisons, then &&)
c) a || ((!b) && c) (! grabs tightest, && beats ||; this one deserves its parentheses in real code)
Question #2
What does this print?
fn main() {
println!("{}", 13 / 4);
println!("{}", 13 % 4);
println!("{}", -13 / 4);
println!("{}", 13.0 / 4.0);
}Show solution
3
1
-3
3.25
Integer division truncates toward zero in both directions (-3.25 truncates to -3); the remainder line is the leftover from line one (three 4s fit in 13, one left); the float line is the only one doing math-class division.
Question #3
Three claims, true or false, one sentence of why:
a) If f(x) has a side effect, f(x) || true guarantees the side effect happens.
b) (a - b).abs() < f64::EPSILON is the professional way to compare two computed f64s.
c) n % 2 == 1 correctly detects odd numbers for every i32 value of n.
Show solution
a) True, but only by the skin of its teeth: || evaluates its left side always (it's the right side short-circuiting skips), so f(x) runs. Flip the operands and it might not.
b) False: that's machine epsilon, a near-zero tolerance that fails once values accumulate normal rounding error; use the combined absolute-plus-relative approx_equal from lesson 6.4.
c) False: negative odd numbers give a remainder of -1, not 1, because % takes the dividend's sign; n % 2 != 0 works for both signs.
Question #4
The chapter's program: a cash register. Read the cost and the amount paid (both in whole cents, using the read-a-number recipe from lesson 6.4), and either report how much is still owed, or break the change due into quarters (25), dimes (10), nickels (5), and pennies. Compound assignment will earn its keep. Match the sample run exactly:
Cost in cents?
289
Paid in cents?
500
Change due: 211 cents
quarters: 8
dimes: 1
nickels: 0
pennies: 1
And if they underpay:
Cost in cents?
500
Paid in cents?
289
You still owe 211 cents.Show solution
use std::io;
fn read_cents(prompt: &str) -> u32 {
println!("{prompt}");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("failed to read input");
input.trim().parse().expect("that wasn't a whole number")
}
fn main() {
let cost = read_cents("Cost in cents?");
let paid = read_cents("Paid in cents?");
if paid < cost {
println!("You still owe {} cents.", cost - paid);
} else {
let mut change = paid - cost;
println!("Change due: {change} cents");
let quarters = change / 25;
change %= 25;
let dimes = change / 10;
change %= 10;
let nickels = change / 5;
let pennies = change % 5;
println!("quarters: {quarters}");
println!("dimes: {dimes}");
println!("nickels: {nickels}");
println!("pennies: {pennies}");
}
}
Design notes, lesson by lesson: each coin is one integer division ("how many whole quarters fit?", 6.2) followed by one compound remainder assignment ("keep what's left", 6.3), and the cascade repeats per denomination. The paid < cost comparison (6.4) does double duty: it picks the message, and it makes both subtractions safe, since u32 subtraction would panic on a negative result (lesson 4.4's overflow rules; each branch only subtracts the smaller from the larger). The read_cents helper is lesson 5.6's recipe behind a function from chapter 2.
Question #5
A reviewer suggests "the program would be friendlier in dollars: read f64s like 2.89 instead of cents." What goes wrong?
Show solution
Money amounts like 2.89 aren't exactly representable in binary floats (lesson 6.4), so the arithmetic accumulates sub-cent errors, and the coin-counting logic depends on exact division and remainder, which floats can't promise (worse: % and / on floats wouldn't truncate to whole coins at all without extra conversion). Whole cents in an integer make every operation exact. This is a general rule worth keeping: count money in its smallest unit, in integers. If friendliness matters, read dollars as text, convert to cents once, carefully, and do all arithmetic there.
Chapter 7 puts these operators where they live in real programs: conditions that branch and loops that repeat, including your first program that runs until it decides to stop.