Encode::Unicode(3perl)

🌸 NAME 🌸

Encode::Unicode β€” Various Unicode Transformation Formats

that's it, that's the whole vibe. she does Unicode Transformation Formats and she does them well. πŸ’…


✨ SYNOPSIS ✨

use Encode qw/encode decode/;
$ucs2 = encode("UCS-2BE", $utf8);
$utf8 = decode("UCS-2BE", $ucs2);

like yes bestie, encode in, decode out, that's the whole friendship. 🫢


πŸ¦‹ ABSTRACT πŸ¦‹

okay so here's the deal: this module implements all the Character Encoding Schemes of Unicode that are officially documented by the Unicode Consortium. the one glaring exception is UTF-8, obviously, and that's because UTF-8 is a native format in perl. she was born here. she doesn't need an implementation. periodt. πŸ‘‘

and <http://www.unicode.org/glossary/> straight up says, and I quote:

Character Encoding Scheme A character encoding form plus byte serialization. There are Seven character encoding schemes in Unicode: UTF-8, UTF-16, UTF-16BE, UTF-16LE, UTF-32 (UCS-4), UTF-32BE (UCS-4BE) and UTF-32LE (UCS-4LE), and UTF-7. Since UTF-7 is a 7-bit (re)encoded version of UTF-16BE, It is not part of Unicode's Character Encoding Scheme. It is separately implemented in Encode::Unicode::UTF7. For details see Encode::Unicode::UTF7.

so yeah, UTF-7 is a whole separate girl β€” she lives in Encode::Unicode::UTF7, not here. go check her out if that's your thing. πŸ¦‹

πŸ”₯ Quick Reference πŸ”₯

here's the cheat sheet, screenshot it, put it on your wall, whatever:

                       Decodes from ord(N)           Encodes chr(N) to...
              octet/char BOM S.P d800-dfff  ord > 0xffff     \x{1abcd} ==
         ---------------+-----------------+------------------------------
         UCS-2BE       2   N   N  is bogus                  Not Available
         UCS-2LE       2   N   N     bogus                  Not Available
         UTF-16      2/4   Y   Y  is   S.P           S.P            BE/LE
         UTF-16BE    2/4   N   Y       S.P           S.P    0xd82a,0xdfcd
         UTF-16LE    2/4   N   Y       S.P           S.P    0x2ad8,0xcddf
         UTF-32        4   Y   -  is bogus         As is            BE/LE
         UTF-32BE      4   N   -     bogus         As is       0x0001abcd
         UTF-32LE      4   N   -     bogus         As is       0xcdab0100
         UTF-8       1-4   -   -     bogus   >= 4 octets   \xf0\x9a\af\8d
         ---------------+-----------------+------------------------------

that's giving ⭐ main character table ⭐ ngl.


🌈 Size, Endianness, and BOM 🌈

so you can sort every one of these CES into three little boxes, and those boxes are: size of each character, endianness, and Byte Order Mark. three criteria, one glow-up. ✨

πŸŽ€ by size πŸŽ€

  • UCS-2 is a fixed-length encoding where each character takes 16 bits. she does not support surrogate pairs, she refuses, she's not doing it. when a surrogate pair shows up during decode(), its place gets filled with \x{FFFD} if CHECK is 0, or the routine croaks if CHECK is 1. same energy for a character whose ord value is bigger than 0xFFFF β€” filled with \x{FFFD} if CHECK is 0, croaks if CHECK is 1. no in-between, bestie. πŸ’€
  • UTF-16 is almost the same as UCS-2 except she actually supports surrogate pairs, and honestly? good for her. when she bumps into a high surrogate (0xD800-0xDBFF), she goes and fetches the following low surrogate (0xDC00-0xDFFF) and "desurrogate"s them to form a character. bogus surrogates result in death. literally. when \x{10000} or above shows up during encode(), she "ensurrogate"s them and pushes the surrogate pair straight to the output stream. iconique. πŸ’…
  • UTF-32 (also known as UCS-4) is a fixed-length encoding where each character takes 32 bits. and since it's 32-bit, there is no need for surrogate pairs at all. she said no drama, no pairs, we're thriving. 🧚

πŸ’œ by endianness πŸ’œ

fun history lesson: the first goal of Unicode β€” and yes, it's the now failed goal, we don't talk about it β€” was to map all character repertoires into a single fixed-length integer so programmers would be happy. 😭 since each character is either a short or a long in C, you've gotta pay attention to the endianness of each platform when you're passing data between them. this is the part where it gets real.

anything marked BE is Big Endian (aka network byte order) and LE is Little Endian (aka VAX byte order). and for anything that isn't marked BE or LE? a character called a Byte Order Mark (BOM) that tells you the endianness gets prepended to the string. she announces herself. πŸ’β€β™€οΈ

