Chapter 6 · Operators
Exercise · Chapter 6

Day-One Triage Console

A release just shipped and you're on call. Build/test events stream into the triage console, and the console has to make snap decisions about each one: is this build number even or odd? does this number fall in a special bucket? is this a leap year for the date math? are two measured timings "the same"? which ticket priority does this severity score deserve? should the instrumentation fire on this instruction?

You'll write the console's brain as nine small pure functions — each one is a single operator-driven decision. The twist: you never write a loop. The grader hammers each function with many inputs (even numbers, negative numbers, century years, both sides of an epsilon, off-by-one boundaries), so all you have to get right is the operators. That's the entire point of Chapter 6: precedence, %, ?:, relational and logical operators, and the floating-point comparison pitfall — the small mechanics that decide whether a predicate fires in the right place. (In CS6340 these are exactly the predicates that decide whether an instrumentation pass touches the right LLVM instructions.)

Your tasks

  1. Parity. isEven(int) returns true when the value divides by 2 evenly. classifyParity(int) returns "even"/"odd" using one ?: expression. Test oddness with % 2 != 0, not == 1 (negatives break the == 1 version).
  2. FizzBuzz category. fizzbuzzCategory(int) returns "fizzbuzz"/"fizz"/ "buzz"/"number" for a single value. Check "divisible by both 3 and 5" first (use &&). No loop, no printing — the grader does the repetition.
  3. Leap year. isLeapYear(int) as one logical expression (no if): divisible by 4 and (not divisible by 100 or divisible by 400).
  4. Safe float compare. approxEqual(double,double,double) returns whether std::abs(a - b) <= epsilon — never == on calculated doubles.
  5. Non-negative wrap. wrapIndex(int,int) folds an index into [0, size), staying non-negative even for negative indices (raw % can go negative).
  6. Sampling gate. shouldSampleInstruction(idx, sampleEvery, budget) fires only when sampleEvery > 0 and budget > 0 and idx % sampleEvery == 0. Put sampleEvery > 0 first so short-circuit && never lets you compute % 0.
  7. Priority ladder. ticketPriority(int) maps a score to "P0"/"P1"/ "P2"/"P3" with relational tests (nested ?: or if/return).
  8. Exactly-one-of. exactlyOneSource(bool,bool) returns the logical XOR — for two bools that's just !=.
  9. Integer power. powInt(int,int) computes base^exponent via std::pow (C++ has no **; ^ is XOR). Convert the double result back to int.

Success criteria

  • isEven(-7) / classifyParity(-7) — the negative-odd % 2 == 1 trap
  • fizzbuzzCategory(15) and (0) — "both" must beat plain "fizz"
  • isLeapYear(1900) vs isLeapYear(2000) — the century/400 rule
  • approxEqual(0.1 + 0.2, 0.3, 1e-9) — the classic == failure
  • wrapIndex(-1, 3) == 2 — folding a negative remainder
  • shouldSampleInstruction(8, 0, 10) — short-circuit must dodge % 0
  • ticketPriority(90) / (70) / (40) — the band boundaries
  • powInt(2, 8) == 256 — what 2 ^ 8 would wrongly give you (XOR = 10)
Concepts practiced
  • Remainder operator % and the divisibility idiom (x % n) == 0 (6.2, 6.3)
  • The sign-follows-left-operand rule for % on negatives, and fixing it (6.3)
  • Conditional operator ?: as an expression-level if/else, including nesting (6.6)
  • Relational operators < <= > >= == != producing bool (6.7)
  • Logical operators && || ! and short-circuit evaluation as a guard (6.8)
  • Logical XOR via != on two bools (6.8)
  • Floating-point comparison with an epsilon instead of == (6.7)
  • Precedence & associativity — parenthesizing for an unambiguous reader (6.1)
  • No ** in C++: exponentiation via std::pow + static_cast (6.3)
  • Reused from earlier chapters: functions / headers / header guard (Ch 2), bool and static_cast and if (Ch 4), std::string_view (Ch 5)
Constraints
  • Allowed: %, ?:, relational (< <= > >= == !=), logical (&& || !), arithmetic-assignment (+=), if/return, static_cast, std::abs, std::pow, and the function/string_view machinery already in the files.
  • Forbidden (not taught yet): any loop (for/while — Chapter 8), switch (Ch 8), <random>, containers, classes. If you reach for a loop, stop — the grader is the loop.
  • Idioms required by the notes: divisibility as (x % n) == 0; oddness as != 0; parenthesize mixed logical/relational/?: expressions; sampleEvery > 0 before the % in Task 6; epsilon comparison (never ==) for Task 4.
  • Keep every function pure: read the parameters, return a value — no I/O, no globals, no side effects.
