Computing Foundations › Inside the Machine › Day 4
Hands-on lab — Day 4: Binary and Data Representation: Bits, Bytes, and Numbers
- ← Back to the Day 4 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/computing-foundations/day-004-binary-and-data-representation-bits-bytes/
Commands
Setup
cd labs/sections/computing-foundations/day-004-binary-and-data-representation-bits-bytes Run
bash examples/binary_toolkit.sh
bash starter/binary_toolkit.sh Test
bash tests/run_tests.sh File tree
examples/binary_toolkit.sh expected-output/tests-run.txt expected-output/toolkit-demo.txt metadata.yml README.md requirements/README.md security.md starter/binary_toolkit.sh starter/conversion-drills.md tests/run_tests.sh troubleshooting.md
Lab README
Day 004 lab — Binary by Hand and by Shell
Lesson
- Lesson title: Binary and Data Representation: Bits, Bytes, and Numbers
- Day number: 4 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-004-binary-and-data-representation-bits-bytes
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-004-binary-and-data-representation-bits-byteswhen the site is running.
Purpose
Day 4's lesson teaches binary, hexadecimal, two's complement, and the byte arithmetic behind model sizes. This lab makes it muscle memory in two passes: first by hand — twelve conversion drills on paper, carries and all — and then by shell, where you build a base-conversion toolkit from the two number tools every macOS and Linux machine already ships: printf and bc.
Learning objectives
- Convert between decimal, binary, and hexadecimal by hand, quickly and correctly.
- Add 8-bit binary numbers with carries and negate a number in two's complement.
- Drive
printf(decimal ↔ hex, character → code) andbc(ibase/obase) as base converters. - Complete a working shell toolkit by filling in four well-specified exercises.
- Verify your own hand arithmetic mechanically, and run an automated test suite.
Prerequisites
- The Day 4 lesson (read it first — every drill uses a method worked there).
- The Day 1 lab (comfort running commands in a terminal).
- Paper and a pencil for the drill sheet. This is deliberate: the hand pass comes first.
Supported operating systems
- macOS — fully supported;
printfandbcare preinstalled (tested on Apple Silicon). - Linux — fully supported; most desktop distributions preinstall
bc(minimal/container images may not — see requirements/README.md). - Windows — run everything unmodified inside WSL; native PowerShell lacks
bcand printf's character trick.
Hardware requirements
Any computer that can open a terminal. The lab computes with numbers no larger than a few hundred; there are no meaningful CPU, RAM, or disk demands.
Required software
bash(3.2 or newer — preinstalled on macOS and Linux).printf(a shell builtin) andbc(the POSIX arbitrary-precision calculator).
Free and open-source options
Everything in this lab is free: bash, printf, and bc are open-source and ship with (or install from the standard package manager of) every supported OS. No account, API key, or purchase is needed.
Installation
Usually none. If bc is missing (some minimal Linux images), install it with your package manager — one line, covered in requirements/README.md. Then change into this directory:
cd labs/sections/computing-foundations/day-004-binary-and-data-representation-bits-bytes
File structure
day-004-binary-and-data-representation-bits-bytes/
├── README.md ← you are here
├── metadata.yml ← machine-readable lab metadata
├── starter/
│ ├── conversion-drills.md ← 12 hand drills with working space (do these first)
│ └── binary_toolkit.sh ← YOUR working file (4 exercises in a running skeleton)
├── examples/
│ └── binary_toolkit.sh ← completed reference toolkit
├── tests/
│ └── run_tests.sh ← automated checks
├── expected-output/
│ ├── toolkit-demo.txt ← real captured demo run of the reference toolkit
│ └── tests-run.txt ← real captured test run (14 checks)
├── requirements/
│ └── README.md ← dependency statement (bash + bc)
├── troubleshooting.md
└── security.md
How to run
From this directory:
## 1. Do the twelve drills on paper
open starter/conversion-drills.md # or any text editor / pager
## 2. See the finished toolkit work, and use it to check your drill answers
bash examples/binary_toolkit.sh
bash examples/binary_toolkit.sh d2b 42
## 3. Your task: complete the four exercises in the starter, then run it
bash starter/binary_toolkit.sh
## 4. Check your work
bash tests/run_tests.sh
What the commands do
bash examples/binary_toolkit.sh— runs the demo: every converter once, with labeled output. With arguments it converts one value:d2b/b2dgo decimal↔binary viabc(obase=2/ibase=2),d2h/h2dgo decimal↔hex viaprintf '%X'/printf '%d' 0x…,h2bgoes hex→binary viabcwith both bases set (obase first), andbyteprints a character's ASCII code, hex form, and 8-bit pattern using printf's"'A"trick.bash starter/binary_toolkit.sh— the same skeleton with the four conversion functions returningunknown; each exercise comment names the exact incantation. Edit the file and replace eachecho "unknown"line.bash tests/run_tests.sh— verifies the reference toolkit against known values (42→101010, 255→11111111, 0xFF→255, 'A'→65/01000001, a round trip), and tests your starter: structure only while anyunknownremains, full strictness once you have completed all four exercises.
Expected output
See expected-output/toolkit-demo.txt — a real captured run:
=== Binary toolkit demo ===
dec2bin 42 -> 101010
bin2dec 101010 -> 42
dec2hex 255 -> FF
hex2dec 0xFF -> 255
hex2bin 2F -> 00101111
byte inspector -> character 'A' -> decimal 65 = hex 0x41 = bits 01000001
=== End of demo ===
Your completed starter must produce exactly the same demo. The captured test run is in expected-output/tests-run.txt.
Validation steps
- All twelve drills on the sheet have an answer and shown working — no answer without steps.
- Each drill answer matches the toolkit's output for the listed check command.
bash starter/binary_toolkit.shprints the demo with nounknownanywhere.bash tests/run_tests.shpasses (next section) — with the starter finished, it runs the full 20 strict checks.
Tests
bash tests/run_tests.sh
As shipped (starter untouched) the suite runs 10 strict checks against the reference and 4 structural checks against the starter, ending 14 checks, 0 failure(s). Once your starter has no unknown left, it is held to the same standard as the reference, ending 20 checks, 0 failure(s). The command exits 0 on success and non-zero on any failure, so it can run in CI.
Cleanup
Nothing to clean up: the scripts read no files and write nothing outside their console output. To reset your work, restore the starter from version control: git checkout -- starter/binary_toolkit.sh.
Troubleshooting
See troubleshooting.md for the full list (bc missing, hex case sensitivity, ibase/obase ordering, leading zeros, PowerShell notes).
Security notes
See security.md. Short version: pure local arithmetic — no network, no privileges, no files written, nothing sensitive read.
Extension exercises
- Add a
b2h(binary → hexadecimal) command to your toolkit —bccan do it directly, or you can chain your existingb2dandd2h. Extend the tests to cover it. - Add a
negcommand that prints the 8-bit two's complement of a small positive number.bchas no bit-flip, but the lesson gives you the arithmetic shortcut: the pattern for −n reads, as an unsigned byte, 256 − n. - Add a
wordcommand that walks a whole word (e.g.word Hi) and prints one byte-inspector line per character; compare the bit patterns ofAandaand explain the single differing bit.
Navigation
- Previous day: Day 3 — Memory and Storage (
../day-003-memory-and-storage-ram-disks-and, if present in your checkout). - Next day: Day 5 — Text, Images, and Sound as Data (
../day-005-text-images-and-sound-as-data, where these bytes become media). - Week overview:
../README.md
Expected output
tests-run.txt
Testing ./examples/binary_toolkit.sh ...
ok: d2b 42 -> 101010
ok: d2b 255 -> 11111111
ok: b2d 101010 -> 42
ok: b2d 10110101 -> 181
ok: d2h 42 -> 2A
ok: h2d FF -> 255
ok: h2d 0xFF -> 255
ok: h2b 2F -> 00101111
ok: byte A reports decimal 65 and bits 01000001
ok: round trip 200 -> binary -> 200
Testing ./starter/binary_toolkit.sh (structure only — exercises not finished) ...
ok: starter demo runs and exits 0
ok: starter prints demo header
ok: d2h 42 -> 2A
ok: h2d FF -> 255
14 checks, 0 failure(s).
toolkit-demo.txt
=== Binary toolkit demo ===
dec2bin 42 -> 101010
bin2dec 101010 -> 42
dec2hex 255 -> FF
hex2dec 0xFF -> 255
hex2bin 2F -> 00101111
byte inspector -> character 'A' -> decimal 65 = hex 0x41 = bits 01000001
=== End of demo ===
Source files
examples/binary_toolkit.sh (2808 bytes)
#!/usr/bin/env bash
# Day 004 lab — binary toolkit (completed reference implementation).
#
# A set of base converters built from exactly two preinstalled tools:
# printf — formats numbers between decimal and hexadecimal
# bc — an arbitrary-precision calculator that speaks bases 2..16
#
# Usage:
# bash binary_toolkit.sh # run the demo (all converters)
# bash binary_toolkit.sh d2b 42 # decimal -> binary
# bash binary_toolkit.sh b2d 101010 # binary -> decimal
# bash binary_toolkit.sh d2h 255 # decimal -> hexadecimal
# bash binary_toolkit.sh h2d FF # hexadecimal -> decimal (0x prefix ok)
# bash binary_toolkit.sh h2b 2F # hexadecimal -> binary, 4 bits per digit
# bash binary_toolkit.sh byte A # inspect one character's ASCII byte
set -euo pipefail
# Decimal -> binary. bc's obase sets the output base.
dec2bin() {
echo "obase=2; $1" | bc
}
# Binary -> decimal. bc's ibase sets the input base.
bin2dec() {
echo "ibase=2; $1" | bc
}
# Decimal -> hexadecimal. printf's %X renders a value in uppercase hex.
dec2hex() {
printf '%X\n' "$1"
}
# Hexadecimal -> decimal. printf understands 0x-prefixed input with %d.
hex2dec() {
printf '%d\n' "0x${1#0x}"
}
# Hexadecimal -> binary, padded to 4 bits per hex digit.
# Order matters in bc: set obase BEFORE ibase, and use UPPERCASE hex digits.
hex2bin() {
local hex bits width
hex="$(printf '%s' "${1#0x}" | tr '[:lower:]' '[:upper:]')"
bits="$(echo "obase=2; ibase=16; ${hex}" | bc)"
width=$(( ${#hex} * 4 )) # each hex digit is exactly 4 bits
printf "%0${width}d\n" "${bits}" # re-pad the leading zeros bc drops
}
# Byte inspector: one character -> its ASCII code, hex form, and 8-bit pattern.
# printf "'X" is the POSIX trick that yields a character's code number.
inspect_byte() {
local char code hex bits
char="$1"
code="$(printf '%d' "'${char}")"
hex="$(printf '%02X' "${code}")"
bits="$(printf '%08d' "$(echo "obase=2; ${code}" | bc)")"
echo "character '${char}' -> decimal ${code} = hex 0x${hex} = bits ${bits}"
}
demo() {
echo "=== Binary toolkit demo ==="
echo "dec2bin 42 -> $(dec2bin 42)"
echo "bin2dec 101010 -> $(bin2dec 101010)"
echo "dec2hex 255 -> $(dec2hex 255)"
echo "hex2dec 0xFF -> $(hex2dec 0xFF)"
echo "hex2bin 2F -> $(hex2bin 2F)"
echo "byte inspector -> $(inspect_byte A)"
echo "=== End of demo ==="
}
cmd="${1:-demo}"
case "${cmd}" in
d2b) dec2bin "$2" ;;
b2d) bin2dec "$2" ;;
d2h) dec2hex "$2" ;;
h2d) hex2dec "$2" ;;
h2b) hex2bin "$2" ;;
byte) inspect_byte "$2" ;;
demo) demo ;;
*)
echo "unrecognized command: ${cmd}" >&2
echo "usage: bash binary_toolkit.sh [d2b|b2d|d2h|h2d|h2b|byte] <value>" >&2
exit 1
;;
esac
metadata.yml (586 bytes)
lesson_id: D004
day: 4
kind: conversion-exercises
languages: [bash]
setup_commands:
- cd labs/sections/computing-foundations/day-004-binary-and-data-representation-bits-bytes
run_commands:
- bash examples/binary_toolkit.sh
- bash starter/binary_toolkit.sh
test_commands:
- bash tests/run_tests.sh
cleanup_commands:
- 'git checkout -- starter/binary_toolkit.sh # optional: reset your work'
requires_network: false
requires_api_key: false
estimated_minutes: 30
last_executed: '2026-07-12'
executed_on: 'macOS (Apple Silicon), bash tests/run_tests.sh → 14 checks, 0 failures'
requirements/README.md (381 bytes)
# Dependencies — Day 004 lab
**`bash`, `printf`, and `bc` — all preinstalled on macOS and mainstream
Linux.** `printf` is a shell builtin; `bc` is a POSIX calculator.
No network, no package manager, no accounts. If `bc` is missing on a
minimal Linux image, install it with your package manager (for example
`sudo apt install bc` on Debian/Ubuntu). On Windows, run under WSL.
starter/binary_toolkit.sh (2971 bytes)
#!/usr/bin/env bash
# Day 004 lab — binary toolkit (YOUR working file).
#
# This skeleton already runs: two converters (dec2hex, hex2dec) are complete
# so you can see the pattern. Your job is the four numbered exercises below —
# each comment names the exact printf/bc incantation to use. Replace each
# `echo "unknown"` line with that incantation. The finished reference is in
# examples/binary_toolkit.sh — try each exercise yourself before peeking.
#
# Usage (identical to the reference):
# bash binary_toolkit.sh # demo
# bash binary_toolkit.sh d2b 42 # decimal -> binary
# bash binary_toolkit.sh b2d 101010 # binary -> decimal
# bash binary_toolkit.sh d2h 255 # decimal -> hexadecimal
# bash binary_toolkit.sh h2d FF # hexadecimal -> decimal
# bash binary_toolkit.sh h2b 2F # hexadecimal -> binary
# bash binary_toolkit.sh byte A # inspect one character's ASCII byte
set -euo pipefail
# ---- Already implemented (study these two before starting) ----------------
# Decimal -> hexadecimal: printf's %X renders a value in uppercase hex.
dec2hex() {
printf '%X\n' "$1"
}
# Hexadecimal -> decimal: printf understands 0x-prefixed input with %d.
hex2dec() {
printf '%d\n' "0x${1#0x}"
}
# ---- Your four exercises ---------------------------------------------------
# Exercise 1: decimal -> binary.
# Use bc with an output base: echo "obase=2; $1" | bc
dec2bin() {
echo "unknown"
}
# Exercise 2: binary -> decimal.
# Use bc with an input base: echo "ibase=2; $1" | bc
bin2dec() {
echo "unknown"
}
# Exercise 3: hexadecimal -> binary.
# Use bc with BOTH bases — obase must come first, hex digits UPPERCASE:
# echo "obase=2; ibase=16; $1" | bc
# (You may pass the digits through: tr '[:lower:]' '[:upper:]')
hex2bin() {
echo "unknown"
}
# Exercise 4: byte inspector — a character's ASCII code and its bits.
# Get the code with printf's apostrophe trick: printf '%d' "'$1"
# Then convert the code with bc as in Exercise 1, and pad the result to a
# full byte with: printf '%08d\n' <bits>
inspect_byte() {
echo "unknown"
}
# ---- Command dispatch (no changes needed below this line) ------------------
demo() {
echo "=== Binary toolkit demo ==="
echo "dec2bin 42 -> $(dec2bin 42)"
echo "bin2dec 101010 -> $(bin2dec 101010)"
echo "dec2hex 255 -> $(dec2hex 255)"
echo "hex2dec 0xFF -> $(hex2dec 0xFF)"
echo "hex2bin 2F -> $(hex2bin 2F)"
echo "byte inspector -> $(inspect_byte A)"
echo "=== End of demo ==="
}
cmd="${1:-demo}"
case "${cmd}" in
d2b) dec2bin "$2" ;;
b2d) bin2dec "$2" ;;
d2h) dec2hex "$2" ;;
h2d) hex2dec "$2" ;;
h2b) hex2bin "$2" ;;
byte) inspect_byte "$2" ;;
demo) demo ;;
*)
echo "unrecognized command: ${cmd}" >&2
echo "usage: bash binary_toolkit.sh [d2b|b2d|d2h|h2d|h2b|byte] <value>" >&2
exit 1
;;
esac
starter/conversion-drills.md (3593 bytes)
# Day 004 drill sheet — conversions by hand
Work every drill **on paper first**, showing your steps in the space under
each one. Only then check yourself with the listed command (run from this
lab's directory, using either your completed starter toolkit or the
reference in `examples/`). The full worked answer key lives with the
instructor materials — resist looking until all twelve are done.
Method reminders from the lesson:
- **Decimal → binary:** subtract the largest power of 2 that fits, repeat
(or divide by 2 repeatedly and read the remainders bottom-to-top).
- **Binary → decimal:** add the place values (128 64 32 16 8 4 2 1) under
the 1-bits.
- **Hex → binary:** replace each hex digit with its 4-bit group; no
arithmetic needed.
- **Addition:** columns from the right; 1 + 1 = 10, so write 0, carry 1.
- **Two's complement negation:** flip every bit, then add 1.
## Part A — decimal to binary
**Drill 1.** Convert **13** to binary.
work:
answer: ________ check: bash examples/binary_toolkit.sh d2b 13
**Drill 2.** Convert **42** to binary.
work:
answer: ________ check: bash examples/binary_toolkit.sh d2b 42
**Drill 3.** Convert **91** to binary.
work:
answer: ________ check: bash examples/binary_toolkit.sh d2b 91
**Drill 4.** Convert **200** to binary.
work:
answer: ________ check: bash examples/binary_toolkit.sh d2b 200
## Part B — binary to decimal
**Drill 5.** Convert **1011** to decimal.
work:
answer: ________ check: bash examples/binary_toolkit.sh b2d 1011
**Drill 6.** Convert **10010110** to decimal.
work:
answer: ________ check: bash examples/binary_toolkit.sh b2d 10010110
**Drill 7.** Convert **11111111** to decimal.
work:
answer: ________ check: bash examples/binary_toolkit.sh b2d 11111111
## Part C — hexadecimal to binary
**Drill 8.** Convert **0x2F** to an 8-bit binary pattern (digit by digit —
no arithmetic).
work:
answer: ________ check: bash examples/binary_toolkit.sh h2b 2F
**Drill 9.** Convert **0xB4** to an 8-bit binary pattern.
work:
answer: ________ check: bash examples/binary_toolkit.sh h2b B4
## Part D — 8-bit addition with carries
Show the carry row above each sum, as in the lesson's worked examples.
**Drill 10.** Add **01011101 + 00100110**. Give the 8-bit result and verify
it in decimal.
carries:
0 1 0 1 1 1 0 1
+ 0 0 1 0 0 1 1 0
-----------------
answer: ________ check: convert both inputs with b2d, add in
decimal, then d2b the total
**Drill 11.** Add **10011001 + 01101100**. Give the 8-bit result **and**
state what happened to the ninth bit — treating the inputs as unsigned,
did this addition overflow?
carries:
1 0 0 1 1 0 0 1
+ 0 1 1 0 1 1 0 0
-----------------
answer: ________ check: b2d both inputs, add in decimal, and
compare against b2d of your 8-bit answer
## Part E — two's complement
**Drill 12.** Using 8-bit two's complement, find the bit pattern for
**−44** (start from 44 = 00101100: flip every bit, add 1). Then prove your
answer by adding it to 00101100 and showing the result is zero once the
ninth bit falls off.
work:
answer: ________ check: your pattern read as unsigned should
equal 256 − 44; confirm with b2d
tests/run_tests.sh (3208 bytes)
#!/usr/bin/env bash
# Tests for the Day 004 lab. Run from the lab directory:
# bash tests/run_tests.sh
#
# Verifies that the reference toolkit converts known values correctly, and —
# once the learner has completed the starter's four exercises — holds their
# toolkit to the same standard. Exits 0 on success, non-zero on any failure.
set -u
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
failures=0
checks=0
check() {
local label="$1" ok="$2"
checks=$((checks + 1))
if [ "${ok}" = "yes" ]; then
echo " ok: ${label}"
else
echo " FAIL: ${label}"
failures=$((failures + 1))
fi
}
# expect <script> <expected> <cmd> <arg>
expect() {
local script="$1" expected="$2" got
shift 2
got="$(bash "${script}" "$@" 2>&1)" || true
if [ "${got}" = "${expected}" ]; then
check "$* -> ${expected}" "yes"
else
check "$* -> ${expected} (got: ${got})" "no"
fi
}
run_conversion_checks() {
local script="$1"
echo "Testing ${script} ..."
expect "${script}" "101010" d2b 42
expect "${script}" "11111111" d2b 255
expect "${script}" "42" b2d 101010
expect "${script}" "181" b2d 10110101
expect "${script}" "2A" d2h 42
expect "${script}" "255" h2d FF
expect "${script}" "255" h2d 0xFF
expect "${script}" "00101111" h2b 2F
local byte_line
byte_line="$(bash "${script}" byte A 2>&1)" || true
case "${byte_line}" in
*65*01000001*) check "byte A reports decimal 65 and bits 01000001" "yes" ;;
*) check "byte A reports decimal 65 and bits 01000001 (got: ${byte_line})" "no" ;;
esac
# round trip: any value should survive d2b then b2d
local rt
rt="$(bash "${script}" b2d "$(bash "${script}" d2b 200)" 2>&1)" || true
[ "${rt}" = "200" ] && check "round trip 200 -> binary -> 200" "yes" \
|| check "round trip 200 -> binary -> 200 (got: ${rt})" "no"
}
run_structure_checks() {
local script="$1" output
echo "Testing ${script} (structure only — exercises not finished) ..."
if output="$(bash "${script}" demo 2>&1)"; then
check "starter demo runs and exits 0" "yes"
else
check "starter demo runs and exits 0" "no"
echo "${output}" | sed 's/^/ /'
fi
echo "${output}" | grep -q '^=== Binary toolkit demo ===$' \
&& check "starter prints demo header" "yes" || check "starter prints demo header" "no"
# the two prebuilt converters must already work in the skeleton
expect "${script}" "2A" d2h 42
expect "${script}" "255" h2d FF
}
# bc is the only dependency beyond the shell; fail early with a clear message.
if ! command -v bc >/dev/null 2>&1; then
echo "FAIL: 'bc' is not installed — see requirements/README.md" >&2
exit 1
fi
run_conversion_checks "${lab_dir}/examples/binary_toolkit.sh"
# The starter ships with 'unknown' placeholders on purpose; once the learner
# has replaced them all, hold their toolkit to the full conversion standard.
if grep -q '"unknown"' "${lab_dir}/starter/binary_toolkit.sh"; then
run_structure_checks "${lab_dir}/starter/binary_toolkit.sh"
else
run_conversion_checks "${lab_dir}/starter/binary_toolkit.sh"
fi
echo
echo "${checks} checks, ${failures} failure(s)."
[ "${failures}" -eq 0 ]
Troubleshooting
Troubleshooting — Day 004 lab
bc: command not found (Linux)
Some minimal Linux images omit bc. Install it with your package manager
(sudo apt install bc, sudo dnf install bc, etc.), or run the lab on
macOS/WSL where bc ships by default.
printf: 2F: invalid number when converting hex
printf '%d' 0x2F needs the 0x prefix to read hex; the toolkit adds it
for you in the h2d/h2b converters. If you call printf directly, write
printf '%d\n' 0x2F, not printf '%d\n' 2F.
My binary result has no leading zeros
bc prints the minimal number of digits: 42 becomes 101010, not
00101010. That is correct — a leading zero carries no value. When a byte
is expected (8 bits), pad it yourself; the worksheet notes where padding
matters.
Two's-complement negation looks wrong
Remember the fixed width. In 8 bits, negating 5 means: invert 00000101
to 11111010, then add 1 to get 11111011 (which is -5). If you drop the
width you will get a different bit pattern — the instructor solution shows
the full working.
The byte inspector prints a different code for my character
byte A reports 65 because 'A' is ASCII 65. Accented or non-ASCII
characters are multi-byte in UTF-8 (Day 5 covers this) — the single-byte
inspector is meant for plain ASCII characters.
Security notes
Security notes — Day 004 lab
- What the scripts do: run
printfandbcto convert numbers between bases and inspect one character's ASCII code. They read no files, write nothing, and make no network connections. - Privileges: normal user; no
sudo. - Privacy: nothing personal is read or transmitted.
- Read before running: the scripts are short and commented — read them first, as with every lab in this course.