ANSI is the American National Standards Institute. An encoding defined from that family is also called MBCS (Multi-Byte Character Set).

At first, the Internet had only one character set: the ASCII character set from ANSI. It uses 7 bits for a character, for 128 characters in all, including English letters, digits, and punctuation. It was later extended to 8 bits, for 256 characters, mainly by adding special symbols to the original 7-bit set. As other languages were added, ASCII was no longer enough. Each country defined its own character set on top of ASCII. These sets derived from the ANSI standard are commonly called ANSI character sets. Their formal name is MBCS (Multi-Byte Character System). They keep ASCII values 0-127 and stay compatible with 7-bit ASCII. A byte greater than 128 is a leading byte, and the second byte (and sometimes a third) that follows it forms the actual code together with that leading byte. There are many such character sets. GB2312 is one of them.

Different ANSI encodings are not compatible with each other. Text in two languages cannot be stored in the same ANSI-encoded string. A serious drawback is that the same code value can mean different characters in different encodings. That confusion led to Unicode. Unicode assigns one encoding to every writing system, so the same character no longer has conflicting codes.

Character set: assigns a unique numeric ID to each character (a code point).
Encoding: the rules that turn a code point into a byte sequence.

Unicode code points are divided into 17 planes. Each plane contains 2^16 (65,536) code points. The code points of the 17 planes run from U+xx0000 to U+xxFFFF, where xx is a hexadecimal value from 00 to 10.
17 * 65,536 = 1,114,112, so Unicode can represent 1,114,112 different characters, each with its own number.

Another important idea is the code unit, the smallest number of bytes used to encode a character. UTF-8 needs at least one byte, UTF-16 at least two, UCS-2 two, and UCS-4 and UTF-32 four. UTF-8 and UTF-16 are variable-length encodings. UCS-2, UCS-4, and UTF-32 are fixed-length encodings.

UTF-32 and UCS-4
Before Unicode and ISO 10646 were merged, ISO 10646 defined a 31-bit form for the Universal Coded Character Set (UCS-4). Each code uses 4 bytes, and the space runs from 0x00000000 to 0x7FFFFFFF (more than 2 billion characters). UCS-4 has more than 2 billion code positions, but actual use does not go past 0x10FFFF. To stay compatible with Unicode, ISO also promised not to assign UCS-4 codes above 0x10FFFF. UTF-32 uses the same values as UCS-4, but its space is limited to 0 through 0x10FFFF. UTF-32 is a subset of UCS-4.

UTF-16 and UCS-2
Besides UCS-4, ISO 10646 defined a 16-bit form (UCS-2). Each code uses 2 bytes and has 65,536 positions (enough for the 63K most common characters worldwide; 0xD800-0xDFFF is left unused for Unicode compatibility). For example, the UCS-2 code of “汉” is 6C49. Two bytes are not enough to be truly universal, so UTF-16 was created. Like UCS-2, it uses two bytes for the 63K most common characters. Unlike UCS-2, it uses 4 bytes for less common characters. UTF-16 is a variable-length encoding.

Unicode code points are divided into 17 planes, each with 2^16 (65,536) code points. The first plane is the Basic Multilingual Plane (BMP). The others are supplementary planes. In the BMP (0-0xFFFF), 0xD800-0xDFFF is reserved and unused. UCS-2 can encode only BMP characters. For those characters, UTF-16 and UCS-2 use the same values (the Unicode code point itself). For example, “汉” has code point 6C49 and UTF-16 code 6C49. UTF-16 can also use the reserved range 0xD800-0xDFFF to encode supplementary-plane characters, so it can encode every Unicode character.

How does UTF-16 encode a supplementary plane?
Unicode code points run from 0 to 0x10FFFF. Outside the BMP, 0xFFFFF code points remain, all greater than or equal to 0x10000. Subtract 0x10000 from a supplementary-plane code point and the result is in 0-0xFFFFF, which fits in 20 bits. Add 0xD800 to the high 10 bits to get the first two bytes of the UTF-16 code. Add 0xDC00 to the low 10 bits to get the last two bytes. For example: The character “𪺫” has Unicode code point 2AEAB. Subtract 0x10000 to get 1AEAB (binary 0001 1010 1110 1010 1011). The high 10 bits plus D800 give D86B, and the low 10 bits plus DC00 give DEAB. The UTF-16 code is D86BDEAB in big-endian order, or 6BD8ABDE in little-endian order.