CAVEAT: though BOM in utf8 (\xEF\xBB\xBF) is valid, it is meaningless, and as of this writing the Encode suite just leaves it as is (\x{FeFF}). like okay girl, do your thing I guess. πŸ™ˆ

BOM as integer when fetched in network byte order:

                     16         32 bits/char
         -------------------------
         BE      0xFeFF 0x0000FeFF
         LE      0xFFFe 0xFFFe0000
         -------------------------

and this module handles the BOM like this, bestie, take notes:

  • when BE or LE is explicitly stated as the name of the encoding, the BOM is simply treated as a normal character β€” that's ZERO WIDTH NO-BREAK SPACE to you and me.
  • when BE or LE is omitted during decode(), it checks if a BOM is sitting at the beginning of the string; if one is found, the endianness gets set to whatever the BOM says. she listens. 🫢
  • Default Byte Order β€” when no BOM is found, Encode 2.76 and below croaked. but since Encode 2.77, it falls back to BE, accordingly to RFC2781 and the Unicode Standard version 8.0. growth! character development! 🌱
  • when BE or LE is omitted during encode(), it returns a BE-encoded string with a BOM prepended. so when you want to encode a whole text file, make sure you encode() the whole text at once β€” not line by line, or each line, and not file, will have a BOM prepended. you've been warned, do not do the thing. 😭
  • and "UCS-2" is the exception to all of this. unlike the others, this one is an alias of UCS-2BE. UCS-2 is already registered by IANA and others that way, so. rules are rules. πŸ’…

🧚 Surrogate Pairs 🧚

to say the least, surrogate pairs were the biggest mistake of the Unicode Consortium. 😭 but according to the late Douglas Adams in The Hitchhiker's Guide to the Galaxy Trilogy, "In the beginning the Universe was created. This has made a lot of people very angry and been widely regarded as a bad move". their mistake was not of this magnitude so let's forgive them. πŸ•ŠοΈ

(I don't dare make any comparison with Unicode Consortium and the Vogons here ;) or, comparing Encode to Babel Fish is completely appropriate β€” if you can only stick this into your ear :)

so anyway, surrogate pairs were born when the Unicode Consortium finally admitted that 16 bits were not big enough to hold all the world's character repertoires. but they'd already made UCS-2 16-bit. what do we do?? πŸ€”

back then, the range 0xD800-0xDFFF was not allocated. so: split that range in half, use the first half to represent the "upper half of a character" and the second half to represent the "lower half of a character". that way you can represent 1024 * 1024 = 1048576 more characters. now we can store character ranges up to \x{10ffff} even with 16-bit encodings. that pair of half-characters is now called a surrogate pair, and UTF-16 is the name of the encoding that embraces them. she said "I'll take them, they're mine now". πŸ₯Ή

here's the formula to ensurrogate a Unicode character \x{10000} and above:

$hi = ($uni - 0x10000) / 0x400 + 0xD800;
$lo = ($uni - 0x10000) % 0x400 + 0xDC00;

and to desurrogate:

$uni = 0x10000 + ($hi - 0xD800) * 0x400 + ($lo - 0xDC00);

math girlies rise. πŸ“βœ¨

note this move has made \x{D800}-\x{DFFF} into a forbidden zone β€” but perl does not prohibit the use of characters within this range. to perl, every one of \x{0000_0000} up to \x{ffff_ffff} (*) is just a character. she doesn't judge.
(*) or \x{ffff_ffff_ffff_ffff} if your perl is compiled with 64-bit integer support! look at her go πŸ”₯

πŸ’€ Error Checking πŸ’€

unlike most encodings that accept various ways to handle errors, Unicode encodings simply croaks. that's it. no options, no leniency, no "let's talk about it". she's not interested. πŸ’…

% perl -MEncode -e'$_ = "\xfe\xff\xd8\xd9\xda\xdb\0\n"' \
       -e'Encode::from_to($_, "utf16","shift_jis", 0); print'
UTF-16:Malformed LO surrogate d8d9 at /path/to/Encode.pm line 184.
% perl -MEncode -e'$a = "BOM missing"' \
       -e' Encode::from_to($a, "utf16", "shift_jis", 0); print'
UTF-16:Unrecognised BOM 424f at /path/to/Encode.pm line 184.

see? croaked. out loud. in front of everyone. 😭

unlike other encodings where mappings are not one-to-one against Unicode, UTFs are supposed to map 100% against one another. so Encode is more strict on UTFs.

consider that "division by zero" of Encode :) she said be normal or be nothing. πŸ’€βœ¨


⭐ SEE ALSO ⭐


okay that's the whole page bestie, you're officially educated πŸ’– this one's brought to you by perl v5.44.0, dated 2026-10-17 β€” go forth and decode() responsibly, ily, mwah πŸŽ€βœ¨πŸŒˆπŸ«Ά

πŸ“¦ where this page comes from
  • arch rolling: perl 5.44.0-1 Β· Artistic-1.0-Perl AND GPL-1.0-or-later
  • πŸ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms