Radix Cheat Sheet & Bit-Chunking Mental Math
In software development, converting between Binary, Octal, Decimal, and Hexadecimal rarely requires arithmetic division. Because 8 (2³) and 16 (2⁴) are exact powers of 2, bits map directly to single digits:
| Decimal | 4-Bit Binary (Nibble) | Hexadecimal | 3-Bit Binary | Octal |
|---|---|---|---|---|
| 0 | 0000 | 0 | 000 | 0 |
| 1 | 0001 | 1 | 001 | 1 |
| 2 | 0010 | 2 | 010 | 2 |
| 3 | 0011 | 3 | 011 | 3 |
| 4 | 0100 | 4 | 100 | 4 |
| 5 | 0101 | 5 | 101 | 5 |
| 6 | 0110 | 6 | 110 | 6 |
| 7 | 0111 | 7 | 111 | 7 |
| 8 | 1000 | 8 | — | — |
| 9 | 1001 | 9 | — | — |
| 10 | 1010 | A | — | — |
| 11 | 1011 | B | — | — |
| 12 | 1100 | C | — | — |
| 13 | 1101 | D | — | — |
| 14 | 1110 | E | — | — |
| 15 | 1111 | F | — | — |
Group bits into 4-element nibbles from right to left. Each 4-bit chunk translates directly into one hex character:
Convert without base 10 arithmetic by expanding hex to binary and regrouping into 3-bit slices:
Two's Complement Debugging: The High-Nibble Sign Rule
CPUs do not store a separate minus sign (-). Negative numbers are encoded using Two's Complement, where the most significant bit (MSB) has negative weight. This allows arithmetic addition circuits to handle subtraction directly: A - B = A + (~B + 1).
In any fixed-width hex dump, look at the very first hexadecimal digit:
- 8, 9, A, B, C, D, E, F: Top bit is 1 → Negative in signed mode.
- 0, 1, 2, 3, 4, 5, 6, 7: Top bit is 0 → Positive or Zero.
When widening a signed integer (e.g. int8_t → int32_t), the CPU copies the MSB into all newly added upper bits to preserve the negative value:
| Decimal Value | 8-Bit Binary | 8-Bit Hex | 16-Bit Hex | 32-Bit Hex | Hardware Interpretation |
|---|---|---|---|---|---|
| +127 (Max Int8) | 0111 1111 | 0x7F | 0x007F | 0x0000 007F | Highest positive 8-bit signed value |
| +1 | 0000 0001 | 0x01 | 0x0001 | 0x0000 0001 | Unit increment |
| 0 | 0000 0000 | 0x00 | 0x0000 | 0x0000 0000 | Single unambiguous zero |
| -1 | 1111 1111 | 0xFF | 0xFFFF | 0xFFFF FFFF | All bits active (all 1s) |
| -42 | 1101 0110 | 0xD6 | 0xFFD6 | 0xFFFF FFD6 | Bitwise inversion + 1 (~42 + 1) |
| -128 (Min Int8) | 1000 0000 | 0x80 | 0xFF80 | 0xFFFF FF80 | Lowest negative 8-bit boundary |
Word-Length Register Boundaries & Overflow Wrap-Around
In physical hardware, registers operate on fixed bit widths. When an operation exceeds register limits, high bits are truncated modulo 2ᵟ, wrapping values around. Use the table below to verify data type bounds:
| Register Width | Unsigned Range [0, 2ᴺ - 1] | Signed Range [-2ᴺ⁻¹, 2ᴺ⁻¹ - 1] | Max Hex Value | Language Types (Rust / C / Go) |
|---|---|---|---|---|
| 8-bit (Byte) | 0 to 255 | -128 to +127 | 0xFF | u8 / i8, uint8_t / int8_t, byte |
| 16-bit (Word) | 0 to 65,535 | -32,768 to +32,767 | 0xFFFF | u16 / i16, uint16_t / int16_t |
| 32-bit (DWord) | 0 to 4,294,967,295 | -2,147,483,648 to +2,147,483,647 | 0xFFFF FFFF | u32 / i32, uint32_t / int32_t |
| 64-bit (QWord) | 0 to 18,446,744,073,709,551,615 | -9,223,372,036,854,775,808 to +9,223,372,036,854,775,807 | 0xFFFF FFFF FFFF FFFF | u64 / i64, uint64_t / int64_t |
| Auto (BigInt) | 0 to ∞ (Memory bound) | -∞ to +∞ (Memory bound) | Dynamic | BigInt (JS), int (Python 3), big.Int (Go) |
When adding 1 to a signed 32-bit maximum (0x7FFF FFFF / +2,147,483,647), the binary value increments to 0x8000 0000. Because the MSB becomes 1, the value wraps around to the lowest negative boundary: -2,147,483,648. Selecting fixed bit widths in our tool models this exact register truncation.
Base 36 in Real-World Systems: URL Slugs and Database ID Compression
While binary and hex dominate low-level memory inspection, higher radices solve data storage and transmission constraints:
Case-insensitive representation using all 10 digits and 26 English letters. Because it never produces percent-encoding artifacts (%2B, %2F), it is the standard for compressing auto-incrementing integer IDs into clean URL slugs (e.g. Reddit comment keys, shortened link tokens).
Uses 32 characters (A–Z, 2–7), omitting visually ambiguous characters (0/O, 1/I/L, 8/B). Engineered specifically for human manual entry, such as Google Authenticator 2FA secret setup keys and Tor .onion addresses.
Unlike Base 36 which represents a single numeric integer, Base 64 is a byte-serialization scheme. It packs 6 bits per character to safely transmit arbitrary binary images, tokens, and cryptographic keys over 7-bit ASCII protocols (HTTP headers, JSON, MIME email).
Programming Language Numeric Literals & Bitmasking Reference
Compilers interpret raw numbers as base 10 by default. Use standardized prefixes to declare non-decimal literal constants:
| Language | Binary Prefix | Octal Prefix | Hex Prefix | Digit Separators |
|---|---|---|---|---|
| JavaScript / TypeScript | 0b / 0B | 0o / 0O | 0x / 0X | Underscore: 0xFF_FF |
| Python 3 | 0b / 0B | 0o / 0O | 0x / 0X | Underscore: 0b1111_0000 |
| Rust | 0b | 0o | 0x | Underscore: 0xDEAD_BEEF |
| Go (Golang) | 0b / 0B | 0o / 0O | 0x / 0X | Underscore: 0_644 |
| C / C++ | 0b / 0B (C++14 / C23) | 0 (legacy) / 0o (C23) | 0x / 0X | Single quote (C++): 0xFF'FF |
flags |= (1 << n)flags &= ~(1 << n)flags ^= (1 << n)(flags & (1 << n)) != 0Production Code Snippets Across Languages
Practical methods to parse and format integers across radices (2 to 36) in production backends:
// BigInt converts losslessly to any radix string from 2 to 36
const val = 3735928559n;
const hex = val.toString(16); // "deadbeef"
const b36 = val.toString(36); // "1ps9wxb"
// Parse strings back to integer
const parsedHex = BigInt('0x' + hex); // 3735928559n
// Simulate 8-bit Two's Complement signed wrap-around
const signed8 = BigInt.asIntN(8, -42n); // -42n
const unsigned8 = BigInt.asUintN(8, -42n); // 214n (0xD6)# Format integer to binary, octal, hex
num = 3735928559
bin_str = bin(num) # '0b11011110101011011011111011101111'
hex_str = hex(num) # '0xdeadbeef'
# Parse strings of any radix from 2 to 36
val_from_hex = int("DEADBEEF", 16) # 3735928559
val_from_b36 = int("1ps9wxb", 36) # 3735928559package main
import (
"fmt"
"strconv"
)
func main() {
val := int64(3735928559)
// FormatInt supports any base from 2 to 36
hexStr := strconv.FormatInt(val, 16) // "deadbeef"
b36Str := strconv.FormatInt(val, 36) // "1ps9wxb"
// Parse with explicit base
parsed, _ := strconv.ParseInt(hexStr, 16, 64)
fmt.Println(parsed) // 3735928559
}fn main() {
let val: u32 = 0xDEAD_BEEF;
// Format strings
let bin_str = format!("{:b}", val);
let hex_str = format!("{:X}", val); // "DEADBEEF"
// Parse from radix (2 to 36)
let parsed = u32::from_str_radix("DEADBEEF", 16).expect("valid hex");
assert_eq!(val, parsed);
}Frequently Asked Questions
Why does my hex output show 0xFF or 0xFFFFFFFF instead of a negative sign for -1?
Computer memory and processor registers do not store independent "-" glyphs. Negative numbers are stored in Two's Complement notation. In Two's Complement, -1 is represented by setting every bit in the word to 1 (all bits active). In an 8-bit register, all 1s equals binary 1111 1111, which displays as hexadecimal 0xFF. In a 32-bit register, 32 consecutive 1s equals 0xFFFF FFFF. When you toggle the "Signed" option in this converter, it interprets the most significant bit (MSB) as negative weight, correctly resolving 0xFF to -1 in 8-bit mode.
How can developers instantly tell if a hexadecimal number represents a negative value?
Inspect the leading (most significant) hexadecimal digit. Because each hex character represents 4 binary bits (8, 4, 2, 1), any hex character from 8 through F has its highest bit set to 1 (8 = 1000₂, 9 = 1001₂, A = 1010₂, ..., F = 1111₂). In signed two's complement registers, a leading MSB of 1 signifies a negative value. If the leading digit is 0 through 7 (where the MSB is 0), the number is positive or zero.
What is the practical difference between arithmetic shift (>>) and logical shift (>>>)?
In binary bitwise manipulation, an arithmetic right shift (>>) preserves the sign bit: if the number is negative (MSB is 1), it fills newly vacated bit positions on the left with 1s (sign extension). A logical right shift (>>>) ignores signedness and unconditionally shifts in 0s from the left, transforming negative numbers into large positive unsigned integers. For example, in 32-bit: (-42 >> 1) remains -21, whereas (-42 >>> 1) yields 2,147,483,627.
Why does native JavaScript lose precision for integers above 53 bits without BigInt?
Standard JavaScript Number primitives follow IEEE-754 double-precision floating-point format, allocating 53 bits to the significand (mantissa), 11 bits to the exponent, and 1 sign bit. Integers greater than Number.MAX_SAFE_INTEGER (9,007,199,254,740,991, or 2⁵³ - 1) cannot be uniquely represented and suffer rounding collapse. BigInt bypasses the floating-point unit by storing arbitrary-precision integers as dynamically allocated arrays of 64-bit machine limbs, ensuring lossless conversion across 64-bit, 128-bit, and 256-bit keys.
Why do UNIX file permissions (chmod 755) map cleanly to octal numbers?
UNIX file security divides access permissions into three discrete bit flags—Read (4, 2²), Write (2, 2¹), and Execute (1, 2⁰)—across three target entities: User (Owner), Group, and Other. Because 2³ = 8, each 3-bit permission cluster exactly matches a single octal digit from 0 (---) to 7 (rwx). A mode string like rwxr-xr-x breaks down into [111] (7), [101] (5), and [101] (5), eliminating bit-shifting conversions.
How does sign extension work when casting an 8-bit integer into a 32-bit register?
When an 8-bit signed integer is widened to 32 bits, the hardware must preserve its negative mathematical value. If the 8-bit value is -42 (binary 1101 0110, hex 0xD6), zero-extending it would produce 0x0000 00D6 (+214). Sign extension copies the 8th bit (1) across all upper 24 bits, producing 0xFFFF FFD6 (binary 1111...1101 0110), which correctly evaluates to -42 in 32-bit two's complement.
Why is Base 36 preferred over Base 64 for database sequential IDs and URL slugs?
Base 36 uses digits 0–9 and Latin letters A–Z (36 characters) and is completely case-insensitive. This prevents human transcription confusion and guarantees that URLs remain clean and readable without percent-encoding (%2B, %2F). While Base 64 is more compact, its case-sensitivity (distinguishing "a" from "A") and inclusion of symbols (+, /, =) can cause routing collisions on case-insensitive filesystems or require URL-safe variants (RFC 4648 Base64URL).
How do bitmasking operations use hexadecimal constants like 0x0F and 0xFF?
Because one hexadecimal digit represents exactly 4 binary bits (one nibble) and two hex digits represent an 8-bit byte, hexadecimal provides a visual template for bitmasks. The constant 0x0F (binary 0000 1111) masks out the upper 4 bits and extracts the lower nibble. The constant 0xFF (binary 1111 1111) extracts individual bytes from 32-bit words, such as extracting RGB color channels: (color >> 16) & 0xFF extracts the Red byte.
