digiwleeaLearn
Open the lab →

ASCII and character encoding

How text becomes numbers

7 min read

ASCII is a character encoding that assigns every letter, digit, punctuation mark, and control code a 7-bit number from 0 to 127, so text is stored as a sequence of byte codes: 'A' is 65 (0x41), '0' is 48 (0x30), and 'a' is 97 (0x61).

You can read a row of bits as a binary number and write it compactly in hexadecimal. But a computer's memory holds only numbers, so how does it store the letter A, or a comma, or a space? The answer is a codebook: agree on a number for every character, and text becomes a list of those numbers. ASCII (the American Standard Code for Information Interchange) is the classic codebook, and it is the reason A is stored as the number 65. Encode any text to its ASCII bytes (and back) with the text to binary tool.
Think of a hotel with numbered rooms. The guest in room 65 is the character A; room 97 holds a; room 48 holds the digit character 0. Storing the word Hi is just writing down its room numbers in order. Nothing about the bits *looks* like a letter; the meaning is entirely the shared agreement that code 65 means A. Look up any character, code, or control name in the ASCII table.

One number per character (7 bits, 128 codes)

ASCII uses 7 bits, so it has 2^7 = 128 codes, numbered 0 to 127. That is enough for the uppercase letters, the lowercase letters, the ten digit characters, common punctuation, and a block of invisible control codes (like newline and tab). Each character maps to exactly one code, and each code to one character. In practice a character is stored in a whole byte (8 bits) with the top bit b7 left 0, so an ASCII code always looks like 0xxx xxxx.
characterdecimalhexbinary (byte)
NUL00x000000 0000
LF (newline)100x0A0000 1010
space320x200010 0000
0480x300011 0000
9570x390011 1001
A650x410100 0001
Z900x5A0101 1010
a970x610110 0001
z1220x7A0111 1010
A slice of the ASCII table. Codes 0 to 31 are invisible control codes (NUL is 0, newline LF is 10). The printable characters start at space (32). Notice the byte's top bit is always 0: an ASCII code never exceeds 0111 1111 (127).

The ranges are laid out on purpose

ASCII is not a random shuffle. The characters are grouped into tidy, contiguous ranges, and the gaps between the ranges are chosen so common conversions become simple arithmetic:
  • Control codes 0 to 31 (0x00 to 0x1F): non-printing signals like NUL (0), tab (9), and newline LF (10).
  • Digits '0' to '9' are 48 to 57 (0x30 to 0x39): consecutive, in order.
  • Uppercase 'A' to 'Z' are 65 to 90 (0x41 to 0x5A).
  • Lowercase 'a' to 'z' are 97 to 122 (0x61 to 0x7A), sitting exactly 32 codes above their uppercase twins.

The case bit and the digit trick

Because uppercase starts at 65 and lowercase starts at 97, every letter's two cases differ by exactly 32, which is 0x20. And 32 is 2^5, so it is a single bit: bit 5. Flip bit 5 and you swap case. 'A' is 0100 0001 and 'a' is 0110 0001; the only difference is that one lit bit.
'A'
= 65
= 0x41, 'a'
= 97
= 0x61, 0x61 - 0x41
= 0x20
= 32
= bit 5 (uppercase + 0x20 = lowercase)
The digit characters have their own shortcut. Since '0' is code 48 (0x30), the character '7' is code 55, and 55 - 48 = 7 recovers the actual number the character shows. **Subtracting 0x30 turns a digit character into its numeric value**, which is exactly what a program does when it reads a typed number one character at a time.
'0'
= 48
= 0x30, '7'
= 55
= 0x37, '7' - '0'
= 55 - 48
= 7 (digit character minus 0x30 = its value)

Worked example: encoding a word