Print the character “𪺫” in C#:
string hex = "6BD8ABDE";
byte[] hexByteArr = Enumerable.Range(0, hex.Length)
                                    .Where(x => x % 2 == 0)
                                    .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                                    .ToArray();
string word = UnicodeEncoding.Unicode.GetString(hexByteArr);

UTF-8
UTF-16, UTF-32, UCS-2, and UCS-4 all use more than one byte for every character, which wastes bandwidth for English text. UTF-8 avoids that. ASCII characters keep their ASCII values and use one byte. Other characters use 2, 3, or 4 bytes.

UTF-8 encoding rules:
(1) Symbols in ASCII use a single byte, and the value is the same as the ASCII value. ASCII values run from 0 to 0x7F, so the first bit of every one-byte code is 0. That bit separates one-byte codes from multi-byte codes.
(2) Other characters use several bytes (N bytes). The first N bits of the first byte are 1, bit N+1 is 0, and the first two bits of each of the remaining N-1 bytes are 10. The other bits store the Unicode code point.

Bytes Unicode Decimal UTF-8
1 000000-00007F 0-127 0xxxxxxx
2 000080-0007FF 128-2047 110xxxxx 10xxxxxx
3 000800-00FFFF 2048-65535 1110xxxx 10xxxxxx 10xxxxxx
4 010000-10FFFF 65536-1114111 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
For values above ASCII, the leading 1 bits of the first byte tell how long the character is. 110xxxxx means a 2-byte character. 1110xxxx means a 3-byte character, and so on. The x bits are filled with the binary code point. In a multi-byte sequence, the number of leading 1s in the first byte is the number of bytes in the sequence.

UTF stands for Unicode Transformation Format: the way a Unicode number is turned into program data.
UTF-8, UTF-16, and UTF-32 are all derived from Unicode so that the same characters can be stored or transmitted in different ways. Each of them can represent every character in the Unicode standard. UTF-8 and UTF-16 are widely used. UTF-32 is rarely used.

UTF-8, UTF-16, UTF-32, UCS-2, and UCS-4
Comparison UTF-8 UTF-16 UTF-32 UCS-2 UCS-4
Code space 0-10FFFF 0-10FFFF 0-10FFFF 0-FFFF 0-7FFFFFFF
Minimum bytes 1 2 4 2 4
Maximum bytes 4 4 4 2 4
Depends on byte order No Yes Yes Yes Yes

Byte order mark (BOM)
Unicode BOM
Encoding BOM Endian Decimal Decimal byte[]
UTF-7 2B 2F 76 38 2D endianness 185479870509 43,47,118,56,45 43,47,118,56,45
UTF-8 EF BB BF endianness 15711167 239,187,191 -17,-69,-65
UTF-16-LE FF FE little endian 65534 255,254 -1,-2
UTF-16-BE FE FF big endian 65279 254,255 -2,-1
UTF-32-LE FF FE 00 00 little endian 4294836224 255,254,0,0 -1,-2,0,0
UTF-32-BE 00 00 FE FF big endian 65279 0,0,254,255 0,0,-2,-1
UTF-8 encodes in bytes, so byte order does not apply and a BOM is not required to show order. A BOM can still mark the encoding.
The UTF-8 encoding of the character ZERO WIDTH NO-BREAK SPACE is EF BB BF.

For more on byte order, see Endianness on Wikipedia.


Unicode misconceptions
  • Unicode is just a 16-bit encoding. Some people think every character takes 16 bits, so there are only 65,536 possible characters. That is not true. It is the most common misunderstanding about Unicode.
  • Can any unassigned code point be used internally? No. Unassigned positions will eventually be given to a character. Use private-use code points or noncharacter code points.
  • Does every Unicode code point represent a character? No. There are noncharacter code points (FFFE, FFFF, 1FFFE, and so on), surrogate code points, private-use code points, unassigned code points, and control and format characters (RLM, ZWNJ, and so on).
  • Is character mapping one-to-one? No. A mapping can also be:
    • One-to-many: (ß → SS)
    • Context-dependent: (…Σ ↔ …ς and …ΣΤ… ↔ …στ…)
    • Language-dependent: (I ↔ ı and İ ↔ i)