Shell Mathematics: Linux as Your Calculator

Linux IS your calculator. This reference covers using bash, awk, bc, and other CLI tools for mathematical operations - from basic arithmetic to cryptographic foundations.

Source file: examples/codex/bash/math/

Topics Covered

1. Brace Expansion (Combinatorics)

Brace expansion as Cartesian product, arithmetic sequences, nested expansion.

The Cartesian Product

Brace expansion is the Cartesian product in shell syntax.

In set theory: A × B = {(a,b) | a ∈ A, b ∈ B}

In bash:

# Set A = {a, b}, Set B = {1, 2}
# A × B = {a1, a2, b1, b2}
echo {a,b}{1,2}
# Output: a1 a2 b1 b2

# Three sets: |A| × |B| × |C| = 2 × 2 × 2 = 8 elements
echo {a,b}{1,2}{x,y}
# Output: a1x a1y a2x a2y b1x b1y b2x b2y

# Practical: Create test matrix (3 × 2 = 6 files)
touch test-{small,medium,large}-{pass,fail}.log

2. Arithmetic Expansion

Integer arithmetic, comparison operators, ternary expressions.

Integer Arithmetic

Bash performs integer arithmetic natively with $.

# Basic operations
echo $((5 + 3))     # 8   (addition)
echo $((10 - 4))    # 6   (subtraction)
echo $((6 * 7))     # 42  (multiplication)
echo $((20 / 3))    # 6   (integer division - truncates)
echo $((20 % 3))    # 2   (modulo - remainder)
echo $((2 ** 10))   # 1024 (exponentiation)

# Variables (no $ needed inside $(( )))
x=5; y=3
echo $((x + y))     # 8
echo $((x * y))     # 15
echo $((x ** y))    # 125 (5³)

# Compound assignment
((x += 5))          # x = x + 5
((x++))             # x = x + 1 (post-increment)
((++x))             # x = x + 1 (pre-increment)

3. Numeral Systems (Baldor-style)

From unary to hexadecimal - the mathematical theory behind number bases.

Positional Notation - The Foundation

Every number system uses positional notation where digit position determines value.

In base b, a number d₃d₂d₁d₀ means:

d₃×b³ + d₂×b² + d₁×b¹ + d₀×b⁰
# Decimal 1234 = 1×10³ + 2×10² + 3×10¹ + 4×10⁰
echo $((1*10**3 + 2*10**2 + 3*10**1 + 4*10**0))
# Output: 1234

# Binary 1101 = 1×2³ + 1×2² + 0×2¹ + 1×2⁰
echo $((1*2**3 + 1*2**2 + 0*2**1 + 1*2**0))
# Output: 13

# Hex A3 = 10×16¹ + 3×16⁰
echo $((10*16**1 + 3*16**0))
# Output: 163

Unary (Base 1) - The Simplest System

Unary uses only ONE symbol. Count = number of symbols.

|     = 1
||    = 2
|||   = 3
|||| = 4
# Convert decimal to unary (tally marks)
decimal_to_unary() {
  local n=$1
  printf '|%.0s' $(seq 1 $n)
  echo
}
decimal_to_unary 5
# Output: |||||

# Convert unary to decimal
unary_to_decimal() {
  echo "${#1}"  # String length
}
unary_to_decimal "|||||||"
# Output: 7

# Unary addition is concatenation!
a="|||"
b="||||"
echo "${a}${b}"  # ||||||| = 7

Historical note: Tally marks, Roman numerals (partially), and Peano arithmetic use unary concepts.

Binary (Base 2) - The Computer’s Language

Only digits: 0, 1

Decimal | Binary | Powers of 2
--------|--------|-------------
   0    |   0    | 0
   1    |   1    | 2⁰
   2    |  10    | 2¹
   3    |  11    | 2¹ + 2⁰
   4    | 100    | 2²
   5    | 101    | 2² + 2⁰
   8    |1000    | 2³
  16    |10000   | 2⁴
 255    |11111111| 2⁸ - 1
