Why LEN seems wrong sometimes
An introduction to Unicode, for spreadsheet people
Type one emoji into a cell and ask Excel how long it is:
That’s one picture, and Excel says it’s eight characters long, or five in newer workbooks. Neither answer is 1. It isn’t a bug. Excel is counting things you can’t see, and which things it counts depends on a workbook setting.
This page explains what those things are, from the ground up, and assumes you know nothing about Unicode yet. By the end you’ll be able to explain both numbers, and why two pieces of text that look exactly alike can refuse to match.
- How text is storedCharacters are numbers, and one character can take up several of the “slots” that Excel counts.
- Why LEN surprises youExcel counts storage slots or Unicode characters, never quite the characters you see.
- Why your Excel version mattersA per-workbook setting changes what
LEN,MID,FIND,SEARCHandREPLACEcount. - Why = and EXACT disagreeExcel has two definitions of “equal,” and invisible characters fall between them.
Already know this and just need to debug something? Go to the workbench or the cheat sheet.
Unicode from zero
Computers can only store numbers. Text works because everyone agrees on which number stands for which character.
That agreement is called a character set. One of the earliest was ASCII (1963). It covered 128 characters: English letters, digits and punctuation. In ASCII, A is 65, B is 66 and a space is 32. That was enough for English and nothing else. There was no é, no Ω and no ☂.
Other countries built their own sets, and they reused the same numbers for different characters. A file written with one set and opened with another came out as garbage, which is why you sometimes still see café where café should be.
Unicode (1991) fixed this by giving every character in every writing system its own number, plus symbols, math and eventually emoji. That number is the character’s code point.
Reading U+ notation
Code points are written as U+ followed by the number in hexadecimal (base 16). Hex uses sixteen digits, 0–9 and then A–F, so A means 10 and F means 15.
Why hex and not ordinary decimal? Each hex digit fits exactly into 4 bits, so hex lines up neatly with how computers store data. Four hex digits are exactly 16 bits. That fact explains the LEN puzzle, so keep it in mind.
| Character | Code point | Decimal | What it is |
|---|---|---|---|
| A | U+0041 | 65 | Same as ASCII. Unicode kept the first 128. |
| é | U+00E9 | 233 | Latin small letter e with acute |
| ☂ | U+2602 | 9730 | Umbrella |
| 👨 | U+1F468 | 128104 | Man. Five hex digits. |
| ZWJ | U+200D | 8205 | Zero width joiner. Invisible, but it’s a real character. |
Seeing code points in Excel
Two functions convert between characters and their code points, and two more convert between decimal and hex:
=UNICODE("A") → 65 character → code point (decimal)
=UNICHAR(9730) → ☂ code point → character
=DEC2HEX(9730) → 2602 decimal → hex, so this is U+2602
=UNICHAR(HEX2DEC("1F468")) → 👨 from U+ notation back to the character
A code point is a character’s ID number. It doesn’t decide how the character looks (the font does that) or how many slots it takes up in memory (the encoding does that). Those are separate questions.
Why LEN says 8 (or 5)
Back to the family emoji. We’ll count it three times, looking a little deeper into how it’s stored each time. Both of Excel’s answers will turn up along the way.
What you see 1
One picture of a family. It’s what you’d point at with your finger and call one character.
What Unicode sees 5
Unicode has no single “family” code point for this. The picture is built from three people joined by two invisible glue characters called zero-width joiners (ZWJ). Your phone or computer sees man + glue + woman + glue + girl and draws one combined picture.
This is what LEN reports in Compatibility Version 2 workbooks.
How the text is stored 8
Excel stores text in UTF-16, which means every character goes into fixed 16-bit slots called code units. Each slot holds exactly four hex digits, up to FFFF. The glue fits in one slot because 200D is four digits. Each person is five digits (1F468) and doesn’t fit, so it’s split across two slots.
LEN reports in Compatibility Version 1Neither answer is wrong. They answer different questions. In Version 1, LEN answers “how many 16-bit slots does this take up?” and says 8. In Version 2, it answers “how many Unicode characters is this?” and says 5. Neither counts what you see. The split into two slots is called a surrogate pair (section 5). Which count your workbook uses depends on its compatibility version (section 4).
Check each stage yourself:
| Formula | Version 1 | Version 2 |
|---|---|---|
| =LEN(UNICHAR(128104)) | 2 | 1 |
| =LEN(UNICHAR(8205)) | 1 | 1 |
| =LEN(UNICHAR(128104)&UNICHAR(8205)&UNICHAR(128105)&UNICHAR(8205)&UNICHAR(128103)) | 8 | 5 |
Three meanings of “character”
The three counts you just saw have names. Most text bugs come from counting one of them while thinking about another.
| Name | Plain English | Family emoji | Excel counts it with |
|---|---|---|---|
| Grapheme | What a person sees and would call one character. | 1 | REGEXEXTRACT + \X (§7) |
| Code point | One of Unicode’s numbered characters. A grapheme can be made of several. | 5 | LEN in Version 2 |
| Code unit | One storage slot. In UTF-16 a slot is 16 bits, and a code point takes one or two. | 8 | LEN in Version 1 |
é on a keyboard that has no é key: you press the accent key, then e. That’s two keystrokes for one letter on screen. Code points are like the keystrokes and graphemes are like the letters you end up with. Code units go one level lower still: they’re how much space those keystrokes take up in memory.No version of LEN counts graphemes. In Version 1 workbooks, LEN, MID, FIND, SEARCH and REPLACE count code units. In Version 2 they count code points. Either way, invisible marks, skin tones and joiners all add to the count.
Here are more examples. Each one is a single grapheme, and they don’t all look like emoji:
| Looks like | Made of | Graphemes | LEN v2 code points | LEN v1 code units | |
|---|---|---|---|---|---|
| A | a plain letter | 1 | 1 | 1 | |
| ☂ | umbrella | 1 | 1 | 1 | |
| ☂️ | umbrella + invisible “draw as emoji” marker | 1 | 2 | 2 | |
| 🐸 | frog, a five-digit code point | 1 | 1 | 2 | |
| é | e + a combining accent | 1 | 2 | 2 | |
| 👋🏽 | waving hand + skin tone | 1 | 2 | 4 | |
| 🇯🇵 | two “regional indicator” letters J + P = flag of Japan | 1 | 2 | 4 | |
| 👨👩👧 | three people + two joiners | 1 | 5 | 8 |
On Windows the flag shows as the letters “JP”, because Windows doesn’t include flag emoji. The data is fine; it’s still one grapheme.
Compatibility versions
For decades, LEN("😀") returned 2. Microsoft has now fixed that. To avoid silently changing the results of millions of existing spreadsheets, the fix is a per-workbook setting.
A compatibility version is a workbook setting that decides which behaviour certain functions use. There are currently two:
Legacy
- Text functions count UTF-16 code units, so an emoji outside the BMP counts as 2
- Every Excel supports it
- Workbooks created before the change open as Version 1
Unicode-aware
- Text functions count code points, so a surrogate pair counts as 1
- Excel for Microsoft 365 (Windows and Mac) only
- The default for new workbooks in the Current Channel since April 2026
What changes
Microsoft lists exactly five functions: LEN, MID, FIND, SEARCH and REPLACE. In Version 2 they treat a surrogate pair as one character, so lengths and positions are counted in code points.
| Text | Formula | Version 1 | Version 2 |
|---|---|---|---|
| 🐸 | =LEN(UNICHAR(128056)) | 2 | 1 |
| 👨👩👧 | LEN of the family | 8 | 5 |
| 👋🏽 | =LEN(UNICHAR(128075)&UNICHAR(127997)) | 4 | 2 |
| ☂️ | =LEN(UNICHAR(9730)&UNICHAR(65039)) | 2 | 2 |
| 🐸 | =MID(UNICHAR(128056), 1, 1) | half a frog (�) | the whole frog |
What doesn’t change
- Version 2 still doesn’t count graphemes. It counts code points. Microsoft specifically notes that variation selectors are still counted separately, and so are skin tones and ZWJ joiners, which is why the family is still 5. For a grapheme count, see section 7.
- Other text functions aren’t on the list. Coverage of the change describes
LEFT,RIGHT,TEXTBEFORE,TEXTAFTERandTEXTSPLITas already handling surrogate pairs correctly. Comparisons (=,EXACT) andSUBSTITUTEaren’t mentioned either, so section 10 applies in both versions.
Checking and changing it
Go to Formulas › Calculation Options › Compatibility Version. The setting belongs to the workbook, not to your copy of Excel. Two workbooks open side by side can give different LEN results for the same text.
There’s no function that reports the version directly, but the frog gives it away. A single emoji is 2 slots in Version 1 and 1 code point in Version 2:
=IF(LEN(UNICHAR(128056)) = 1, "Version 2", "Version 1")
Excel 2024 and earlier only support Version 1. If someone opens a Version 2 workbook in one of them, they get a warning that calculation results may differ. The five functions then fall back to counting code units. A formula like =MID(A1, 3, 1) can return different text for the same cell depending on who opens the file.
The rollout details are recent and may change. Microsoft’s Compatibility Versions page has the latest.
Workbench
Now that you know the three counts, try your own text. Paste anything below, such as a cell’s contents, a name that won’t match, or an emoji. Everything runs in your browser and nothing is sent anywhere.
How to read the inspector
- The four numbers are the counts from section 3. Code points are what
LENreturns in Version 2 workbooks; UTF-16 units are what it returns in Version 1. - The alignment strip stacks the three layers so you can see which slots belong to which code point, and which code points belong to which grapheme. Purple and teal slots are the two halves of a surrogate pair.
- Orange dashed boxes are invisible characters or characters that attach to the one before them. These are the usual suspects when text won’t match.
- Click a row to see each code point’s official name and number. “Break role” says whether it starts a new grapheme (Base) or attaches to the previous one (Extend, ZWJ).
- The formula at the bottom rebuilds your exact string in Excel, invisible characters included.
Inspect a string
what a person sees ·
\X count=
LEN in Version 2=
LEN in Version 1size in a CSV file
That formula rebuilds the exact string in Excel, invisible characters included. Paste it into a cell next to your data and compare the two with EXACT.
Are these equal?
EXACT() and the last models Excel’s =. Section 10 explains why they differ.Surrogate pairs
This section explains how a code point too big for one slot gets split into two. It’s the reason for Version 1’s 8, and the thing Version 2 was built to hide.
A little history
Early Unicode assumed 65,536 characters (0000–FFFF) would be enough for everything. Windows, Java, JavaScript and Excel were designed around that, with one 16-bit slot per character. In 1996 Unicode grew past that limit to make room for historic scripts, rare Chinese characters and, later, emoji. The new maximum was 10FFFF.
Those systems couldn’t switch to bigger slots without breaking everything already written. So Unicode set aside 2,048 values in the 16-bit range that aren’t characters at all. Each one means “I’m half of a bigger number.” These are surrogates:
- A high surrogate (
D800–DBFF) always comes first and carries the top half of the number. - A low surrogate (
DC00–DFFF) always comes second and carries the bottom half.
Together they make a surrogate pair: two slots that represent one code point. The two ranges don’t overlap, so any slot tells you whether it’s a first half, a second half, or a normal character.
The code space is divided into 17 planes of 65,536 code points each. Plane 0, the Basic Multilingual Plane, covers everything up to FFFF. That includes almost all everyday text in most languages, and it fits in one slot. Planes 1–16, sometimes called the “astral” planes, need a pair. Most emoji live in plane 1, which is why a single emoji has a LEN of 2 in Version 1 workbooks.
Worked example: 🐸 U+1F438 frog face
1F438 is bigger than FFFF, so it needs two slots. The recipe is: subtract 10000, split what’s left into two 10-bit halves, and add each half to the start of its surrogate range.
Going backwards: pair → code point
cp = (hi − 0xD800) × 0x400 + (lo − 0xDC00) + 0x10000
= (0xD83D − 0xD800) × 0x400 + (0xDC38 − 0xDC00) + 0x10000
= 0x3D × 0x400 + 0x38 + 0x10000
= 0x1F438 ✓
0x400 is 1,024 = 210, the size of each half. The 0x prefix just means “this number is hex.”
It’s still one code point. The pair only exists in UTF-16. Other encodings store the same frog differently:
MID counts slots, so it can cut a pair in half. =MID(UNICHAR(128056), 1, 1) returns only the high surrogate, which displays as � or an empty box. Passing that half to UNICODE() gives #VALUE!, because half a pair isn’t a character. FIND and SEARCH positions are also counted in slots, so a position found in one version can point at the wrong character in the other.
In Version 2, MID counts code points and returns the whole frog. UNICODE works in both versions when you give it a complete pair: =UNICODE("🐸") returns 128056.
Grapheme clusters
A grapheme is what a reader sees as one character. Unicode’s name for it is an extended grapheme cluster: a base character plus everything attached to it.
Some code points never stand alone. They modify the character in front of them, like an accent placed on a letter. When text is split into graphemes, these attaching characters stay with the base:
Unicode doesn’t keep a list of every possible grapheme, because there are far too many combinations. Instead it gives every code point a property that says how it behaves: “I’m a normal character,” “I attach to the previous one,” “I’m a joiner,” “I’m half a flag,” and so on. A short set of rules in a document called UAX #29 uses those properties to decide where one grapheme ends and the next begins. In short:
- Accents and modifiers attach. Combining marks, skin tones and the invisible emoji-style marker
U+FE0Fstay with the character before them. - Joined emoji stay together. Emoji + ZWJ + emoji forms one grapheme.
- Flag letters pair up. Regional-indicator letters join two at a time, so 🇯🇵🇫🇷 is two flags (Japan and France) and not one four-letter blob.
- Everything else is its own grapheme.
The actual rules (optional)
× means “don’t break here” and ÷ means “break here.” The names (Extend, ZWJ, RI…) are values of each code point’s Grapheme_Cluster_Break property.
| Rule | Pattern | Meaning |
|---|---|---|
| GB3 | CR × LF | A Windows line break (\r\n) is one grapheme. |
| GB9 | × (Extend | ZWJ) | Never break before an accent, U+FE0F, a skin tone or a ZWJ. |
| GB9a/b | × SpacingMark, Prepend × | Keeps vowel signs attached in scripts such as Hindi and Thai. |
| GB11 | ExtPict Extend* ZWJ × ExtPict | Emoji joined with a ZWJ stay together. |
| GB12/13 | RI RI × RI RI | Flag letters pair up two at a time. |
| GB999 | Any ÷ Any | Otherwise, break. |
No version of LEN follows these rules. Excel does have a way to split text into graphemes, though, and it’s covered in the next section.
Counting graphemes with \X
Excel for Microsoft 365 has three regular expression functions: REGEXTEST, REGEXEXTRACT and REGEXREPLACE. Their regex language includes a pattern that matches exactly one grapheme.
A regular expression (regex) is a small pattern language for describing text. Excel’s regex functions use a variety called PCRE2, where \X (capital X) means “one extended grapheme cluster.” That’s the same unit the grapheme rules in section 6 produce: what a reader sees as one character.
REGEXEXTRACT(text, pattern, [return_mode]) returns the text that matches the pattern. With return_mode set to 1 it returns every match as an array that spills into neighbouring cells. Using \X as the pattern returns one cell per grapheme:
A1: 👨👩👧🐸é
=REGEXEXTRACT(A1, "\X", 1) → 👨👩👧 | 🐸 | é (spills 3 cells)
Now you can do what LEN never could:
| Goal | Formula |
|---|---|
| Count graphemes | =IF(A1="", 0, COUNTA(REGEXEXTRACT(A1, "\X", 1))) |
First grapheme (a safe LEFT(A1,1)) | =REGEXEXTRACT(A1, "\X") |
| First n graphemes | =CONCAT(TAKE(TOCOL(REGEXEXTRACT(A1, "\X", 1)), n)) |
| Size of each grapheme | =LEN(REGEXEXTRACT(A1, "\X", 1)) |
| Every code point number | =UNICODE(REGEXEXTRACT(A1, "(?s).", 1)) |
- The
IFguard. When nothing matches (an empty cell),REGEXEXTRACTreturns an error, andCOUNTAwould count that error as 1. TOCOLmakes the spill a single column whichever direction it comes out, soTAKEalways takes the first n items."(?s).": a.matches one code point, never half a surrogate pair, so this lists code points the same way in Version 1 and Version 2.(?s)lets.match line breaks too.- No escaping needed. Backslashes are ordinary characters in Excel strings, so type
"\X"exactly as shown. Only double quotes need doubling.
The family emoji, all three ways:
| Formula | Counts | Result |
|---|---|---|
| =LEN(A1) | code units (Version 1) | 8 |
| =LEN(A1) | code points (Version 2) | 5 |
| =COUNTA(REGEXEXTRACT(A1, "\X", 1)) | graphemes (either version) | 1 |
• The regex functions are only in Excel for Microsoft 365 (Windows and Mac). Older versions show #NAME?.
• The grapheme rules come from the regex engine, which follows a particular Unicode version. Very new emoji may split differently than in your browser, so if a count looks wrong, compare it with the workbench.
• \X uses the grapheme rules, not the list of approved emoji. An unapproved combination like frog + ZWJ + umbrella counts as 1 even though it displays as two pictures (section 8).
Regex is also a compact way to delete invisible characters. This removes all 16 variation selectors in one go:
=REGEXREPLACE(A1, "[\x{FE00}-\x{FE0F}]", "")
\x{FE0F} is PCRE2’s way to write a code point in hex, and […-…] matches anything in that range.
The zero-width joiner U+200D
The ZWJ is the invisible glue from the family emoji. On its own it has no width and no shape. Put it between two emoji and it asks the font to draw them as one combined picture.
LEN 5 (v1) or 3 (v2). LEN 5 (v1) or 4 (v2). Joined emoji only merge into one picture when Unicode has approved that exact combination and your font includes a drawing of it. You can’t make a new emoji by putting a ZWJ between any two.
The approved combinations are listed in a file called emoji-zwj-sequences.txt, and the set is called RGI (“recommended for general interchange”). If a combination isn’t on the list, or your font doesn’t have it, the pieces are drawn side by side.
There’s a wrinkle. The grapheme rules only look at properties (“this is an emoji, this is a ZWJ”) and never check the approved list. So the frog-umbrella counts as one grapheme in the workbench and with \X, even though you see two pictures. Most of the time, what you see and the grapheme count agree. When they don’t, the grapheme count follows the rules and the picture follows the font.
The ZWJ also explains a common cleanup bug. If you delete all the emoji from some text, the invisible ZWJs that sat between them are still there, making LEN higher than it should be.
Text vs emoji style
Many symbols can be drawn two ways: a plain black-and-white symbol (“text style”) or a colourful emoji (“emoji style”). It’s the same code point either way.
Each symbol has a default style. Older symbols from before emoji existed, like the umbrella, usually default to text style. Newer ones usually default to emoji style. To override the default, you add an invisible variation selector right after the symbol:
U+FE0F(“VS16”) means draw it as an emoji.U+FE0E(“VS15”) means draw it as text.
| Rendered | Code points | Style | Why |
|---|---|---|---|
| ☂ | 2602 | text | Umbrella defaults to text style |
| ☂️ | 2602FE0F | emoji | VS16 asks for emoji |
| ☔ | 2614 | emoji | Umbrella with rain drops defaults to emoji style |
| ⛵ | 26F5 | emoji | Sailboat defaults to emoji style |
| ⛵︎ | 26F5FE0E | text | VS15 asks for text (not every font supports this) |
The default is recorded in a Unicode property called Emoji_Presentation. Your screen may not match the table: many fonts, including Windows’ emoji font, draw some symbols in colour whatever the selector says. That makes the problem worse, because two strings with different code points end up looking identical.
Emoji keyboards and pickers usually insert the VS16 version so you get colour. Typed text, older data and some programs leave it out. The same symbol ends up stored two ways depending on where it came from, and both versions look alike on screen. ☂ and ☂️ have different lengths in both compatibility versions and don’t pass EXACT. That’s the classic “identical text won’t match” problem, and the next section is about how to deal with it.
Excel’s two kinds of equal
Excel has two ways to check whether text matches, and they don’t agree. Most “identical text won’t match” problems come from using both in the same workbook.
A1 = B1
- Ignores the invisible emoji marker
U+FE0F: ☂ = ☂️ - Treats both spellings of an accented letter as equal: é (one code point) = é (e + accent)
- Ignores upper and lower case:
a=A
EXACT(A1, B1)
- Compares the text code point by code point
- Case matters
- Treats all of the pairs on the left as different
These functions are strict like EXACT: SUBSTITUTE, FIND and EXACT itself. They look for the exact same code points. Compatibility versions change how FIND counts positions, not what it considers a match.
Paste these into a sheet to see the difference:
| Pair | Formula | = | EXACT |
|---|---|---|---|
| ☂ vs ☂️ | =UNICHAR(9730) = UNICHAR(9730)&UNICHAR(65039) | TRUE | FALSE |
| é vs é | =UNICHAR(233) = "e"&UNICHAR(769) | TRUE | FALSE |
| a vs A | ="a" = "A" | TRUE | FALSE |
For the EXACT column, wrap the same two values: =EXACT(UNICHAR(9730), UNICHAR(9730)&UNICHAR(65039)). The = results were observed in Excel for Microsoft 365, so try them on your own version to confirm.
Why mixing them causes trouble
Say A1 holds the plain umbrella and B1 holds the emoji-style one. A formula that checks with = and then acts with SUBSTITUTE gets two different answers:
Step 1 · Check
Step 2 · Act
A1: =REPT(UNICHAR(9730), 2) ☂☂ (plain)
B1: =UNICHAR(9730) & UNICHAR(65039) ☂️ (with marker)
=A1 = REPT(B1, 2) → TRUE
=SUBSTITUTE(A1, B1, "") → ☂☂ unchanged
Swap them, so the text holds ☂️ and you remove the plain ☂. SUBSTITUTE does find it inside the longer text and deletes it, leaving the invisible marker U+FE0F behind. You can’t see it, but LEN counts it, and every later comparison with that cell fails.
Use one definition of “equal” throughout. Either clean the data once so every copy is stored the same way, or clean both sides right before comparing (section 11 shows how). For emoji-style markers, removing every VS16 is often enough:
=SUBSTITUTE(A1, UNICHAR(65039), "")
More Excel gotchas
- Excel has no function to clean up Unicode.
=does some of this cleanup internally for its comparison, but you can’t get the cleaned text back out. DEC2HEX(cp, 4)returns#NUM!for emoji. The optional 4 means “pad to 4 digits,” and a code point like1F438needs 5, the same 4-digit limit from section 2. Leave the 4 out:DEC2HEX(cp).CODEandCHARpredate Unicode and only know an old Windows character set. For anything outside that set,CODEreturns 63, the code for?. UseUNICODEandUNICHARinstead.SEARCHignores case andFINDdoesn’t.XLOOKUPandMATCHignore case too. For a lookup that respects case, use=XLOOKUP(TRUE, EXACT(keys, x), values).- The usual way to count occurrences,
(LEN(s) − LEN(SUBSTITUTE(s, x, ""))) / LEN(x), gives wrong answers when some copies ofxhave theFE0Fmarker and others don’t. Count them withCOUNTA(REGEXEXTRACT(s, …, 1))after cleaning instead.
Normalization: making equal text look equal
To normalize text is to rewrite it into one standard form, so that text that means the same thing is also stored the same way. Then even strict comparisons like EXACT agree.
There are two kinds of cleanup, and they need very different tools. Some you can work out from the code point’s number alone. The rest needs a lookup table.
Kind 1: the number tells you (a formula can do it)
A few kinds of character can be spotted by their number because they sit in known ranges. For comparison purposes you can usually just delete them:
| Delete | Range | Decimal (for UNICHAR) |
|---|---|---|
| Variation selectors (the emoji/text markers) | U+FE00 – U+FE0F | 65024 – 65039 |
| Skin-tone modifiers | U+1F3FB – U+1F3FF | 127995 – 127999 |
| Zero-width joiner | U+200D | 8205 |
With the regex functions it’s one line, because a character class can hold several ranges:
=REGEXREPLACE(A1, "[\x{FE00}-\x{FE0F}\x{1F3FB}-\x{1F3FF}\x{200D}]", "")
Without them (any Excel 365), loop over the list of numbers and remove each character in turn:
=LET(
strip, VSTACK(SEQUENCE(16,,65024), 8205, SEQUENCE(5,,127995)),
REDUCE(A1, strip, LAMBDA(s, c, SUBSTITUTE(s, UNICHAR(c), "")))
)
Save either one as a named LAMBDA (for example FOLDKEY) and compare FOLDKEY(A1) = FOLDKEY(B1). Use it only for comparing. Removing the ZWJ turns 👩💻 into 👩💻, which is not something you want to store.
Kind 2: needs a lookup table (a formula can’t)
Remember the two ways to write é? Unicode says they’re canonically equivalent, which means officially the same text:
- Composed: one code point,
U+00E9(233). - Decomposed: two code points,
U+0065e (101) +U+0301combining accent (769).
You can’t get from 233 to “101 + 769” with arithmetic. The pairing was simply decided by the Unicode Consortium and recorded in a table (UnicodeData.txt). Some pairings are even odder: Å U+212B ANGSTROM SIGN is officially the same as Å U+00C5. The standard forms have names:
- NFC (“composed”) turns everything into the one-code-point form where one exists. It’s the most common choice.
- NFD (“decomposed”) splits everything into base + accents.
A formula alone can’t convert text to NFC or NFD, because that needs Unicode’s table. Excel’s = uses the table internally, but only to compare, and never gives you the converted text.
One fun exception: the 11,172 Korean Hangul syllables are arithmetic. Each syllable’s number encodes its parts, and the workbench uses that to name them. Try 한 (U+D55C, “HAN”).
Where to do it instead
Normalize the text before it reaches the grid, or with a tool that has the table built in:
// JavaScript
"e\u0301".normalize("NFC") === "\u00E9" // true
# Python
unicodedata.normalize("NFC", s)
' VBA: call the Windows API
Declare PtrSafe Function NormalizeString Lib "kernel32" ( _
ByVal NormForm As Long, ByVal lpSrc As LongPtr, ByVal cwSrc As Long, _
ByVal lpDst As LongPtr, ByVal cwDst As Long) As Long ' NormForm 1 = NFC
How many characters are there, really?
Unicode has room for 1,114,112 code points (17 planes × 65,536). Most of that room is empty.
| Slice | Count | What it is |
|---|---|---|
| Total code space | 1,114,112 | Every number from U+0000 to U+10FFFF |
| Characters in Unicode 16.0 (2024) | 154,998 | Actual characters with names: letters, symbols, emoji… |
| Characters in Unicode 17.0 (2025) | 159,801 | 4,803 added |
| Surrogates | 2,048 | The “half of a pair” values from section 5. They exist only to make UTF-16 work and are never characters. |
| Private use | 137,468 | Left empty on purpose so companies and apps can put their own symbols there (icon fonts, for example) |
| Noncharacters | 66 | Permanently reserved for programs’ internal use |
So about 86% of the code space holds no standard character (1 − 154,998 / 1,114,112). If you don’t count the reserved areas (surrogates, private use and noncharacters), about 73% is still completely unassigned. There’s plenty of room for new emoji.
You’ll sometimes see ~297,000 quoted for Unicode 17.0. That figure counts the 137,468 private-use slots as assigned (159,801 + 137,468 = 297,269), so you can’t compare it with the ~155,000 figure for 16.0. Counted the same way, 17.0 added about 4,800 characters.
Reserved code points don’t have names. Tools show placeholders like <private-use-E000> instead.
The official character list, UnicodeData.txt, has far fewer lines than there are characters. Large blocks whose names follow a pattern are stored as just a first and a last line. For example, all 42,720 characters of CJK Extension B (20000–2A6DF) take two lines:
20000;<CJK Ideograph Extension B, First>;Lo;0;L;;;;;N;;;;;
2A6DF;<CJK Ideograph Extension B, Last>;Lo;0;L;;;;;N;;;;;
Cheat sheet
For when you already know the concepts and just need the fix. The section numbers point back to the explanations.
| Symptom | Likely cause | Fix |
|---|---|---|
| Identical-looking text won’t match | Invisible U+FE0F or U+FE0E on one side (§9) | Remove variation selectors from both sides (§11), or store one consistent form |
= says TRUE but SUBSTITUTE/FIND find nothing | = is loose and those functions are strict (§10) | Clean both sides before substituting, and test with EXACT |
EXACT is FALSE for “the same” accented word | One side stores the accent separately, as e + U+0301 (§11) | Normalize to NFC before the data reaches Excel |
Same text, different LEN in two workbooks | The workbooks use different compatibility versions (§4) | Formulas › Calculation Options › Compatibility Version |
LEN says 2 for one emoji | Version 1 workbook: surrogate pair counted as 2 slots (§5) | Switch to Version 2, or count graphemes with \X (§7) |
LEN says 2+ for one emoji even in Version 2 | Skin tone, ZWJ joins or VS16 markers are separate code points (§6, §8) | Count with COUNTA(REGEXEXTRACT(A1,"\X",1)) |
LEN is one more than expected | A leftover U+FE0F from an earlier substitution (§10) | Remove UNICHAR(65039) |
| Counting occurrences with SUBSTITUTE gives the wrong number | Only some copies have FE0F, or the search text has several parts | Clean both strings first, then count |
MID shows � or a box | Version 1: cut a surrogate pair in half (§5) | Use Version 2, or take whole graphemes with REGEXEXTRACT (§7) |
UNICODE() returns #VALUE! | It was given half a surrogate pair (§5) | Fix the cut that produced it |
FIND position is off after sharing the file | Positions count slots in Version 1 and code points in Version 2 (§4) | Don’t store positions; recompute them in the workbook that uses them |
DEC2HEX(cp,4) returns #NUM! | The code point needs 5 hex digits (§10) | Leave out the 4 |
CODE() returns 63 | The character isn’t in the old Windows character set | Use UNICODE() |
| Emoji shows black-and-white | Text-style default and no VS16 (§9) | Append UNICHAR(65039), and do it the same way everywhere |
| Two emoji appear where one should | Unapproved ZWJ combination, or the font doesn’t have it (§8) | Check emoji-zwj-sequences.txt |
| Flag shows as two letters | Windows has no flag emoji | Not a data problem; the text is correct |
| Lookup ignores case when you need it to match exactly | XLOOKUP/MATCH ignore case | XLOOKUP(TRUE, EXACT(keys, x), vals) |
Handy numbers for UNICHAR: 65039 VS16 (emoji style) · 65038 VS15 (text style) · 8205 ZWJ · 769 combining acute accent · 127995–127999 skin tones. Version check: =IF(LEN(UNICHAR(128056))=1, "Version 2", "Version 1").
Glossary
- ASCII
- A 1963 character set of 128 characters: English letters, digits and punctuation. Unicode’s first 128 code points are the same.
- Bit
- A single 0 or 1. 16 bits can represent 216 = 65,536 different values. One hex digit is exactly 4 bits.
- BMP (Basic Multilingual Plane)
- Plane 0, U+0000–U+FFFF. Everything here fits in one UTF-16 slot.
- Canonical equivalence
- Unicode’s official statement that two different code point sequences mean the same text, such as é and e + U+0301.
- Code point
- A character’s number in Unicode, written U+ and hex, e.g. U+2602. Ranges from U+0000 to U+10FFFF.
LENcounts these in Version 2 workbooks. - Code unit
- One storage slot in an encoding. In UTF-16 a slot is 16 bits.
LENcounts these in Version 1 workbooks. - Combining mark
- A code point that attaches to the character before it, such as an accent. It has no meaning on its own.
- Compatibility version
- A per-workbook Excel setting (Formulas › Calculation Options) that picks old or new behaviour for certain functions. In Version 2,
LEN,MID,FIND,SEARCHandREPLACEcount code points instead of code units. - Encoding
- The method for storing code points as bytes. UTF-8, UTF-16 and UTF-32 all store the same code points in different ways.
- Grapheme (cluster)
- What a reader sees as one character. It can be made of many code points. In Excel,
REGEXEXTRACTwith\Xfinds them. - Hexadecimal (hex)
- Base-16 numbers using the digits 0–9 and A–F. Written with a
0xorU+prefix. Excel converts withDEC2HEXandHEX2DEC. - NFC / NFD
- Standard “normal forms” of text. NFC uses the one-code-point form where possible and NFD splits into base + marks. Converting to one is called normalizing.
- PCRE2
- “Perl Compatible Regular Expressions,” version 2. The regex engine behind Excel’s
REGEXfunctions. In PCRE2,\Xmatches one grapheme. - Plane
- A block of 65,536 code points. There are 17 of them (0–16).
- Regular expression (regex)
- A pattern language for matching text. For example,
\d+means “one or more digits.” - RGI
- “Recommended for general interchange”: Unicode’s list of emoji and emoji combinations that platforms are expected to support.
- Surrogate pair
- Two UTF-16 slots (high D800–DBFF, then low DC00–DFFF) that together store one code point above U+FFFF.
- UAX #29
- The Unicode document that defines how to split text into graphemes, words and sentences.
- UTF-16
- An encoding that uses 16-bit slots: one slot for code points up to U+FFFF, two (a surrogate pair) above that. Used by Excel, Windows, Java and JavaScript.
- Variation selector
- An invisible code point that picks a drawing style for the character before it. U+FE0F = emoji style, U+FE0E = text style.
- ZWJ (zero-width joiner)
- U+200D. Invisible glue that asks the font to combine the emoji on either side into one picture.
References
Notes
- Microsoft’s own pages list only the five functions that change. The statement that
LEFT,RIGHT,TEXTBEFORE,TEXTAFTERandTEXTSPLITalready handled surrogate pairs comes from FM Magazine’s coverage. - Microsoft’s
REGEXEXTRACTdocumentation doesn’t say what is returned when nothing matches, or which direction the results spill. TheIFandTOCOLwrappers guard against both. - Microsoft doesn’t document that
=ignoresU+FE0Fand treats decomposed accents as equal. These results come from testing in Excel for Microsoft 365 and could change.
Citations
- “ASCII”. Wikipedia.
- “Mojibake”. Wikipedia.
- “Unicode”. Wikipedia.
- “Compatibility Versions”. Microsoft Support. Microsoft.
- “LEN function”. Microsoft Support. Microsoft.
- “Improving five Excel text functions: LEN, MID, SEARCH, FIND, and REPLACE + Compatibility Versions”. Microsoft 365 Insider Blog. Microsoft Tech Community.
- “Compatibility Versions in Excel: What you need to know”. FM Magazine. November 2025.
- “UTF-16”. Wikipedia.
- “FAQ: UTF-8, UTF-16, UTF-32 & BOM”. The Unicode Consortium.
- “Unicode Standard Annex #29: Unicode Text Segmentation”. The Unicode Consortium.
- “REGEXEXTRACT function”. Microsoft Support. Microsoft.
- “pcre2pattern: Perl-compatible regular expression syntax”. PCRE2 documentation.
- “emoji-zwj-sequences.txt”. The Unicode Consortium.
- “Unicode Technical Standard #51: Unicode Emoji”. The Unicode Consortium.
- “Unicode Standard Annex #15: Unicode Normalization Forms”. The Unicode Consortium.
- “UnicodeData.txt”. Unicode Character Database. The Unicode Consortium.
- “NormalizeString function (winnls.h)”. Microsoft Learn. Microsoft.
- “Unicode 16.0.0”. The Unicode Consortium. September 2024. “Unicode 16.0 adds 5185 characters, for a total of 154,998 characters.”
- “Unicode 17.0.0”. The Unicode Consortium. September 2025. “Unicode 17.0 adds 4803 characters, for a total of 159,801 characters.”
- “FAQ: Private-Use Characters, Noncharacters & Sentinels”. The Unicode Consortium.
- “Intl.Segmenter”. MDN Web Docs. Mozilla.
Cite this page
Freelove, Jeremy (2026). “Why LEN seems wrong sometimes: An introduction to Unicode, for spreadsheet people”. jeremyfreelove.com. Updated 18 September 2026.
Links point to the latest version of each page. Microsoft’s pages on Compatibility Versions describe a feature that is still rolling out, so check them again if something here doesn’t match your Excel.