Encode the word Hi into bytes. Look up each character's code, then write it in hex and binary:
  1. H is the 8th uppercase letter, so its code is 65 + 7 = 72, which is 0x48, or 0100 1000 as a byte.
  2. i is the 9th lowercase letter, so its code is 97 + 8 = 105, which is 0x69, or 0110 1001 as a byte.
  3. So Hi is the two-byte sequence 72 105, written 0x48 0x69, or the bits 0100 1000 0110 1001.
ASCII is why text can travel through hardware that only understands numbers. A key press, a byte in RAM, a character on a bus, and a pixel-drawn glyph are all tied together by the code table: the keyboard sends 65, memory stores 65, and the display font draws the shape for 65. It also shows the payoff of a well-designed encoding: laying the ranges out in order turns case conversion and digit reading into one subtraction instead of a lookup table.
The byte figure below lets you dial in any 8-bit code by hand. Set bit b6 and bit b0 (the pattern 0100 0001, which is 0x41) to spell the code for A; flip bit b5 on as well (0110 0001, 0x61) to turn it into a.
One byte holding a character code: set the eight bits b7-b0 and read them back on the probes. Dial 0100 0001 (0x41) for A, then turn on b5 (the 0x20 case bit) to get 0110 0001 (0x61), the code for a. Open it in the lab and try a few characters from the table.
Common mistakes. The character '7' (code 55) is not the number 7; to get the value you subtract '0' (48), and forgetting that is a classic bug when parsing typed digits. Do not assume one byte is always one character either: that is only true for plain ASCII. And remember ASCII is a 7-bit code (0 to 127); the top bit of the byte is spare, and different systems have used it for different "extended" characters, which is one reason a single agreed 8-bit character set never really existed. Finally, control codes like NUL (0) and newline (10) are real characters in the stream even though they print nothing.
Modern text uses UTF-8, a variable-length encoding whose one-byte codes are exactly ASCII (0 to 127), so every ASCII file is already valid UTF-8, while characters beyond ASCII (accented letters, other scripts, emoji) use two to four bytes each. ASCII is the foundation UTF-8 was built to extend.
Try it
The character 'B' has ASCII code 66. Using the case bit, what is the code for 'b'? And using the digit trick, what value does the digit character '9' (code 57) represent?
You now know how text becomes bits: pick a code table, and every character is just a number your circuits can store and move. Next in the number track are codes built to *survive* mistakes, error detection and correction, and the arithmetic tracks put these numbers to work: adding, subtracting, and multiplying them in hardware.

Frequently asked

What is ASCII?

ASCII (American Standard Code for Information Interchange) is a character encoding that assigns every letter, digit, punctuation mark, and control code a 7-bit number from 0 to 127. Text is stored as a sequence of these codes, for example A is 65 (0x41) and a is 97 (0x61). It is usually stored one character per byte, with the byte's top bit 0.

What is the ASCII code for 'A'?

The ASCII code for A is 65, written 0x41 in hex or 0100 0001 in binary. Uppercase letters run from A = 65 to Z = 90. The lowercase a is 97 (0x61), exactly 32 more, because uppercase and lowercase differ by the single bit worth 0x20.

How do you convert a character to a number in ASCII?

Look the character up in the ASCII table to get its code. For digit characters there is a shortcut: subtract the code of '0' (48, or 0x30). For example '7' is code 55, and 55 - 48 = 7, so subtracting 0x30 turns the digit character into the value it shows. To swap a letter's case, flip the bit worth 0x20 (32): uppercase plus 0x20 gives lowercase.

What is the difference between ASCII and Unicode (UTF-8)?

ASCII is a 7-bit code with only 128 characters, enough for English letters, digits, and punctuation. Unicode assigns a number to essentially every character in every writing system, and UTF-8 is the common way to store those numbers using one to four bytes each. UTF-8's one-byte codes are exactly ASCII (0 to 127), so ASCII text is already valid UTF-8, but characters beyond ASCII need more than one byte.

Every lesson here builds toward one thing: a working CPU, from the transistor up.

Open the free lab →
Found this useful? Share itShare on X