Binary is the language of digital hardware, and hexadecimal is the shorthand professionals use to talk about it without losing their minds. Once you see why one hex digit equals exactly four bits, the conversions stop being arithmetic exercises and start being a lookup.
Why 16 is the right shorthand
Each hex digit represents one of sixteen values, and sixteen is two to the fourth. So one digit carries four bits:
1 hex digit = 4 bits · 2 hex digits = 8 bits = 1 byte
Converting 255 decimal: in binary that is 11111111 — eight digits, every one of which must be correct. In hex it is FF — two digits. Checking a 32-bit value in binary means scanning 32 characters; in hex it means scanning 8.
The conversion table is worth memorising in one direction only — hex digit to 4-bit pattern — because the other direction is just reading the bits in groups of four:
| Hex | Binary | Hex | Binary | Hex | Binary |
|---|---|---|---|---|---|
| 0 | 0000 | 4 | 0100 | 8 | 1000 |
| 1 | 0001 | 5 | 0101 | 9 | 1001 |
| 2 | 0010 | 6 | 0110 | A | 1010 |
| 3 | 0011 | 7 | 0111 | F | 1111 |
The number base converter does this in both directions, but the manual method is: to hex, divide by 16 and read the remainders up; to binary from hex, substitute each digit.
Worked examples
Decimal 100 to binary. Repeated division by 2: 100÷2=50 r0, 50÷2=25 r0, 25÷2=12 r1, 12÷2=6 r0, 6÷2=3 r0, 3÷2=1 r1, 1÷2=0 r1. Reading the remainders bottom-up: 1100100.
Decimal 100 to hex. 100÷16=6 r4, 6÷16=0 r6. Reading up: 64. And 6 = 0110, 4 = 0100, so 0x64 = 0110 0100 = 100. The two paths agree, which is the check.
0xFF to decimal. F = 1111, F = 1111, so 11111111 = 255. But if this is an 8-bit signed value, the leading 1 means negative — see below.
Why 0xFF is −1, not 255
This is where binary stops being counting and starts being a representation with rules. A computer needs one scheme that handles positive and negative numbers, and there are several. The one that won is two's complement:
- Zero is all zeros: 00000000.
- Positive numbers are ordinary binary: +5 is 00000101.
- Negative numbers: invert every bit of the positive version, then add 1.
So −1: start from +1 = 00000001, invert to 11111110, add 1 to get 11111111 = 0xFF. And −5: invert 00000101 to 11111010, add 1 to get 11111011 = 0xFB.
The elegance is that addition becomes subtraction. 5 + (−1) is 00000101 + 11111111 = 1 00000100, dropping the carry out of 8 bits gives 00000100 = 4. Correct, with no special case in the hardware. Every other scheme (sign-magnitude, one's complement) needs extra rules and fails at zero.
The range is asymmetric, and that is the point
In 8-bit two's complement the range is −128 to +127, not −127 to +127. One extra negative value comes from the fact that 10000000 = −128, while the symmetric +128 has no representation. The same shape repeats: 16-bit runs −32,768 to +32,767, 32-bit runs −2,147,483,648 to +2,147,483,647.
This is why the famous integer overflow bug of 1996, when the integer register in flight software was 16-bit and an altitude of 65,535 feet was assigned to a value too large to hold: 32767 + 1 became −32768, the system saw a large negative number, and the aircraft rolled over. The arithmetic was correct; the width was not.
Bytes, and the number that does not add up
A bit is one binary digit, a byte is 8 bits, and a byte spans exactly two hex digits — 0x00 to 0xFF. That is why memory dumps, hex editors and colour codes all work in pairs: #RRGGBB is three bytes, #RRGGBBAA is four with alpha.
Now the storage discrepancy. Manufacturers define a terabyte decimally (10^12 bytes) because it is a round number in the units they sell in. Operating systems report in binary multiples (2^40 = 1,099,511,627,776 bytes). So:
10^12 ÷ 2^30 = 931.3
A "1 TB" drive shows as 931 GB in every operating system, and nothing has gone missing. The IEC tried to fix this by naming the binary version TiB (tebibyte), so 1 TB = 10^12 bytes and 1 TiB = 1.0995 × 10^12 bytes. In practice most software still says "GB" for both, which is why the argument is endless. The data size converter handles both conventions explicitly.
Mixed radices in daily use
Once you have hex, other bases are a lookup rather than a mystery:
- Octal is base 8 — three bits per digit. It survives mainly in file permissions (
chmod 755) and in the escape sequences in C (is 10,Ais 'A'). - Base64 is not a number system at all — it maps bytes to printable ASCII so binary can travel through text-only channels like email. 3 bytes become 4 characters, which is the source of the 33% size increase. The Base64 calculator does the encoding.
- Colour hex codes are just bytes.
#1d4ed8is R=0x1d (29), G=0x4e (78), B=0xd8 (216) — the exact values from this site's own palette.
Bitwise operations, and why they are not arithmetic
Once you can read hex, the operations programmers actually use are only a handful, and one of them causes bugs more often than any other.
AND, OR, XOR, NOT, shifts. All operate on each bit independently — there is no carrying between positions, which is what makes them different from addition and multiplication.
- AND keeps a bit only if it is 1 in both. This is how you test a flag:
flags & 0x04is non-zero if bit 2 is set. - OR sets a bit if it is 1 in either. This is how you set a flag.
- XOR flips the bit. XOR with an all-ones mask (0xFF for a byte) is bitwise NOT, and XOR with itself is the classic way to zero a register without a memory write.
- Shifts move bits.
x << 1doubles,x >> 1halves, and the type of the variable decides whether the top bit is discarded or sign-extended.
The classic bug: x & 0x0F and x && 0x0F look nearly identical and are completely different. The first is a bitmask yielding a number 0-15. The second is a logical AND yielding true or false, and in most languages a non-zero right-hand side makes it true. Writing && where you meant & is the single most common bitwise typo, and it compiles in most languages.
Bit masks, and why 0xFF appears everywhere
A mask is a value that selects which bits you care about. 0xFF is 11111111 in eight bits, so x & 0xFF extracts the low byte of a larger value and discards everything above it. This is why you see it in code handling network protocols, image formats and binary files: it is the standard way to say "only this byte".
Related masks worth recognising at a glance:
- 0x0F — the low 4 bits, a single hex digit
- 0x1F — the low 5 bits, the largest value that fits in a signed 8-bit integer is 0x7F, and a 5-bit unsigned field maxes at 0x1F
- 0x7F — clears the sign bit, the standard way to get the absolute value of a small integer
- 0x80000000 — the sign bit of a 32-bit value, which is how
Math.absand integer negation are often implemented
Signed versus unsigned, and why the same bits mean different numbers
Eight bits can be read two ways. As unsigned, 0xFF is 255. As signed two's complement, it is −1. The bits are identical; the interpretation is a property of the type declaration, not the data.
This produces a classic failure. Read 255 from a file into an unsigned byte, pass it to a function expecting a signed 8-bit integer, and −1 appears where 255 was intended — a number that then propagates through arithmetic and usually surfaces much later as an implausible negative size or an out-of-range index. In C-family languages the fix is a cast, and the safest form is the explicit conversion: (uint8_t)value rather than relying on implicit narrowing.
Java and C# have no unsigned integers at all by default, which removes one class of bug and creates another: you cannot express a value above 2^63 − 1, and file formats that use unsigned 32-bit lengths must be read with Integer.toUnsignedLong or similar.
Floating point is not a number type
One more thing that surprises people who arrive from integer work: IEEE 754 floating point cannot represent most decimal fractions exactly. 0.1 has no exact binary representation, so 0.1 + 0.2 evaluates to 0.30000000000000004 rather than 0.3. This is not a bug, it is an inherent limit of binary representation.
It matters wherever exactness is expected — money most of all. Financial arithmetic is conventionally done in integer minor units (cents) or fixed-point decimal types precisely because floating point accumulates these tiny errors into visible ones. If your values must reconcile to the cent, do not use a float.
Frequently asked questions
What is the difference between & and &&?
& is bitwise AND, operating on each bit independently with no carrying, and returns a number. && is logical AND, returns true or false, and short-circuits. In code that means flags & 0x04 tests a bit, while flags && 0x04 merely asks whether flags is non-zero. The typo is common because the glyphs differ by one character.
How do I convert a negative number to hex?
Convert its two's complement representation, not its magnitude. For -1 in one byte, take +1 (0x01), invert to 0xFE, add 1 to get 0xFF. In general, compute the positive value as a power-of-two width, subtract it, and convert the absolute value of the result.
What is a hex mask for?
Selecting specific bits from a value while discarding the rest. 0xFF keeps the low byte, 0x0F keeps the low nibble, 0x7F clears the sign bit. It is the standard technique for reading structured binary formats, setting and clearing flags, and checking permissions.
Why does 0.1 + 0.2 not equal 0.3 in code?
Because binary has no exact representation for most decimal fractions, and IEEE 754 floating point is binary. 0.1 is stored as the nearest binary fraction, 0.2 likewise, and their sum lands on the nearest float to 0.3, which is slightly above it. For money, use integer minor units or a decimal type rather than a float.
Frequently asked questions
How do I convert decimal to binary?
Divide by 2 repeatedly and read the remainders bottom to top. 100 gives 1100100. For large numbers, or numbers with fractions, the division approach stops being convenient and you should use hex as the intermediate.
Why is hexadecimal easier than binary?
Because one hex digit carries exactly four bits, so a byte is two hex digits. 255 decimal is 11111111 in binary — eight digits to check — but FF in hex, two digits. Long division and long multiplication both work in groups of four bits rather than one.
What is two's complement?
The standard way to represent negative numbers in binary. The pattern is: invert every bit of the positive number's representation, then add 1. In 8-bit two's complement, +1 is 00000001, so -1 is 11111111 = 0xFF, not 255. It is the only symmetric scheme that makes addition work without extra logic.
Why does a 1 TB drive show as 931 GB?
Because manufacturers use decimal units (1 TB = 10^12 bytes) while operating systems use binary units (1 TiB = 2^40 bytes). 10^12 divided by 2^30 is 931.3. Nothing is missing — the two labels just count differently, which is why the IEC prefix TiB exists.