# Binary to decimal
echo $((2#11111111))  # 255
echo $((2#10101010))  # 170

# Decimal to binary
dec_to_bin() {
  local n=$1 result=""
  while [[ $n -gt 0 ]]; do
    result="$((n % 2))$result"
    n=$((n / 2))
  done
  echo "${result:-0}"
}
dec_to_bin 42
# Output: 101010

# Using bc (easier)
echo "obase=2; 42" | bc
# Output: 101010

# Print binary with leading zeros (8-bit)
printf "%08d\n" $(echo "obase=2; 42" | bc)
# Output: 00101010

Octal (Base 8) - Unix Permissions

Digits: 0-7. Each octal digit = 3 binary digits.

Octal | Binary | Decimal | Permission
------|--------|---------|----------
  0   |  000   |    0    | ---
  1   |  001   |    1    | --x
  2   |  010   |    2    | -w-
  3   |  011   |    3    | -wx
  4   |  100   |    4    | r--
  5   |  101   |    5    | r-x
  6   |  110   |    6    | rw-
  7   |  111   |    7    | rwx
# Octal to decimal
echo $((8#755))   # 493
echo $((8#644))   # 420

# Decimal to octal
printf "%o\n" 493  # 755
echo "obase=8; 493" | bc  # 755

# File permissions in action
# rwxr-xr-x = 111 101 101 = 7 5 5 = 755
echo $((2#111101101))  # 493

# Why octal for permissions?
# 3 categories (owner, group, other) × 3 bits each = 9 bits
# 9 bits divides evenly by 3 → perfect for octal

Hexadecimal (Base 16) - The Programmer’s Choice

Digits: 0-9, A-F (10-15). Each hex digit = 4 binary digits (nibble).

Hex | Binary | Decimal
----|--------|--------
 0  | 0000   |   0
 1  | 0001   |   1
 ...
 9  | 1001   |   9
 A  | 1010   |  10
 B  | 1011   |  11
 C  | 1100   |  12
 D  | 1101   |  13
 E  | 1110   |  14
 F  | 1111   |  15
# Hex to decimal
echo $((16#FF))       # 255
echo $((16#CAFE))     # 51966
echo $((16#DEADBEEF)) # 3735928559

# Decimal to hex
printf "%x\n" 255     # ff
printf "%X\n" 255     # FF
printf "%08X\n" 255   # 000000FF (8 digits, zero-padded)
echo "obase=16; 255" | bc  # FF

# Hex color codes (RGB)
# #FF5733 = Red=255, Green=87, Blue=51
echo "Red: $((16#FF)), Green: $((16#57)), Blue: $((16#33))"

# MAC addresses: 6 bytes = 12 hex digits
mac="AA:BB:CC:DD:EE:FF"
echo "$mac" | tr -d ':' | fold -w2 | while read byte; do
  echo -n "$((16#$byte)) "
done
echo
# Output: 170 187 204 221 238 255

Converting Between Any Bases

The universal algorithm: base₁ → decimal → base₂

# General base conversion with bc
# obase = output base, ibase = input base
# IMPORTANT: set obase BEFORE ibase!

# Binary to octal
echo "obase=8; ibase=2; 11111111" | bc
# Output: 377

# Octal to hex
echo "obase=16; ibase=8; 755" | bc
# Output: 1ED

# Hex to binary
echo "obase=2; ibase=16; FF" | bc
# Output: 11111111

# Base 5 to decimal (exotic)
echo "ibase=5; 1234" | bc
# 1×5³ + 2×5² + 3×5¹ + 4×5⁰ = 125 + 50 + 15 + 4 = 194
# Output: 194

# Bash native: base 2-64 input
echo $((36#ZZ))   # Base 36: Z=35, so ZZ = 35×36 + 35 = 1295
echo $((62#aZ))   # Base 62 (0-9, a-z, A-Z)

Quick Reference Table

Decimal Binary Octal Hex Powers

0

0

0

0

-

1

1

1

1

2⁰

2

10

2

2

4

100

4

4

8

1000

10

8

16

10000

20

10

2⁴

32

100000

40

20

2⁵

64

1000000

100

40

2⁶

128

10000000

200

80

2⁷

255

11111111

377

FF

2⁸-1

256

100000000

400

100

2⁸

# Generate this table programmatically
for n in 0 1 2 4 8 16 32 64 128 255 256; do
  printf "%3d | %9s | %3o | %3X\n" \
    $n "$(echo "obase=2; $n" | bc)" $n $n
done

4. Bitwise Operations

AND, OR, XOR, shifts - for networking and cryptography.

Bitwise Operators

Critical for networking, cryptography, and systems programming.

# AND (&) - both bits must be 1
echo $((12 & 10))
#   1100 (12)
# & 1010 (10)
# = 1000 (8)
# Output: 8

# OR (|) - either bit is 1
echo $((12 | 10))
#   1100 (12)
# | 1010 (10)
# = 1110 (14)
# Output: 14

# XOR (^) - bits are different
echo $((12 ^ 10))
#   1100 (12)
# ^ 1010 (10)
# = 0110 (6)
# Output: 6

# NOT (~) - flip all bits (two's complement)
echo $((~12))
# Output: -13 (in two's complement)

# Left shift (<<) - multiply by 2^n
echo $((1 << 8))    # 256  (1 × 2⁸)
echo $((5 << 2))    # 20   (5 × 2² = 5 × 4)

# Right shift (>>) - divide by 2^n
echo $((256 >> 4))  # 16   (256 ÷ 2⁴ = 256 ÷ 16)

Subnet Calculations

Apply bitwise AND to calculate network address.

# Network address = IP AND Subnet Mask
# IP: 192.168.1.100, Mask: 255.255.255.0 (/24)

ip_dec=$((192 * 256**3 + 168 * 256**2 + 1 * 256 + 100))
mask_dec=$((255 * 256**3 + 255 * 256**2 + 255 * 256 + 0))
network=$((ip_dec & mask_dec))

printf "IP:      %d.%d.%d.%d\n" $((ip_dec >> 24 & 255)) $((ip_dec >> 16 & 255)) $((ip_dec >> 8 & 255)) $((ip_dec & 255))
printf "Mask:    %d.%d.%d.%d\n" $((mask_dec >> 24 & 255)) $((mask_dec >> 16 & 255)) $((mask_dec >> 8 & 255)) $((mask_dec & 255))
printf "Network: %d.%d.%d.%d\n" $((network >> 24 & 255)) $((network >> 16 & 255)) $((network >> 8 & 255)) $((network & 255))
# Output:
# IP:      192.168.1.100
# Mask:    255.255.255.0
# Network: 192.168.1.0

# CIDR to subnet mask
cidr=24
mask=$(( (0xFFFFFFFF << (32 - cidr)) & 0xFFFFFFFF ))
printf "%d.%d.%d.%d\n" $((mask >> 24 & 255)) $((mask >> 16 & 255)) $((mask >> 8 & 255)) $((mask & 255))
# Output: 255.255.255.0

Bit Flags (Unix Permissions)

File permissions are bit flags.

# rwxrwxrwx = 9 bits = 3 octal digits
# r=4 (100), w=2 (010), x=1 (001)

# Check if executable (owner)
perms=755  # rwxr-xr-x
echo $(( (8#$perms >> 6) & 1 ))  # 1 (owner can execute)

# Set specific permission bits
read_bit=4
write_bit=2
exec_bit=1
owner_perms=$((read_bit | write_bit | exec_bit))  # 7 (rwx)
group_perms=$((read_bit | exec_bit))               # 5 (r-x)
other_perms=$((read_bit | exec_bit))               # 5 (r-x)
echo "${owner_perms}${group_perms}${other_perms}"  # 755

# umask calculation
# Default file: 666, Default dir: 777
# umask 022: files get 644, dirs get 755
echo $((8#666 & ~8#022))  # 644
echo $((8#777 & ~8#022))  # 755

bc - Arbitrary Precision Calculator

Bash only does integers. Use bc for floating point.

# Basic floating point
echo "5.5 + 3.2" | bc
# Output: 8.7

# Set precision with scale
echo "scale=10; 22/7" | bc
# Output: 3.1428571428

# Square root
echo "scale=10; sqrt(2)" | bc
# Output: 1.4142135623

# Powers and logarithms (need -l for math library)
echo "scale=10; e(1)" | bc -l      # e^1 = 2.718...
echo "scale=10; l(10)" | bc -l     # ln(10) = 2.302...
echo "scale=10; s(0)" | bc -l      # sin(0) = 0
echo "scale=10; c(0)" | bc -l      # cos(0) = 1
echo "scale=10; a(1)" | bc -l      # atan(1) = 0.785... (π/4)

# Calculate π
echo "scale=50; 4*a(1)" | bc -l
# 3.14159265358979323846264338327950288419716939937510

awk - Floating Point and More

awk handles floats natively and is more powerful than bc for data processing.

# Basic math
awk 'BEGIN {print 5.5 + 3.2}'           # 8.7
awk 'BEGIN {print 22/7}'                # 3.14286
awk 'BEGIN {printf "%.10f\n", 22/7}'    # 3.1428571429

# Built-in functions
awk 'BEGIN {print sqrt(2)}'             # 1.41421
awk 'BEGIN {print sin(3.14159/2)}'      # 1 (sin 90°)
awk 'BEGIN {print cos(0)}'              # 1
awk 'BEGIN {print exp(1)}'              # 2.71828 (e)
awk 'BEGIN {print log(10)}'             # 2.30259 (ln)
awk 'BEGIN {print atan2(1,1)}'          # 0.785398 (π/4)

# Calculate π
awk 'BEGIN {printf "%.50f\n", 4*atan2(1,1)}'

# Exponents
awk 'BEGIN {print 2^10}'                # 1024
awk 'BEGIN {print 10^(-3)}'             # 0.001

Modular Arithmetic (Foundation of Cryptography)

# Basic modulo
echo $((17 % 5))    # 2  (17 = 3×5 + 2)
echo $((100 % 7))   # 2

# Clock arithmetic (12-hour)
hour=23
echo $((hour % 12))  # 11 (11 PM)

# Wrap-around (circular buffer index)
size=8
for i in {0..15}; do
  echo "i=$i -> index=$((i % size))"
done

# Check divisibility
n=42
[[ $((n % 2)) -eq 0 ]] && echo "even" || echo "odd"
[[ $((n % 3)) -eq 0 ]] && echo "divisible by 3"

Cryptographic Applications

# Modular exponentiation (a^b mod m)
# Used in RSA, Diffie-Hellman
# WARNING: Bash integers overflow! Use bc for real crypto

# Simple example: 3^5 mod 7
a=3; b=5; m=7
result=1
for ((i=0; i<b; i++)); do
  result=$(( (result * a) % m ))
done
echo $result  # 5  (243 mod 7 = 5)

# With bc for larger numbers
echo "3^1000 % 17" | bc
# Output: 1

# Caesar cipher (shift cipher)
# Encrypt: shift = 3
shift=3
char='A'
ascii=$(printf "%d" "'$char")
shifted=$(( (ascii - 65 + shift) % 26 + 65 ))
printf "\\$(printf '%03o' $shifted)\n"  # D

# XOR cipher (symmetric encryption basis)
plaintext=65  # 'A'
key=42
cipher=$((plaintext ^ key))
decrypted=$((cipher ^ key))
echo "Plain: $plaintext, Cipher: $cipher, Decrypted: $decrypted"
# Output: Plain: 65, Cipher: 107, Decrypted: 65

Set Operations

Using sort, uniq, comm, and process substitution.

# Define sets as files or arrays
set_a=(1 2 3 4 5)
set_b=(4 5 6 7 8)

# Union (A ∪ B) - all elements
printf '%s\n' "${set_a[@]}" "${set_b[@]}" | sort -u
# 1 2 3 4 5 6 7 8

# Intersection (A ∩ B) - common elements
comm -12 <(printf '%s\n' "${set_a[@]}" | sort) \
         <(printf '%s\n' "${set_b[@]}" | sort)
# 4 5

# Difference (A - B) - in A but not B
comm -23 <(printf '%s\n' "${set_a[@]}" | sort) \
         <(printf '%s\n' "${set_b[@]}" | sort)
# 1 2 3

# Symmetric difference (A △ B) - in either but not both
comm -3 <(printf '%s\n' "${set_a[@]}" | sort) \
        <(printf '%s\n' "${set_b[@]}" | sort) | tr -d '\t'
# 1 2 3 6 7 8

# Cardinality (|A|)
echo "${#set_a[@]}"  # 5

Quick Reference

Tool Best For Example

$

Integer arithmetic, bitwise ops

echo $2**10

bc

Arbitrary precision, floating point

echo "scale=10; sqrt(2)" | bc

bc -l

Math functions (sin, cos, log, exp)

echo "s(3.14159/2)" | bc -l

awk

Floating point in pipelines

awk 'BEGIN {print 22/7}'

python3 -c

Complex math, one-liners

python3 -c "import math; print(math.pi)"

dc

RPN calculator (stack-based)

echo "2 10 ^ p" | dc

printf

Base conversion output

printf "%x\n" 255

Python One-Liners

When you need serious math libraries.

# Basic math
python3 -c "print(22/7)"
python3 -c "print(2**100)"  # Big integers no problem

# Math module
python3 -c "import math; print(math.pi)"
python3 -c "import math; print(math.sqrt(2))"
python3 -c "import math; print(math.factorial(10))"
python3 -c "import math; print(math.gcd(48, 18))"

# Complex numbers
python3 -c "print((3+4j) * (1-2j))"

# Statistics
python3 -c "import statistics; print(statistics.mean([1,2,3,4,5]))"
python3 -c "import statistics; print(statistics.stdev([1,2,3,4,5]))"

# Hex/binary
python3 -c "print(bin(255))"     # 0b11111111
python3 -c "print(hex(255))"     # 0xff
python3 -c "print(int('FF', 16))" # 255

dc - RPN Stack Calculator

Reverse Polish Notation. Operands first, then operator.

# Basic: push 2, push 3, add, print
echo "2 3 + p" | dc
# Output: 5

# 2^10
echo "2 10 ^ p" | dc
# Output: 1024

# Set precision, calculate sqrt(2)
echo "10 k 2 v p" | dc
# Output: 1.4142135623

# Stack operations
echo "1 2 3 f" | dc    # f = print stack
# 3
# 2
# 1

# Factorial using recursion
echo "[d1-d1<f*]sf 10 lfx p" | dc
# Output: 3628800

Network Engineering Calculations

# How many /27 subnets fit in a /24?
echo $((2 ** (27 - 24)))  # 8 subnets

# Hosts per subnet
for cidr in 24 25 26 27 28 29 30; do
  hosts=$((2 ** (32 - cidr) - 2))
  printf "/%d = %d hosts\n" $cidr $hosts
done

# Is this IP in this subnet?
ip_to_dec() {
  IFS='.' read -r a b c d <<< "$1"
  echo $((a * 256**3 + b * 256**2 + c * 256 + d))
}

ip=$(ip_to_dec "192.168.1.100")
network=$(ip_to_dec "192.168.1.0")
mask=$((0xFFFFFF00))  # /24

if [[ $((ip & mask)) -eq $((network & mask)) ]]; then
  echo "IP is in subnet"
fi

Cryptography Basics

# Generate random bytes (hex)
head -c 16 /dev/urandom | xxd -p
# Output: a7f3b2c1... (32 hex chars = 128 bits)

# Hash a string
echo -n "password" | sha256sum | awk '{print $1}'

# Check if number is prime (naive)
is_prime() {
  local n=$1
  [[ $n -lt 2 ]] && return 1
  for ((i=2; i*i<=n; i++)); do
    [[ $((n % i)) -eq 0 ]] && return 1
  done
  return 0
}
is_prime 17 && echo "17 is prime"

# GCD using Euclidean algorithm
gcd() {
  local a=$1 b=$2
  while [[ $b -ne 0 ]]; do
    local t=$b
    b=$((a % b))
    a=$t
  done
  echo $a
}
gcd 48 18  # Output: 6

Subnet Fundamentals

IPv4 = 32 bits. CIDR notation /X means X bits for network, (32-X) bits for hosts.

# The magic formula: 2^(32-CIDR) = total addresses
# Usable hosts = 2^(32-CIDR) - 2 (network + broadcast)

# Generate hosts table
echo "CIDR | Mask | Total | Usable | Common Use"
echo "-----|------|-------|--------|----------"
for cidr in 8 16 20 21 22 23 24 25 26 27 28 29 30 31 32; do
  total=$((2 ** (32 - cidr)))
  usable=$((total - 2))
  [[ $usable -lt 0 ]] && usable=0
  # Calculate mask
  mask=$(( (0xFFFFFFFF << (32 - cidr)) & 0xFFFFFFFF ))
  mask_str=$(printf "%d.%d.%d.%d" \
    $((mask >> 24 & 255)) $((mask >> 16 & 255)) \
    $((mask >> 8 & 255)) $((mask & 255)))
  printf "/%2d  | %-15s | %8d | %7d\n" $cidr "$mask_str" $total $usable
done

Output:

CIDR | Mask            |    Total |  Usable
-----|-----------------|----------|--------
/8   | 255.0.0.0       | 16777216 | 16777214
/16  | 255.255.0.0     |    65536 |    65534
/20  | 255.255.240.0   |     4096 |     4094
/21  | 255.255.248.0   |     2048 |     2046
/22  | 255.255.252.0   |     1024 |     1022
/23  | 255.255.254.0   |      512 |      510
/24  | 255.255.255.0   |      256 |      254
/25  | 255.255.255.128 |      128 |      126
/26  | 255.255.255.192 |       64 |       62
/27  | 255.255.255.224 |       32 |       30
/28  | 255.255.255.240 |       16 |       14
/29  | 255.255.255.248 |        8 |        6
/30  | 255.255.255.252 |        4 |        2   (point-to-point)
/31  | 255.255.255.254 |        2 |        0   (RFC 3021)
/32  | 255.255.255.255 |        1 |        0   (host route)

Subnet Calculator Functions

# IP to decimal
ip_to_dec() {
  IFS='.' read -r a b c d <<< "$1"
  echo $((a << 24 | b << 16 | c << 8 | d))
}

# Decimal to IP
dec_to_ip() {
  printf "%d.%d.%d.%d\n" \
    $(($1 >> 24 & 255)) $(($1 >> 16 & 255)) \
    $(($1 >> 8 & 255)) $(($1 & 255))
}

# CIDR to subnet mask
cidr_to_mask() {
  local cidr=$1
  local mask=$(( (0xFFFFFFFF << (32 - cidr)) & 0xFFFFFFFF ))
  dec_to_ip $mask
}

# Get network address (IP AND mask)
network_addr() {
  local ip=$1 cidr=$2
  local ip_dec=$(ip_to_dec "$ip")
  local mask=$(( (0xFFFFFFFF << (32 - cidr)) & 0xFFFFFFFF ))
  dec_to_ip $((ip_dec & mask))
}

# Get broadcast address (IP OR ~mask)
broadcast_addr() {
  local ip=$1 cidr=$2
  local ip_dec=$(ip_to_dec "$ip")
  local mask=$(( (0xFFFFFFFF << (32 - cidr)) & 0xFFFFFFFF ))
  local wildcard=$((~mask & 0xFFFFFFFF))
  dec_to_ip $((ip_dec | wildcard))
}

# Get first usable host
first_host() {
  local net=$(network_addr "$1" "$2")
  local net_dec=$(ip_to_dec "$net")
  dec_to_ip $((net_dec + 1))
}

# Get last usable host
last_host() {
  local bcast=$(broadcast_addr "$1" "$2")
  local bcast_dec=$(ip_to_dec "$bcast")
  dec_to_ip $((bcast_dec - 1))
}

# Full subnet info
subnet_info() {
  local ip=$1 cidr=$2
  echo "IP Address:    $ip/$cidr"
  echo "Subnet Mask:   $(cidr_to_mask $cidr)"
  echo "Network:       $(network_addr $ip $cidr)"
  echo "Broadcast:     $(broadcast_addr $ip $cidr)"
  echo "First Host:    $(first_host $ip $cidr)"
  echo "Last Host:     $(last_host $ip $cidr)"
  echo "Total Hosts:   $((2 ** (32 - cidr)))"
  echo "Usable Hosts:  $((2 ** (32 - cidr) - 2))"
}

# Example usage:
subnet_info "192.168.1.100" 26

Output:

IP Address:    192.168.1.100/26
Subnet Mask:   255.255.255.192
Network:       192.168.1.64
Broadcast:     192.168.1.127
First Host:    192.168.1.65
Last Host:     192.168.1.126
Total Hosts:   64
Usable Hosts:  62

VLSM (Variable Length Subnet Masking)

Divide a network into different sized subnets based on need.

# Given: 192.168.1.0/24, need subnets for:
# - 50 hosts (needs /26 = 62 usable)
# - 25 hosts (needs /27 = 30 usable)
# - 10 hosts (needs /28 = 14 usable)
# - 2 hosts  (needs /30 = 2 usable, point-to-point)

# Find minimum CIDR for N hosts
cidr_for_hosts() {
  local hosts=$1
  for cidr in {32..0}; do
    usable=$((2 ** (32 - cidr) - 2))
    [[ $usable -ge $hosts ]] && { echo $cidr; return; }
  done
}

echo "50 hosts needs: /$(cidr_for_hosts 50)"  # /26
echo "25 hosts needs: /$(cidr_for_hosts 25)"  # /27
echo "10 hosts needs: /$(cidr_for_hosts 10)"  # /28
echo "2 hosts needs:  /$(cidr_for_hosts 2)"   # /30

# VLSM allocation (largest first!)
# Start: 192.168.1.0/24
#
# Subnet 1: /26 (50 hosts) → 192.168.1.0/26   (0-63)
# Subnet 2: /27 (25 hosts) → 192.168.1.64/27  (64-95)
# Subnet 3: /28 (10 hosts) → 192.168.1.96/28  (96-111)
# Subnet 4: /30 (2 hosts)  → 192.168.1.112/30 (112-115)
# Remaining: 192.168.1.116 - 192.168.1.255 (unused)

Supernetting (Route Aggregation)

Combine contiguous networks into one summary route.

# Given four /24 networks:
# 192.168.0.0/24
# 192.168.1.0/24
# 192.168.2.0/24
# 192.168.3.0/24
#
# Can they be summarized? Check if contiguous and power of 2.

# Binary analysis:
# 192.168.0.0 = 11000000.10101000.00000000.00000000
# 192.168.1.0 = 11000000.10101000.00000001.00000000
# 192.168.2.0 = 11000000.10101000.00000010.00000000
# 192.168.3.0 = 11000000.10101000.00000011.00000000
#
# First 22 bits identical → Summary: 192.168.0.0/22

# Verify:
echo "192.168.0.0/22 covers:"
for i in {0..3}; do
  echo "  192.168.$i.0/24"
done

# Calculate supernet prefix
# 4 networks = 2^2, so subtract 2 from original CIDR
# /24 - 2 = /22

Check if IP is in Subnet

# Is IP in subnet?
ip_in_subnet() {
  local ip=$1 network=$2 cidr=$3
  local ip_dec=$(ip_to_dec "$ip")
  local net_dec=$(ip_to_dec "$network")
  local mask=$(( (0xFFFFFFFF << (32 - cidr)) & 0xFFFFFFFF ))

  [[ $((ip_dec & mask)) -eq $((net_dec & mask)) ]]
}

# Example
if ip_in_subnet "192.168.1.100" "192.168.1.0" 24; then
  echo "192.168.1.100 IS in 192.168.1.0/24"
fi

if ! ip_in_subnet "192.168.2.1" "192.168.1.0" 24; then
  echo "192.168.2.1 is NOT in 192.168.1.0/24"
fi

Data Units (Bytes)

SI (decimal) vs IEC (binary):

SI Unit Value IEC Unit Value

KB (kilobyte)

10³ = 1,000

KiB (kibibyte)

2¹⁰ = 1,024

MB (megabyte)

10⁶ = 1,000,000

MiB (mebibyte)

2²⁰ = 1,048,576

GB (gigabyte)

10⁹

GiB (gibibyte)

2³⁰

TB (terabyte)

10¹²

TiB (tebibyte)

2⁴⁰

# Bytes to human-readable (IEC - what computers actually use)
bytes_to_human() {
  local bytes=$1
  local units=("B" "KiB" "MiB" "GiB" "TiB" "PiB")
  local unit=0
  local size=$bytes

  while [[ $(echo "$size >= 1024" | bc) -eq 1 ]] && [[ $unit -lt 5 ]]; do
    size=$(echo "scale=2; $size / 1024" | bc)
    ((unit++))
  done
  echo "$size ${units[$unit]}"
}

bytes_to_human 1073741824  # 1.00 GiB
bytes_to_human 1500000000  # 1.39 GiB

# Human to bytes
human_to_bytes() {
  local input=$1
  local num=${input%[KMGTP]*}
  local unit=${input##*[0-9]}

  case $unit in
    K|KB)  echo $((num * 1000)) ;;
    KiB)   echo $((num * 1024)) ;;
    M|MB)  echo $((num * 1000000)) ;;
    MiB)   echo $((num * 1024 * 1024)) ;;
    G|GB)  echo $((num * 1000000000)) ;;
    GiB)   echo $((num * 1024 * 1024 * 1024)) ;;
    *)     echo $num ;;
  esac
}

human_to_bytes "4GiB"  # 4294967296

Network Speed Units

Bits vs Bytes: Network = bits/sec, Storage = bytes/sec

# bits per second to bytes per second
# Divide by 8 (8 bits = 1 byte)
bps_to_Bps() {
  echo $(($1 / 8))
}

# Mbps to MiB/s (practical download speed)
mbps_to_mibs() {
  local mbps=$1
  # Mbps = millions of bits per second (SI)
  # To MiB/s: (mbps × 1,000,000) / 8 / 1,048,576
  echo "scale=2; $mbps * 1000000 / 8 / 1048576" | bc
}

echo "100 Mbps = $(mbps_to_mibs 100) MiB/s"   # 11.92 MiB/s
echo "1000 Mbps = $(mbps_to_mibs 1000) MiB/s" # 119.20 MiB/s

# Download time calculator
download_time() {
  local size_mib=$1
  local speed_mbps=$2
  local speed_mibs=$(mbps_to_mibs $speed_mbps)
  echo "scale=2; $size_mib / $speed_mibs" | bc
}

echo "4000 MiB at 100 Mbps: $(download_time 4000 100) seconds"

Time Conversions

# Seconds to human readable
seconds_to_human() {
  local secs=$1
  local days=$((secs / 86400))
  local hours=$(( (secs % 86400) / 3600 ))
  local mins=$(( (secs % 3600) / 60 ))
  local s=$((secs % 60))

  [[ $days -gt 0 ]] && printf "%dd " $days
  [[ $hours -gt 0 ]] && printf "%dh " $hours
  [[ $mins -gt 0 ]] && printf "%dm " $mins
  printf "%ds\n" $s
}

seconds_to_human 90061  # 1d 1h 1m 1s

# Human to seconds
human_to_seconds() {
  local input=$1 total=0
  [[ $input =~ ([0-9]+)d ]] && total=$((total + BASH_REMATCH[1] * 86400))
  [[ $input =~ ([0-9]+)h ]] && total=$((total + BASH_REMATCH[1] * 3600))
  [[ $input =~ ([0-9]+)m ]] && total=$((total + BASH_REMATCH[1] * 60))
  [[ $input =~ ([0-9]+)s ]] && total=$((total + BASH_REMATCH[1]))
  echo $total
}

human_to_seconds "2d 3h 15m"  # 183300

# Epoch timestamps
date +%s                    # Current epoch
date -d @1709337600        # Epoch to date
date -d "2024-03-01" +%s   # Date to epoch

Scientific Notation

# Large numbers in scientific notation
printf "%e\n" 1234567890     # 1.234568e+09
printf "%.2e\n" 6022000000000000000000000  # 6.02e+23 (Avogadro-ish)

# bc for precision
echo "scale=10; 6.022 * 10^23" | bc  # 6022000000000000000000000.0000000000

# awk for scientific
awk 'BEGIN { printf "%.3e\n", 299792458 }'  # 2.998e+08 (speed of light m/s)

# Engineering notation (powers of 3)
eng_notation() {
  local n=$1
  python3 -c "
from decimal import Decimal
d = Decimal('$n')
print(d.normalize().to_eng_string())
"
}

eng_notation 1500000    # 1.5E+6
eng_notation 0.000001   # 1E-6

Memory Calculations (RAM, Disk)

# Check system memory
free -b | awk '/^Mem:/ {print "Total RAM:", $2, "bytes"}'
free -h | awk '/^Mem:/ {print "Total RAM:", $2}'

# Disk space in bytes
df -B1 / | awk 'NR==2 {print "Root disk:", $2, "bytes"}'

# Page size (memory alignment)
getconf PAGESIZE  # Usually 4096 (4 KiB)

# Calculate pages needed for N bytes
pages_needed() {
  local bytes=$1
  local page_size=$(getconf PAGESIZE)
  echo $(( (bytes + page_size - 1) / page_size ))
}

pages_needed 10000  # 3 pages (for 10000 bytes)

# RAM for processes
ps aux --sort=-%mem | head -5 | awk '{print $4"% "$11}'

Quick Unit Reference

Category Conversions Formula

Bytes

1 KiB = 1024 B

2^10

1 MiB = 1024 KiB

2^20 B

1 GiB = 1024 MiB

2^30 B

Network

1 byte = 8 bits

bps / 8 = Bps

Mbps → MiB/s

× 10⁶ / 8 / 2²⁰

Time

1 day = 86400 s

24 × 60 × 60

1 hour = 3600 s

60 × 60

IPv4

1 octet = 8 bits

0-255

IP = 32 bits

4 × 8

Hex

1 byte = 2 hex digits

FF = 255

1 nibble = 1 hex digit

F = 15

12. Pattern Syntax (Brace vs Regex vs Glob)

CRITICAL: Shell has THREE different pattern systems. Don’t mix them!

The Three Pattern Languages

CRITICAL: Shell has THREE different pattern systems. Don’t mix them!

Type Syntax Expansion Example

Brace

{a,b,c}

Shell expands BEFORE command runs

ls {foo,bar}.txtls foo.txt bar.txt

Glob

*, ?, [abc]

Shell matches existing files

ls *.txt → matches files ending in .txt

Regex

(a|b), .*, [0-9]+

Used by grep, sed, awk INSIDE strings

grep 'error|warn' log.txt

# WRONG - mixing regex alternation with shell
ls 02_Assets/LRN-COLLEGE-ALGEBRA-(L|Q|T)*
# zsh: no matches found (unless extendedglob is set)

# CORRECT - brace expansion
ls 02_Assets/LRN-COLLEGE-ALGEBRA-{LATEX,PANDOC,QUARTO,TYPST}
# Expands to 4 separate arguments

# CORRECT - glob with wildcard
ls -d 02_Assets/LRN-COLLEGE-ALGEBRA-*/
# Matches any directory starting with that prefix

# Brace + glob COMBINED (powerful!)
ls src/{main,test}/**/*.{js,ts}
# Braces expand first, then globs match

Key insight: Braces expand to MULTIPLE ARGUMENTS before the command sees them. Globs match EXISTING FILES. Regex only works inside tools like grep/sed.

Extended Glob (zsh/bash)

Enable regex-like patterns in glob context.

# Bash: enable extended glob
shopt -s extglob

# Zsh: enable extended glob
setopt EXTENDED_GLOB

# Extended glob patterns (bash extglob):
# ?(pattern)  - 0 or 1 match
# *(pattern)  - 0 or more matches
# +(pattern)  - 1 or more matches
# @(pattern)  - exactly 1 match
# !(pattern)  - NOT matching

# Example: match .js OR .ts files
ls *.@(js|ts)

# Zsh extended glob uses different syntax:
# (#i) - case insensitive
# (pattern~except) - except
# pattern(/) - directories only
ls LRN-COLLEGE-ALGEBRA-*(/)  # directories only

Math Rendering Ecosystem

Tool Best For Output Learn

LaTeX

Academic papers, formal proofs

PDF (pdflatex, xelatex, lualatex)

High

Typst

Modern LaTeX replacement, fast

PDF

Medium

MathJax

Web display (docs, wikis)

HTML (JavaScript rendered)

Low

AsciiDoc :stem:

Technical docs (uses MathJax)

HTML, PDF

Low

Jupyter

Interactive notebooks

HTML, PDF

Medium

For your workflow:

  • Antora docs: :stem: latexmath (MathJax)

  • Formal papers: LaTeX or Typst

  • Quick notes: AsciiDoc or Markdown with MathJax

LaTeX from Command Line

# Compile LaTeX to PDF
pdflatex document.tex           # Basic
xelatex document.tex            # Better Unicode support
lualatex document.tex           # Lua scripting

# One-liner math rendering (requires standalone class)
cat << 'EOF' > /tmp/math.tex
\documentclass{standalone}
\usepackage{amsmath}
\begin{document}
$E = mc^2$
\end{document}
EOF
pdflatex -output-directory=/tmp /tmp/math.tex

# Convert to PNG for embedding
pdftoppm -png /tmp/math.pdf /tmp/math
# Result: /tmp/math-1.png

# Quick equation check (latexmk for automation)
latexmk -pdf -pvc document.tex  # Auto-recompile on save

Common LaTeX math:

% Fractions
\frac{a}{b}

% Subscript/superscript
x^2, x_i, x_i^2

% Greek letters
\alpha, \beta, \gamma, \pi, \sigma, \theta

% Summation, integral
\sum_{i=1}^{n} x_i
\int_{0}^{\infty} e^{-x} dx

% Matrices
\begin{pmatrix} a & b \\ c & d \end{pmatrix}

% Align equations
\begin{align}
  f(x) &= ax^2 + bx + c \\
       &= a(x - h)^2 + k
\end{align}

Typst - Modern LaTeX Alternative

Faster compilation, cleaner syntax, better error messages.

# Install typst
pacman -S typst  # Arch
brew install typst  # macOS

# Compile
typst compile document.typ
typst compile document.typ output.pdf

# Watch mode (auto-recompile)
typst watch document.typ

# One-liner
echo '$ E = m c^2 $' | typst compile - output.pdf

Typst math syntax:

// Fractions (natural!)
$ a/b $

// Subscript/superscript
$ x^2, x_i, x_i^2 $

// Greek (just type them!)
$ alpha, beta, gamma, pi $

// Summation
$ sum_(i=1)^n x_i $

// Matrices
$ mat(a, b; c, d) $

// Align
$ f(x) &= a x^2 + b x + c \
       &= a(x - h)^2 + k $

Typst vs LaTeX:

Feature LaTeX Typst

Compile speed

Slow

Fast (100x)

Error messages

Cryptic

Clear

Learning curve

Steep

Gentle

Package ecosystem

Massive

Growing

PDF quality

Excellent

Excellent

AsciiDoc Math (MathJax/STEM)

For Antora documentation.

= Document with Math
:stem: latexmath

Inline math: stem:[E = mc^2]

Block math:
[stem]
++++
\sum_{i=1}^{n} x_i = \frac{n(n+1)}{2}
++++

The quadratic formula:
[stem]
++++
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
++++

Build with Antora:

# Local build (Kroki handles diagrams)
cd domus-docs && make

# The :stem: attribute enables MathJax rendering
# MathJax loads from CDN in browser

Maxima - Computer Algebra System

Free, powerful symbolic math (like Mathematica).

# Install
pacman -S maxima wxmaxima  # Arch (wxmaxima = GUI)
apt install maxima wxmaxima  # Debian

# CLI usage
maxima --batch-string="diff(x^3 + 2*x, x);"
# Output: 3*x^2 + 2

maxima --batch-string="integrate(sin(x), x);"
# Output: -cos(x)

maxima --batch-string="factor(x^2 - 4);"
# Output: (x - 2)*(x + 2)

maxima --batch-string="solve(x^2 + 2*x - 3 = 0, x);"
# Output: [x = -3, x = 1]

# Limit
maxima --batch-string="limit(sin(x)/x, x, 0);"
# Output: 1

# Taylor series
maxima --batch-string="taylor(exp(x), x, 0, 5);"
# Output: 1 + x + x^2/2 + x^3/6 + x^4/24 + x^5/120 + ...

Interactive session:

$ maxima
Maxima 5.46.0

(%i1) f: x^3 - 6*x^2 + 11*x - 6;
(%o1)                       x^3 - 6*x^2 + 11*x - 6

(%i2) factor(f);
(%o2)                       (x - 1)*(x - 2)*(x - 3)

(%i3) diff(f, x);
(%o3)                       3*x^2 - 12*x + 11

(%i4) integrate(f, x);
(%o4)                       x^4/4 - 2*x^3 + 11*x^2/2 - 6*x

SymPy - Python Symbolic Math

# Install
pip install sympy

# One-liners
python3 -c "from sympy import *; x = Symbol('x'); print(diff(x**3, x))"
# Output: 3*x**2

python3 -c "from sympy import *; x = Symbol('x'); print(integrate(sin(x), x))"
# Output: -cos(x)

python3 -c "from sympy import *; x = Symbol('x'); print(factor(x**2 - 4))"
# Output: (x - 2)*(x + 2)

python3 -c "from sympy import *; x = Symbol('x'); print(solve(x**2 + 2*x - 3, x))"
# Output: [-3, 1]

# Pretty printing
python3 -c "
from sympy import *
init_printing()
x = Symbol('x')
expr = integrate(exp(-x**2), x)
pprint(expr)
"

# LaTeX output (for docs!)
python3 -c "from sympy import *; x = Symbol('x'); print(latex(Integral(exp(-x**2), x)))"
# Output: \int e^{- x^{2}}\, dx

gnuplot - The Classic

# Install
pacman -S gnuplot  # Arch

# Quick plot to terminal (ASCII art!)
gnuplot -e "set terminal dumb; plot sin(x)"

# Plot to PNG
gnuplot << 'EOF'
set terminal png size 800,600
set output 'plot.png'
set title 'Quadratic Function'
set xlabel 'x'
set ylabel 'f(x)'
plot x**2 - 4*x + 3 title 'x^2 - 4x + 3' with lines
EOF

# Plot data from file
echo -e "1 1\n2 4\n3 9\n4 16\n5 25" > /tmp/data.txt
gnuplot -e "set terminal dumb; plot '/tmp/data.txt' with linespoints"

# Multiple functions
gnuplot << 'EOF'
set terminal png size 800,600
set output 'trig.png'
set title 'Trigonometric Functions'
plot sin(x), cos(x), tan(x) title 'tan(x)'
EOF

# Parametric (circle)
gnuplot << 'EOF'
set terminal dumb
set parametric
set trange [0:2*pi]
plot sin(t), cos(t)
EOF

matplotlib - Python Plotting

# Install
pip install matplotlib numpy

# Quick plot
python3 << 'EOF'
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-10, 10, 100)
y = x**2 - 4*x + 3

plt.figure(figsize=(8, 6))
plt.plot(x, y, label='$f(x) = x^2 - 4x + 3$')
plt.axhline(y=0, color='k', linestyle='-', linewidth=0.5)
plt.axvline(x=0, color='k', linestyle='-', linewidth=0.5)
plt.grid(True, alpha=0.3)
plt.xlabel('x')
plt.ylabel('f(x)')
plt.title('Quadratic Function')
plt.legend()
plt.savefig('quadratic.png', dpi=150)
print('Saved: quadratic.png')
EOF

# Unit circle
python3 << 'EOF'
import matplotlib.pyplot as plt
import numpy as np

theta = np.linspace(0, 2*np.pi, 100)
x = np.cos(theta)
y = np.sin(theta)

plt.figure(figsize=(6, 6))
plt.plot(x, y)
plt.axis('equal')
plt.grid(True)
plt.title('Unit Circle')
plt.savefig('unit_circle.png', dpi=150)
EOF

Terminal Plotting (No GUI)

# Using gnuplot dumb terminal
plot_term() {
  gnuplot -e "set terminal dumb size 60,20; plot $1"
}
plot_term "sin(x)"
plot_term "x**2"

# Using Python with termgraph (pip install termgraph)
echo -e "A 10\nB 25\nC 15\nD 30" | termgraph

# Sparklines with spark (npm install -g spark)
echo "1 5 22 13 53" | spark
# Output: ▁▁▃▂█

# Using plotext (pip install plotext)
python3 << 'EOF'
import plotext as plt
import numpy as np

x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)

plt.plot(x, y)
plt.title("Sine Wave (Terminal)")
plt.show()
EOF

GNU Octave - MATLAB Compatible

# Install
pacman -S octave  # Arch

# Matrix operations
octave --eval "A = [1 2; 3 4]; B = [5 6; 7 8]; A * B"
# Output:
#    19   22
#    43   50

# Determinant
octave --eval "A = [1 2; 3 4]; det(A)"
# Output: -2

# Inverse
octave --eval "A = [1 2; 3 4]; inv(A)"
# Output:
#   -2.0000    1.0000
#    1.5000   -0.5000

# Eigenvalues
octave --eval "A = [4 2; 1 3]; eig(A)"
# Output:
#    2
#    5

# Solve linear system Ax = b
octave --eval "A = [2 1; 1 3]; b = [5; 10]; x = A \\ b"
# Output:
#    1
#    3
# (2*1 + 1*3 = 5, 1*1 + 3*3 = 10) ✓

# Matrix rank, null space
octave --eval "A = [1 2 3; 4 5 6; 7 8 9]; rank(A)"
# Output: 2 (not full rank!)

NumPy - Python Linear Algebra

# Install
pip install numpy

# Matrix multiplication
python3 -c "
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print(A @ B)  # Matrix multiply
"

# Determinant, inverse, eigenvalues
python3 << 'EOF'
import numpy as np
from numpy.linalg import det, inv, eig

A = np.array([[4, 2], [1, 3]])

print(f"Determinant: {det(A)}")
print(f"Inverse:\n{inv(A)}")

eigenvalues, eigenvectors = eig(A)
print(f"Eigenvalues: {eigenvalues}")
print(f"Eigenvectors:\n{eigenvectors}")
EOF

# Solve Ax = b
python3 -c "
import numpy as np
A = np.array([[2, 1], [1, 3]])
b = np.array([5, 10])
x = np.linalg.solve(A, b)
print(f'Solution: x = {x}')
# Verify: A @ x should equal b
print(f'Verification: A @ x = {A @ x}')
"

SciPy, NumPy, SymPy, Matplotlib

The complete scientific computing environment.

# Install the stack
pip install numpy scipy sympy matplotlib pandas jupyter

# Check versions
python3 -c "
import numpy, scipy, sympy, matplotlib
print(f'NumPy: {numpy.__version__}')
print(f'SciPy: {scipy.__version__}')
print(f'SymPy: {sympy.__version__}')
print(f'Matplotlib: {matplotlib.__version__}')
"

Use cases:

Package Purpose

NumPy

Arrays, linear algebra, FFT

SciPy

Integration, optimization, statistics, signal processing

SymPy

Symbolic math (differentiation, integration, solving)

Matplotlib

Plotting and visualization

Pandas

Data frames, time series, CSV handling

Jupyter

Interactive notebooks

Numerical Calculus with SciPy

# Numerical integration
python3 -c "
from scipy import integrate
import numpy as np

# Integrate sin(x) from 0 to pi
result, error = integrate.quad(np.sin, 0, np.pi)
print(f'∫₀^π sin(x) dx = {result:.6f}')
# Output: 2.000000

# Integrate x^2 from 0 to 1
result, error = integrate.quad(lambda x: x**2, 0, 1)
print(f'∫₀^1 x² dx = {result:.6f}')
# Output: 0.333333 (= 1/3)
"

# Numerical differentiation
python3 -c "
from scipy.misc import derivative
import numpy as np

f = lambda x: x**3

# Derivative at x=2
df = derivative(f, 2, dx=1e-6)
print(f\"f(x) = x³, f'(2) = {df:.6f}\")
# Output: 12.000000 (3 * 2² = 12)
"

# Root finding
python3 -c "
from scipy.optimize import fsolve
import numpy as np

# Solve x³ - x - 2 = 0
f = lambda x: x**3 - x - 2
root = fsolve(f, 1.5)  # Initial guess
print(f'Root of x³ - x - 2 = 0: x = {root[0]:.6f}')
"

Statistics with SciPy

# Descriptive statistics
python3 << 'EOF'
import numpy as np
from scipy import stats

data = [23, 45, 67, 32, 89, 12, 56, 78, 34, 91]

print(f"Mean: {np.mean(data):.2f}")
print(f"Median: {np.median(data):.2f}")
print(f"Std Dev: {np.std(data):.2f}")
print(f"Variance: {np.var(data):.2f}")

# Mode
mode_result = stats.mode(data)
print(f"Mode: {mode_result.mode}")
EOF

# Normal distribution
python3 -c "
from scipy.stats import norm

# P(X < 1.96) for standard normal
p = norm.cdf(1.96)
print(f'P(Z < 1.96) = {p:.4f}')  # ~0.975

# Find z-score for 95th percentile
z = norm.ppf(0.95)
print(f'95th percentile z-score: {z:.4f}')  # ~1.645
"

# Hypothesis testing
python3 -c "
from scipy import stats

# Two-sample t-test
group1 = [23, 25, 28, 30, 32]
group2 = [18, 22, 25, 27, 30]

t_stat, p_value = stats.ttest_ind(group1, group2)
print(f't-statistic: {t_stat:.4f}')
print(f'p-value: {p_value:.4f}')
"

Next Steps

All tools covered above are ready to use. Your workflow:

  • Antora docs: :stem: latexmath (MathJax renders in browser)

  • Formal papers: LaTeX or Typst

  • Interactive: Jupyter notebooks

  • Quick calculations: bc, awk, python one-liners

Low-level path:

  • C: Direct memory, pointers, bit manipulation

  • Rust: Safe systems programming with zero-cost abstractions

  • Assembly: x86_64 for understanding CPU instructions

  • Kernel: /usr/src/linux - read the networking stack