binaryencodingasciiutf-8tutorial

How Binary Code Works (and How to Translate It)

· Cosyslabs

Binary code is text and data represented as sequences of 0s and 1s, grouped into 8-bit bytes. To translate text to binary, a character encoding maps each character to one or more byte values, and each byte is written as eight binary digits. The letter H is 01001000.

What binary actually is

Binary is base 2. Decimal gives each digit position a power of ten — ones, tens, hundreds. Binary gives each position a power of two: 1, 2, 4, 8, 16, 32, 64, 128. A digit is either 0 or 1, so a position either contributes its value or contributes nothing. Eight of those positions make a byte, which holds 256 distinct values (0 through 255).

Binary is not a code for letters by itself. It is a way of writing numbers. Turning text into binary always involves a second step — deciding which number stands for which character. That decision is the character encoding.

Reading a byte

Take 01001000. Line the bits up against their place values, left to right:

Bit01001000
Place value1286432168421
Contributes064008000

Add the contributions: 64 + 8 = 72.

Now the second step. In ASCII — and in UTF-8, which agrees with ASCII for these values — 72 is the code point for H. So 01001000 is H.

Do the same for the rest of "Hi":

H  →  72  →  01001000
i  → 105  →  01101001

Check i the same way: 64 + 32 + 8 + 1 = 105.

Going the other direction is the same arithmetic in reverse. Divide the code point by two repeatedly and read the remainders bottom to top, or subtract the largest place value that fits and mark a 1 there.

Why ASCII is 7 bits

ASCII was standardized in 1963 for teleprinters, and it defines exactly 128 characters: code points 0 through 127. That range fits in 7 bits, because 2⁷ = 128.

ASCII covers the unaccented English alphabet in both cases, the digits, common punctuation, and 33 control characters such as newline (10) and tab (9). It covers nothing else — no é, no ß, no Greek, no Cyrillic, no CJK.

Storage has been byte-addressed for decades, so a 7-bit character is stored in an 8-bit byte with the top bit set to 0. That spare bit was originally a parity bit on serial links. In practice it means every ASCII character written in binary starts with a zero, which is a useful thing to notice when you are reading a dump.

Why UTF-8 is variable width

Unicode assigns a code point to well over 100,000 characters, far more than a byte can hold. UTF-8 encodes those code points into one to four bytes, and it does so in a way that keeps ASCII byte-for-byte identical.

Code point rangeBytesBit pattern
U+0000 – U+007F10xxxxxxx
U+0080 – U+07FF2110xxxxx 10xxxxxx
U+0800 – U+FFFF31110xxxx 10xxxxxx 10xxxxxx
U+10000 – U+10FFFF411110xxx 10xxxxxx 10xxxxxx 10xxxxxx

The leading bits are a length marker. A byte starting 0 is a complete one-byte character. A byte starting 110 says "two bytes, me and the next one". Every continuation byte starts 10, so it can never be mistaken for the start of a character — which is what lets a decoder resynchronize after a corrupt byte, and what lets strchr-style scans for ASCII characters work unchanged on UTF-8 text.

A non-ASCII character is more than one byte

This is where naive converters fall apart. Take é, code point U+00E9, decimal 233. It falls in the two-byte range, so its 11 significant bits are split across the 110xxxxx 10xxxxxx template:

é  → U+00E9 → 11000011 10101001   (0xC3 0xA9)

Two bytes, neither of which is 233. Now an emoji:

👋 → U+1F44B → 11110000 10011111 10010001 10001011   (0xF0 0x9F 0x91 0x8B)

Four bytes. So Hi 👋 — four characters — is seven bytes:

01001000 01101001 00100000 11110000 10011111 10010001 10001011
   H        i       space   ─────────── 👋 ───────────

Many binary translators get this wrong because they reach for charCodeAt() in JavaScript. That method returns UTF-16 code units, not UTF-8 bytes. For é it returns 233, producing the 8-bit group 11101001 — which is not valid UTF-8 and will not decode back. For 👋 it returns two surrogate halves, 55357 and 56395, each needing 16 bits. Neither result matches what xxd -b would show for the same text, and neither round-trips.

The fix is to encode to bytes first. TextEncoder produces UTF-8 bytes; TextDecoder reads them back. The Binary Translator uses both, which is why accents, CJK and astral-plane emoji survive a text → binary → text round trip.

Grouping, padding, and separators

Once you have bytes, you still have to decide how to write them down. Three conventions matter:

Padding. The byte 10 is 1010 in its shortest form and 00001010 padded to eight bits. Unpadded output is shorter but ambiguous — you cannot tell where one character ends and the next begins.

Separators. A space between bytes makes the boundaries explicit. Continuous output packs the bits together and relies on the reader knowing every character is exactly eight bits.

The combination that cannot work. Unpadded and unseparated is a one-way trip: 1010 followed by 1000001 becomes 10101000001, and nothing in that string says where to cut. The Binary Translator refuses that combination rather than producing output that will not decode.

Decoding applies the same rules in reverse. A continuous string must be a multiple of 8 bits, space-separated groups must each be 8 bits or fewer, and a run of bytes that is not valid UTF-8 — a continuation byte with no lead byte, a truncated four-byte sequence — is a genuine error rather than something to paper over with replacement characters.

Where you actually meet binary

  • Bitwise flags. Permissions, feature toggles and protocol headers pack several booleans into one byte. chmod 755 is three octal digits, each three bits: 111 101 101.
  • Network protocols. TCP flags, IP headers and TLS records are defined bit by bit in their RFCs.
  • File signatures. A PNG starts with the bytes 89 50 4E 47, a PDF with 25 50 44 46 (%PDF) — the magic numbers file-type detection uses.
  • Encoding bugs. When you see é where é should be, you are looking at UTF-8 bytes interpreted as Latin-1. à is 0xC3 and © is 0xA9 — the exact two bytes of é.

Binary, hex, and base 64

Binary is precise and unreadable at length. Hexadecimal is the usual compromise: each hex digit maps to exactly four bits, so one byte is always two hex characters and the mapping is mechanical.

01001000  →  0x48  →  H

Base64 goes further and regroups the bits — three bytes become four printable characters — at the cost of any obvious relationship between input byte and output character. See What is Base64 Encoding? for how that regrouping works, and the Hex ↔ ASCII Converter when hex is the more convenient view.

Try it now

Convert text to binary and binary back to text with the Binary Translator. It works over UTF-8 bytes, so emoji and accented characters round-trip exactly; you can switch between space-separated and continuous output, turn 8-bit padding on or off, and see the byte, bit and character counts for whatever you paste.

Summary

  • Binary is base 2; each position is a power of two, and eight positions make a byte
  • 01001000 is 64 + 8 = 72, which ASCII and UTF-8 both assign to H
  • ASCII defines 128 characters and fits in 7 bits, stored in a byte with a leading 0
  • UTF-8 is variable width: 1 byte for ASCII, 2 for é, 3 for most CJK, 4 for emoji
  • charCodeAt() returns UTF-16 code units, not UTF-8 bytes — which is why naive converters mangle anything outside ASCII
  • Unpadded and unseparated output cannot be decoded again

More tools from Cosyslabs

  • Unit Convert All — Convert data storage units (bits, bytes, KB, MB, GB) when you need to reason about how much a byte-level encoding actually costs.
  • PDF Convert All — Convert, merge and compress PDFs. PDF is a binary container with an ASCII header, which is why %PDF is readable in a hex dump.
  • Routine Toolkit — Everyday productivity utilities including a word counter, date calculator and number tools.
  • Cosyslabs — The studio behind Dev Tools !, Astrilio, CastFleet, and more.