Chapter 4 · Fundamental Data Types
Exercise · Chapter 4

Numeric Types Lab

The fundamental types — int, unsigned, the fixed-width integers, double, bool, char — all look like plain numbers until they bite you: a counter wraps to zero, 0.1 + 0.2 refuses to equal 0.3, a negative index turns into a gigantic positive one. This lab makes that behavior physical. You implement six small functions, and the grader proves the types behave exactly as the notes warned — overflow wraps, chars are numbers in disguise, floats are approximate, and signed→unsigned casts need a guard.

Each function isolates one idea from Chapter 4, and the tasks ramp up: Task 1 is a one-liner about sizeof; Task 6 is a small if/else-if classifier that ties bool logic and branches back to the CS6340 "why" (every if is a control-flow path that coverage analysis measures). The tests assert only platform-independent facts — fixed-width sizes and relationships, never an absolute sizeof(int) — so a green result means the same thing on every machine.

Your tasks

  1. int32ByteWidth() — return how many bytes a std::int32_t occupies. Use the sizeof operator (it already yields a std::size_t).
  2. shiftChar(char ch, int delta) — shift ch by delta in its encoding. Convert char→int, add, convert int→char; use static_cast for both. E.g. shiftChar('A', 1) == 'B'.
  3. wrapsAround(uint8_t start, uint8_t addend) — return true exactly when start + addend overflows an 8-bit unsigned (true sum > 255). Observe the real wrap; don't use signed overflow.
  4. nearlyEqual(double a, double b, double epsilon) — return |a - b| <= epsilon. Never compare doubles with ==.
  5. isValidIndex(int index, size_t length) — return true iff 0 <= index < length. Check index >= 0 first, then static_cast to std::size_t and compare with length.
  6. classifyNumber(double value) (capstone) — return a char tag using an ordered if/else-if chain: 'Z' if (nearly) zero, 'N' if negative, 'B' if >= 1000.0, 'P' otherwise. Reuse your nearlyEqual for the zero test.

Success criteria

make test prints PASS ✅ all checks. It compiles tests/tests.cpp against your starter/numeric_lab.cpp and runs ~30 CHECKs covering every function plus edge cases — the 255 + 1 overflow boundary, the 0.1 + 0.2 != 0.3 float trap, a negative index, and the ordering of the classifier. Until you fill in the tasks, the placeholders fail and you'll see FAIL: <expr> @line N for each, then FAIL ❌. Turning that red into green is the whole exercise. (Our proof it's solvable: make test-solution is green.)

Concepts practiced
  • sizeof and the fixed-width integers std::int32_t / std::uint8_t (4.3, 4.6)
  • Unsigned wraparound — defined, modular, and surprising (4.5)
  • Floating-point approximation and epsilon comparison instead of == (4.8)
  • bool values and std::boolalpha output (4.9)
  • charint round-tripping; chars are numeric underneath (4.11)
  • static_cast for explicit, searchable conversions; the negative-to-size_t trap (4.12)
  • if / else if / else chains and ordered conditions (4.10)
  • Reused from earlier chapters: user-defined functions across a .h/.cpp split with a header guard, and return (Ch 2); arithmetic expressions (Ch 1)
Constraints
  • Allowed: int / unsigned / fixed-width ints, float/double, bool, char, sizeof, static_cast, and if / else (4.10). std::abs from <cmath> is already included for you.
  • Forbidden (not taught yet): loops, switch, std::string, ?:, &&/|| chained logic — none are needed. Solve every task with the tools above.
  • Always static_cast for conversions you intend (char↔int, signed→size_t). No C-style casts.
  • Guard before you cast a possibly-negative int to std::size_t (Task 5).
  • Do not edit numeric_lab.h, the tests, or the demo — only fill in starter/numeric_lab.cpp.
Build & run locally
shell
make           # compile starter/ + demo  ->  starter/app
make run       # run the demo: see your functions' behavior printed
make test      # grade your code (unit tests) — RED until the TASKs are done
make solution  # build + run the reference demo if you get stuck
make clean
Hints
Task 1 — sizeof