Build & run locally
shell
make            # compile-check your starter/triage.cpp (warning-clean)
make test       # grade your code  ->  RED until the TASK blocks are filled in
make solution   # run the grader against the reference solution
make clean       # remove build artifacts

(make run is an alias for make test here — for this lab, "running" your code is running the grader against it, since the grader supplies main.)

Hints
Task 1 — parity and the negative trap

Even: return (value % 2) == 0;. For the label, the conditional operator is an expression, so you can return it directly: return ((value % 2) == 0) ? "even" : "odd";. Don't write the odd test as % 2 == 1 — in C++ -7 % 2 is -1, so that check is false for negative odds.

Task 2 — order the checks

The "fizzbuzz" case is the most specific, so test it first: if (((value % 3) == 0) && ((value % 5) == 0)) return "fizzbuzz"; then "fizz", then "buzz", then fall through to "number". If you check "fizz" before "fizzbuzz", 15 would wrongly return "fizz".

Task 3 — where the parentheses go

return ((year % 4) == 0) && (((year % 100) != 0) || ((year % 400) == 0)); The inner || group is required: && binds tighter than ||, so without the parentheses the meaning changes.

Task 4 — magnitude of the difference

return std::abs(a - b) <= epsilon; (std::abs from <cmath>, already included). Using <= lets epsilon == 0 still report identical values as equal.

Task 5 — fold the negative remainder
C++
int wrapped { index % size };
if (wrapped < 0) wrapped += size;   // -1 % 3 is -1; +3 makes it 2
return wrapped;
Task 6 — let short-circuit protect you
C++
return (sampleEvery > 0) && (budgetRemaining > 0) && ((instructionIndex % sampleEvery) == 0);

Because && stops at the first false, when sampleEvery is 0 the % on the right is never evaluated — so you never hit x % 0 (undefined behavior).

Task 7 — read the ladder top-down
C++
return (score >= 90) ? "P0"
     : (score >= 70) ? "P1"
     : (score >= 40) ? "P2"
     :                 "P3";

The first >= that holds wins, so order alone resolves the bands.

Task 8 — XOR is just !=

