Skip to content
ASCII World

How Computers Store Text: From Bits to Characters

Understanding physical voltage, abstraction layers, and modern encodings.

By the ASCII World team

The Hardware Level: Voltages, Bits, and Bytes

Modern x86-64 and ARM processors access system memory in addressable units of eight bits, known as bytes, rather than manipulating individual bits directly during standard memory read cycles. Digital hardware knows nothing about letters, punctuation, or glyphs. Memory arrays consist of billions of microscopic capacitors and field-effect transistors. At the physical layer, a voltage level above a defined threshold (such as 1.2 or 3.3 volts, depending on the hardware node) registers as a logical high state, or 1. A voltage level near ground registers as a logical low state, or 0.

When these states combine into eight-bit sequences, processors process them as unsigned integer values ranging from 0 (binary 00000000) to 255 (binary 11111111). Software engineers often write these binary patterns in hexadecimal notation to make them easier to read. For instance, binary 01000001 translates to 0x41 in hex or 65 in decimal. Without an explicit interpretation context provided by software, a byte containing 0x41 is indistinguishable from the integer 65, an opacity value in a graphics buffer, or a machine code instruction for an x86 register operation. Understanding binary representation forms the cornerstone of how software handles text at the hardware boundary.

Mapping Numbers to Symbols: ASCII and Fixed-Width Encoding

In May 1963, the American Standards Association published ASA X3.4-1963, establishing the original 7-bit ASCII standard to standardize teleprinter communication across vendors. ASCII defined 128 character codes, spanning decimal values 0 through 127. The standard allocated values 0 through 31, alongside 127, to control characters designed to command hardware devices. Code 10 specified a line feed (LF) to advance paper on a teletypewriter, while code 13 specified a carriage return (CR) to reset the print head position.

Values 32 through 126 were dedicated to printable characters, encompassing standard Latin letters, Arabic numerals, space, and common punctuation marks. Because telecommunication equipment and memory buses aligned around byte boundaries, computers stored 7-bit ASCII inside 8-bit memory locations. The eighth bit, known as the most significant bit (MSB), was often set to zero or repurposed as a parity bit to detect transmission errors over noisy serial lines.

In 7-bit ASCII, uppercase letter 'A' maps directly to decimal 65 (binary 00100001 with the eighth bit clear, or hex 0x41). Lowercase 'a' maps to decimal 97 (hex 0x61, binary 01100001). The difference between uppercase and lowercase ASCII letters is exactly 32, corresponding to the bit at position 5 (0-indexed). Flipping bit 5 converts ASCII characters between uppercase and lowercase with a simple bitwise XOR operation (char ^ 0x20).

The 8-Bit Fragmentation: Extended ASCII and Code Pages

IBM introduced Code Page 437 for the original IBM PC in 1981, using the top 128 byte values (128 through 255) to display box-drawing characters, accented vowels, and mathematical symbols. As computing spread globally, 128 standard ASCII slots proved inadequate for languages using non-English alphabets, diacritics, or non-Latin scripts. Hardware vendors exploited the unused eighth bit, expanding the available numeric range from 128 to 256 distinct values. This created extended ASCII implementations, but no single standard governed how values 128 through 255 were interpreted.

Microsoft developed code pages such as Windows-1252 for Western European languages and Windows-1251 for Cyrillic scripts. The International Organization for Standardization published the ISO/IEC 8859 family, including ISO 8859-1 (Latin-1). Under ISO-8859-1, byte value 0xE9 represented the lowercase accented letter 'é'. Under ISO-8859-5, that exact same byte value 0xE9 represented the Cyrillic letter 'щ'.

When a computer authored a document using one code page and another system rendered it using a different code page, text corruptions occurred. A document containing French accented text opened on a system configured for Cyrillic would display unreadable strings of characters. This breakdown of text interchange became known as mojibake, exposing the core limitation of 8-bit fixed-width encodings.

The Universal Standard: Unicode and Code Points

Version 1.0 of the Unicode Standard, published in October 1991, assigned abstract integer positions called code points to 7,161 distinct characters across 24 scripts. Unicode decoupled character identity from byte storage. Instead of assigning characters directly to raw disk bytes, Unicode assigns every character an abstract integer value known as a code point. Code points are conventionally written in hexadecimal with a U+ prefix, ranging from U+0000 to U+10FFFF. This architecture provides 1,114,112 possible code point slots, organized into 17 numerical planes of 65,536 code points each.

Plane 0, the Basic Multilingual Plane (BMP), covers code points from U+0000 to U+FFFF, housing scripts and symbols used in modern written communication. Planes 1 through 16, known as supplementary planes, store historical scripts, specialized symbols, mathematical notation, and emojis.

For example, the Latin letter 'A' is assigned code point U+0041. The Cyrillic capital letter 'А' is assigned code point U+0410. Even though both characters look identical in many fonts, Unicode treats them as distinct entities with separate numeric identities, resolving visual ambiguity in machine text processing.

Encoding Unicode: UTF-8, UTF-16, and UTF-32

Ken Thompson and Rob Pike designed UTF-8 during an evening at a New Jersey diner in September 1992, creating a byte-oriented stream format that maintains complete backward compatibility with 7-bit ASCII. A code point is an abstract number, not a layout of bytes on disk or in RAM. To store Unicode code points in memory or transfer them over a network, systems rely on character encoding schemes: UTF-8, UTF-16, and UTF-32.

UTF-32 uses a fixed width of 32 bits (4 bytes) for every single code point. Storing U+0041 requires four bytes: 0x00 0x00 0x00 0x41. While UTF-32 makes array indexing straightforward because character position directly correlates with byte offset, it quadruples text storage requirements for ASCII-heavy content and introduces endianness complications across CPU architectures.

UTF-16 uses variable-length encoding, representing BMP code points in 16 bits (2 bytes). Code points above U+FFFF are encoded using surrogate pairs, which combine two 16-bit code units (a high surrogate from D800 through DBFF and a low surrogate from DC00 through DFFF) to address higher planes. Operating systems like Windows and runtime environments like Java and JavaScript historically adopted UTF-16 internally.

UTF-8 uses a variable width of 1 to 4 bytes per code point. It encodes ASCII characters (code points U+0000 to U+007F) using a single byte identical to standard 7-bit ASCII, making every valid ASCII file a valid UTF-8 file. Code points between U+0080 and U+07FF use two bytes, U+0800 to U+FFFF use three bytes, and U+10000 to U+10FFFF use four bytes.

The layout of UTF-8 byte prefixes allows parsers to instantly determine the byte length of a character from its first byte:

Code Point Range Byte 1 Byte 2 Byte 3 Byte 4
U+0000..U+007F 0xxxxxxx - - -
U+0080..U+07FF 110xxxxx 10xxxxxx - -
U+0800..U+FFFF 1110xxxx 10xxxxxx 10xxxxxx -
U+10000..U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Continuation bytes always start with the bit pattern 10. This structural self-synchronization guarantees that if a transmission drops bytes, a UTF-8 decoder can scan forward to the next byte starting with 0 or 11 and safely resume parsing without corrupting subsequent text. Understanding these prefix patterns is central to how UTF-8 works in high-performance networking and storage engines.

References

  1. RFC 3629: UTF-8, a transformation format of ISO 10646
  2. ASA X3.4-1963: American Standard Code for Information Interchange
  3. The Unicode Standard, Version 15.0.0
  4. ISO/IEC 8859-1:1998 Information technology - 8-bit single-byte coded graphic character sets