sizeof(std::int32_t) is a std::size_t, so return sizeof(std::int32_t); is the whole body. (It's 4 — 32 bits ÷ 8 bits/byte — on every platform that has the type, which is why the test can hard-code == 4.)

Task 2 — char ↔ int round-trip
C++
int code { static_cast<int>(ch) };
return static_cast<char>(code + delta);

Do the + delta in int, then cast the sum back to char. Two explicit casts.

Task 3 — why a plain start + addend > 255 won't work

std::uint8_t is narrower than int, so start + addend is promoted to int and never wraps — the comparison would always be false for the cases that matter. Force the result back into the 8-bit type and check if it shrank:

C++
std::uint8_t wrapped { static_cast<std::uint8_t>(start + addend) };
return wrapped < start;
Task 4 — epsilon compare

return std::abs(a - b) <= epsilon;. Use <= (not <) so a gap exactly equal to epsilon still counts as "near" — one of the edge cases is built to check that.

Task 5 — order of operations is the lesson
C++
if (index < 0) { return false; }
return static_cast<std::size_t>(index) < length;

The index < 0 check must come first. Cast a negative int to std::size_t before checking and it becomes a huge value that slides right past length.

Task 6 — ordered if/else-if

Test the cases in the README's order; the first match wins:

C++
if (nearlyEqual(value, 0.0, 1e-9)) return 'Z';
else if (value < 0.0)             return 'N';
else if (value >= 1000.0)         return 'B';
else                              return 'P';

Reuse your own nearlyEqual for the zero test instead of value == 0.0.

Stretch goals
  • Add a classifyNumber band for "huge" values (>= 1e6) using scientific notation literals (1e6).
  • Replace the if (index < 0) guard with a single combined condition once you've met && (Chapter 6): index >= 0 && static_cast<std::size_t>(index) < length.
  • Print min/max of each fixed-width type with <limits> (std::numeric_limits<std::int32_t>::max()), and confirm wrapsAround agrees at the boundary.
  • Generalize nearlyEqual to a relative tolerance (scale epsilon by the magnitude of the inputs) so it works for very large values too.
starter/numeric_lab.cpp C++
// ============================================================================
//  Chapter 4 — Fundamental Data Types · Project: Numeric Types Lab  (STARTER)
//  numeric_lab.cpp  —  fill in the six TASK blocks, then `make test`.
// ============================================================================
//
//  This file COMPILES AS-IS (warning-clean), but every function returns a
//  deliberately wrong placeholder, so `make test` starts RED. Replace each
//  `>>> YOUR CODE HERE <<<` with a real body to turn it GREEN. The contract for
//  each function lives in ../numeric_lab.h — read those comments first.
//
//  Allowed this chapter: int / unsigned / fixed-width ints, float/double, bool,
//  char, sizeof, static_cast, and if / else (4.10). NO loops, NO switch, NO
//  std::string — those are later chapters.
// ----------------------------------------------------------------------------
#include "../numeric_lab.h"

#include <cmath>   // std::abs(double) — handy for the floating-point tasks

// ─── TASK 1: int32ByteWidth ──────────────────────────────────────────────────
// Return how many BYTES a std::int32_t occupies. Use the `sizeof` operator
// (it already yields a std::size_t, so you can return it directly).
// Hint: `sizeof(std::int32_t)`.
//
//   >>> YOUR CODE HERE <<<
//
std::size_t int32ByteWidth()
{
    return 0;   // placeholder — a real int32_t is never 0 bytes wide
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 2: shiftChar ───────────────────────────────────────────────────────
// Convert `ch` to int, add `delta`, convert the sum back to char, return it.
// Use static_cast for BOTH conversions (char→int, then int→char).
//
//   >>> YOUR CODE HERE <<<
//
char shiftChar(char ch, int delta)
{
    (void)delta;     // silence "unused parameter" until you use it
    return ch;       // placeholder — returns the char unchanged (no shift)
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 3: wrapsAround ─────────────────────────────────────────────────────
// Return true exactly when start + addend overflows an 8-bit unsigned value
// (true sum > 255). One clean way: store the wrapped result in a std::uint8_t
// and check whether it came out SMALLER than `start`. Don't use signed overflow.
//
//   >>> YOUR CODE HERE <<<
//
bool wrapsAround(std::uint8_t start, std::uint8_t addend)
{
    (void)start;
    (void)addend;
    return false;    // placeholder — claims nothing ever wraps (wrong for 255+1)
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 4: nearlyEqual ─────────────────────────────────────────────────────
// Return true when |a - b| <= epsilon. Never compare doubles with ==.
// std::abs(double) (from <cmath>) gives you the magnitude of the difference.
//
//   >>> YOUR CODE HERE <<<
//
bool nearlyEqual(double a, double b, double epsilon)
{
    (void)a;
    (void)b;
    (void)epsilon;
    return false;    // placeholder — claims nothing is ever "close enough"
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 5: isValidIndex ────────────────────────────────────────────────────
// Return true iff 0 <= index < length. CHECK index >= 0 FIRST; only then cast
// it to std::size_t and compare with length. (Casting a negative int to
// std::size_t makes a huge number — that's the bug we're guarding against.)
//
//   >>> YOUR CODE HERE <<<
//
bool isValidIndex(int index, std::size_t length)
{
    (void)index;
    (void)length;
    return true;     // placeholder — claims EVERY index is valid (even -1)
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 6: classifyNumber (capstone) ───────────────────────────────────────
// Return a char tag using an if / else-if / else chain, IN THIS ORDER:
//   'Z' if nearlyEqual(value, 0.0, 1e-9)   (use your own Task-4 function)
//   'N' if value < 0.0
//   'B' if value >= 1000.0
//   'P' otherwise
//
//   >>> YOUR CODE HERE <<<
//
char classifyNumber(double value)
{
    (void)value;
    return '?';      // placeholder — not one of the real tags Z/N/B/P
}
// ─────────────────────────────────────────────────────────────────────────────
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).