return fromFile != fromStdin; — valid precisely because both operands are already bool. (Don't use != as XOR on ints/pointers.)

Task 9 — pow returns a double

return static_cast<int>(std::pow(base, exponent) + 0.5); — the + 0.5 rounds to the nearest int, because std::pow can land just under the true value (e.g. 124.999… for 5^3).

Stretch goals
  • Make powInt exact with an integer loop instead of std::pow, and detect overflow against a wider type (needs loops, Chapter 8).
  • Replace the fixed-epsilon approxEqual with a relative+absolute tolerance (std::max(std::abs(a), std::abs(b)) * relEps) — see notes 6.7.
  • Add a classifyCoverage(covered, total) that returns "complete" / "partial" / "none" using %-free integer comparisons, then a double coverage ratio compared with approxEqual(ratio, 1.0, 1e-12).
  • Turn the console into a real CLI that reads events from std::cin and prints decisions (needs cin loops + validation, Chapters 8–9).
starter/triage.cpp C++
// Chapter 6 — Operators · Project: Day-One Triage Console   (STARTER)
// ─────────────────────────────────────────────────────────────────────────────
// Fill in the nine TASK blocks below. Each maps 1:1 to a task in the README and
// to a declaration in ../triage.h. The bodies currently return PLACEHOLDERS so
// the file compiles immediately — that's why `make test` is RED right now. Your
// job is to turn it GREEN by implementing the real operator logic.
//
//     make build         compile your code (should already work)
//     make test          grade it          (RED until you fill these in)
//     make solution      run the reference if you get stuck
//
// No loops, no I/O, no globals — just operators, `if`, and return values.

#include "../triage.h"
#include <cmath>   // std::abs (Task 4), std::pow (Task 9)

// ─── TASK 1: parity with `%` and `?:` ────────────────────────────────────────
// isEven: an int is even when it divides by 2 with no remainder, i.e. its
// remainder when divided by 2 is 0. Use `%` and `==`.
// classifyParity: pick the label with ONE conditional operator:
//     condition ? "even" : "odd"
// Watch the trap from the notes: test oddness with `% 2 != 0`, NOT `% 2 == 1`,
// because in C++ a negative odd number has remainder -1, not 1.
//
//   >>> YOUR CODE HERE <<<
//
bool isEven(int /*value*/)
{
    return false;   // placeholder — replace with the real check
}

std::string_view classifyParity(int /*value*/)
{
    return "?";     // placeholder — replace with a ?: expression
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 2: FizzBuzz as a category (no loop!) ───────────────────────────────
// Return one of "fizzbuzz" / "fizz" / "buzz" / "number" for a SINGLE value.
// Check the most specific case first: divisible by 3 AND by 5 (use `&&`).
// You may use `if`/`return`, or a nested `?:`. No printing, no loop — the grader
// supplies the loop by calling this across many numbers.
//
//   >>> YOUR CODE HERE <<<
//
std::string_view fizzbuzzCategory(int /*value*/)
{
    return "number";   // placeholder
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 3: leap year with logical operators ────────────────────────────────
// Return, as ONE boolean expression (no `if`):
//     (year % 4 == 0) && ( (year % 100 != 0) || (year % 400 == 0) )
// Keep the parentheses — they make the precedence (and the intent) unambiguous.
//
//   >>> YOUR CODE HERE <<<
//
bool isLeapYear(int /*year*/)
{
    return false;   // placeholder
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 4: safe floating-point comparison ──────────────────────────────────
// Calculated doubles rarely match with `==` (e.g. 0.1 + 0.2 != 0.3). Instead,
// return whether the magnitude of their difference is within epsilon:
//     std::abs(a - b) <= epsilon
//
//   >>> YOUR CODE HERE <<<
//
bool approxEqual(double /*a*/, double /*b*/, double /*epsilon*/)
{
    return false;   // placeholder
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 5: non-negative wrap (ring-buffer index) ───────────────────────────
// Raw `index % size` can be negative in C++ (the result takes the sign of the
// left operand: -1 % 3 is -1). Compute the remainder, and if it came out
// negative, add `size` once to fold it into [0, size). Assume size > 0.
//
//   >>> YOUR CODE HERE <<<
//
int wrapIndex(int /*index*/, int /*size*/)
{
    return 0;   // placeholder
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 6: instrumentation sampling gate (CS6340 tie-in) ───────────────────
// Fire only when ALL hold:  sampleEvery > 0,  budgetRemaining > 0,
// and instructionIndex lands on the stride (instructionIndex % sampleEvery == 0).
// ORDER MATTERS: put `sampleEvery > 0` FIRST so short-circuit `&&` skips the `%`
// when sampleEvery is 0 (otherwise you'd compute `x % 0` — undefined behavior).
//
//   >>> YOUR CODE HERE <<<
//
bool shouldSampleInstruction(int /*instructionIndex*/, int /*sampleEvery*/, int /*budgetRemaining*/)
{
    return false;   // placeholder
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 7: ticket priority from a severity score ───────────────────────────
// Map score -> "P0"/"P1"/"P2"/"P3" using relational operators. A nested `?:`
// reads top-down like a ladder:
//     (score >= 90) ? "P0" : (score >= 70) ? "P1" : (score >= 40) ? "P2" : "P3"
// (You may use `if`/`return` instead if you prefer.)
//
//   >>> YOUR CODE HERE <<<
//
std::string_view ticketPriority(int /*score*/)
{
    return "P3";   // placeholder
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 8: exactly-one-of (logical XOR via !=) ─────────────────────────────
// "Exactly one is true" for two bools is simply `fromFile != fromStdin`.
// (This trick is ONLY valid because both operands are already bool.)
//
//   >>> YOUR CODE HERE <<<
//
bool exactlyOneSource(bool /*fromFile*/, bool /*fromStdin*/)
{
    return false;   // placeholder
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 9: integer power via std::pow ──────────────────────────────────────
// C++ has no exponent operator. Compute std::pow(base, exponent) in double, then
// convert back with static_cast<int>. std::pow lands on a value like 124.99999
// for some inputs, so add 0.5 before truncating to round to the nearest int:
//     static_cast<int>(std::pow(base, exponent) + 0.5)
// Assume exponent >= 0 and the true result is non-negative and fits in an int.
//
//   >>> YOUR CODE HERE <<<
//
int powInt(int /*base*/, int /*exponent*/)
{
    return 0;   // placeholder
}
// ─────────────────────────────────────────────────────────────────────────────
Run
Submit
Run in your browser — coming soon For now: copy or download the files and use make test locally (see “Build & run locally” above).