<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  
  <title>Jeremy Freelove</title>
  <subtitle>Notes on things I&#39;m trying to understand.</subtitle>
  <link href="https://jeremyfreelove.com/feed.xml" rel="self" />
  <link href="https://jeremyfreelove.com/" />
  <updated>2026-09-18T00:00:00Z</updated>
  <id>https://jeremyfreelove.com/</id>
  <author>
    <name>Jeremy Freelove</name>
  </author>
  <entry>
    <title>Why LEN seems wrong sometimes</title>
    <link href="https://jeremyfreelove.com/writing/why-len-seems-wrong/" />
    <updated>2026-09-18T00:00:00Z</updated>
    <id>https://jeremyfreelove.com/writing/why-len-seems-wrong/</id>
    <summary>One emoji, eight characters, and why identical-looking text won&#39;t match.</summary>
    <content type="html">&lt;div class=&quot;shell&quot;&gt;
  &lt;nav class=&quot;toc&quot; id=&quot;toc&quot; aria-label=&quot;Table of contents&quot;&gt;
    &lt;h2&gt;Contents&lt;/h2&gt;
    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#start&quot;&gt;&lt;span class=&quot;n&quot;&gt;2602&lt;/span&gt;Start here&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#unicode&quot;&gt;&lt;span class=&quot;n&quot;&gt;0041&lt;/span&gt;Unicode from zero&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#puzzle&quot;&gt;&lt;span class=&quot;n&quot;&gt;1F468&lt;/span&gt;Why LEN says 8 (or 5)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#layers&quot;&gt;&lt;span class=&quot;n&quot;&gt;00E9&lt;/span&gt;Three meanings of &amp;ldquo;character&amp;rdquo;&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#versions&quot;&gt;&lt;span class=&quot;n&quot;&gt;1F438&lt;/span&gt;Compatibility versions&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a class=&quot;tool&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#workbench&quot;&gt;&lt;span class=&quot;n&quot;&gt;25CC&lt;/span&gt;Workbench&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#surrogates&quot;&gt;&lt;span class=&quot;n&quot;&gt;D83D&lt;/span&gt;Surrogate pairs&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#graphemes&quot;&gt;&lt;span class=&quot;n&quot;&gt;0301&lt;/span&gt;Grapheme clusters&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#regex&quot;&gt;&lt;span class=&quot;n&quot;&gt;0058&lt;/span&gt;Counting graphemes with &#92;X&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#zwj&quot;&gt;&lt;span class=&quot;n&quot;&gt;200D&lt;/span&gt;The zero-width joiner&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#presentation&quot;&gt;&lt;span class=&quot;n&quot;&gt;FE0F&lt;/span&gt;Text vs emoji style&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#excel&quot;&gt;&lt;span class=&quot;n&quot;&gt;003D&lt;/span&gt;Excel&amp;rsquo;s two kinds of equal&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#normalization&quot;&gt;&lt;span class=&quot;n&quot;&gt;212B&lt;/span&gt;Normalization&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#counting&quot;&gt;&lt;span class=&quot;n&quot;&gt;FFFD&lt;/span&gt;How many characters?&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#cheatsheet&quot;&gt;&lt;span class=&quot;n&quot;&gt;2713&lt;/span&gt;Cheat sheet&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#glossary&quot;&gt;&lt;span class=&quot;n&quot;&gt;00B6&lt;/span&gt;Glossary&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#references&quot;&gt;&lt;span class=&quot;n&quot;&gt;2020&lt;/span&gt;References&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
    &lt;p class=&quot;foot&quot;&gt;No Unicode background needed. Dotted-underlined words link to the glossary.&lt;/p&gt;
  &lt;/nav&gt;

  &lt;main&gt;
    &lt;!-- ================= 0 ================= --&gt;
    &lt;section id=&quot;start&quot; class=&quot;hero&quot; aria-labelledby=&quot;start-h&quot; style=&quot;margin-bottom:4rem&quot;&gt;
      &lt;h1 id=&quot;start-h&quot;&gt;Why &lt;code&gt;LEN&lt;/code&gt; seems wrong sometimes&lt;/h1&gt;
      &lt;p class=&quot;kicker&quot;&gt;An introduction to Unicode, for spreadsheet people&lt;/p&gt;
      &lt;p class=&quot;byline&quot;&gt;By &lt;a rel=&quot;author&quot; href=&quot;https://jeremyfreelove.com/&quot;&gt;Jeremy Freelove&lt;/a&gt; &lt;span aria-hidden=&quot;true&quot;&gt;&amp;middot;&lt;/span&gt; Updated &lt;time datetime=&quot;2026-09-18&quot;&gt;18 September 2026&lt;/time&gt;&lt;/p&gt;
      &lt;p&gt;Type one emoji into a cell and ask Excel how long it is:&lt;/p&gt;
      &lt;div class=&quot;sheet&quot; role=&quot;img&quot; aria-label=&quot;Cell A1 holds the family emoji; cell B1 holds =LEN(A1) and shows 8&quot;&gt;
        &lt;div class=&quot;fbar&quot;&gt;&lt;span class=&quot;namebox&quot;&gt;B1&lt;/span&gt;&lt;span class=&quot;fx&quot;&gt;fx&lt;/span&gt;&lt;span&gt;=LEN(A1)&lt;/span&gt;&lt;/div&gt;
        &lt;div class=&quot;cells&quot;&gt;
          &lt;span class=&quot;hd&quot;&gt;&lt;/span&gt;&lt;span class=&quot;hd&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;hd sel&quot;&gt;B&lt;/span&gt;
          &lt;span class=&quot;hd rh sel&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;emoji&quot;&gt;&amp;#x1F468;&amp;#x200D;&amp;#x1F469;&amp;#x200D;&amp;#x1F467;&lt;/span&gt;&lt;span class=&quot;val&quot;&gt;8&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;That&amp;rsquo;s one picture, and Excel says it&amp;rsquo;s eight characters long, or five in newer workbooks. Neither answer is 1. It isn&amp;rsquo;t a bug. Excel is counting things you can&amp;rsquo;t see, and which things it counts depends on a workbook setting.&lt;/p&gt;
      &lt;p&gt;This page explains what those things are, from the ground up, and assumes you know nothing about Unicode yet. By the end you&amp;rsquo;ll be able to explain both numbers, and why two pieces of text that look exactly alike can refuse to match.&lt;/p&gt;
      &lt;ul class=&quot;learn&quot;&gt;
        &lt;li&gt;&lt;b&gt;How text is stored&lt;/b&gt;Characters are numbers, and one character can take up several of the &amp;ldquo;slots&amp;rdquo; that Excel counts.&lt;/li&gt;
        &lt;li&gt;&lt;b&gt;Why LEN surprises you&lt;/b&gt;Excel counts storage slots or Unicode characters, never quite the characters you see.&lt;/li&gt;
        &lt;li&gt;&lt;b&gt;Why your Excel version matters&lt;/b&gt;A per-workbook setting changes what &lt;code&gt;LEN&lt;/code&gt;, &lt;code&gt;MID&lt;/code&gt;, &lt;code&gt;FIND&lt;/code&gt;, &lt;code&gt;SEARCH&lt;/code&gt; and &lt;code&gt;REPLACE&lt;/code&gt; count.&lt;/li&gt;
        &lt;li&gt;&lt;b&gt;Why = and EXACT disagree&lt;/b&gt;Excel has two definitions of &amp;ldquo;equal,&amp;rdquo; and invisible characters fall between them.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p class=&quot;small muted&quot; style=&quot;margin-top:1.2rem&quot;&gt;Already know this and just need to debug something? Go to the &lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#workbench&quot;&gt;workbench&lt;/a&gt; or the &lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#cheatsheet&quot;&gt;cheat sheet&lt;/a&gt;.&lt;/p&gt;
    &lt;/section&gt;

    &lt;!-- ================= 1 ================= --&gt;
    &lt;section id=&quot;unicode&quot; class=&quot;prose&quot; aria-labelledby=&quot;uni-h&quot;&gt;
      &lt;h2 id=&quot;uni-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;A&lt;/b&gt;&lt;i&gt;0041&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;Unicode from zero&lt;/span&gt;&lt;/h2&gt;
      &lt;p class=&quot;lede&quot;&gt;Computers can only store numbers. Text works because everyone agrees on which number stands for which character.&lt;/p&gt;
      &lt;p&gt;That agreement is called a &lt;strong&gt;character set&lt;/strong&gt;. One of the earliest was &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-ascii&quot;&gt;ASCII&lt;/a&gt; (1963)&lt;sup class=&quot;ref&quot; data-ref=&quot;wiki-ascii&quot;&gt;&lt;/sup&gt;. It covered 128 characters: English letters, digits and punctuation. In ASCII, &lt;code&gt;A&lt;/code&gt; is 65, &lt;code&gt;B&lt;/code&gt; is 66 and a space is 32. That was enough for English and nothing else. There was no &lt;code&gt;&amp;#xE9;&lt;/code&gt;, no &lt;code&gt;&amp;#x3A9;&lt;/code&gt; and no &lt;code&gt;&amp;#x2602;&lt;/code&gt;.&lt;/p&gt;
      &lt;p&gt;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 &lt;code&gt;caf&amp;#xC3;&amp;#xA9;&lt;/code&gt; where &lt;code&gt;caf&amp;#xE9;&lt;/code&gt; should be.&lt;sup class=&quot;ref&quot; data-ref=&quot;wiki-mojibake&quot;&gt;&lt;/sup&gt;&lt;/p&gt;
      &lt;p&gt;&lt;strong&gt;Unicode&lt;/strong&gt; (1991) fixed this by giving every character in every writing system its own number, plus symbols, math and eventually emoji.&lt;sup class=&quot;ref&quot; data-ref=&quot;wiki-unicode&quot;&gt;&lt;/sup&gt; That number is the character&amp;rsquo;s &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-codepoint&quot;&gt;code point&lt;/a&gt;.&lt;/p&gt;

      &lt;h3&gt;Reading U+ notation&lt;/h3&gt;
      &lt;p&gt;Code points are written as &lt;code&gt;U+&lt;/code&gt; followed by the number in &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-hex&quot;&gt;hexadecimal&lt;/a&gt; (base 16). Hex uses sixteen digits, &lt;code&gt;0&lt;/code&gt;&amp;ndash;&lt;code&gt;9&lt;/code&gt; and then &lt;code&gt;A&lt;/code&gt;&amp;ndash;&lt;code&gt;F&lt;/code&gt;, so &lt;code&gt;A&lt;/code&gt; means 10 and &lt;code&gt;F&lt;/code&gt; means 15.&lt;/p&gt;
      &lt;div class=&quot;hexbox&quot;&gt;
        &lt;div&gt;&lt;span&gt;hex &amp;rarr; decimal&lt;/span&gt;41 = 4&amp;times;16 + 1 = 65&lt;/div&gt;
        &lt;div&gt;&lt;span&gt;hex &amp;rarr; decimal&lt;/span&gt;FF = 15&amp;times;16 + 15 = 255&lt;/div&gt;
        &lt;div&gt;&lt;span&gt;largest 4-digit hex&lt;/span&gt;FFFF = 65,535&lt;/div&gt;
        &lt;div&gt;&lt;span&gt;largest code point&lt;/span&gt;10FFFF = 1,114,111&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;Why hex and not ordinary decimal? Each hex digit fits exactly into 4 &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-bit&quot;&gt;bits&lt;/a&gt;, so hex lines up neatly with how computers store data. &lt;strong&gt;Four hex digits are exactly 16 bits.&lt;/strong&gt; That fact explains the &lt;code&gt;LEN&lt;/code&gt; puzzle, so keep it in mind.&lt;/p&gt;
      &lt;div class=&quot;tablewrap&quot;&gt;
        &lt;table&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Character&lt;/th&gt;&lt;th&gt;Code point&lt;/th&gt;&lt;th class=&quot;num&quot;&gt;Decimal&lt;/th&gt;&lt;th&gt;What it is&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;A&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;U+0041&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;65&lt;/td&gt;&lt;td&gt;Same as ASCII. Unicode kept the first 128.&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#xE9;&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;U+00E9&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;233&lt;/td&gt;&lt;td&gt;Latin small letter e with acute&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x2602;&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;U+2602&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;9730&lt;/td&gt;&lt;td&gt;Umbrella&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x1F468;&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;U+1F468&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;128104&lt;/td&gt;&lt;td&gt;Man. &lt;strong&gt;Five&lt;/strong&gt; hex digits.&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;span class=&quot;inv&quot;&gt;ZWJ&lt;/span&gt;&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;U+200D&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;8205&lt;/td&gt;&lt;td&gt;Zero width joiner. Invisible, but it&amp;rsquo;s a real character.&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;h3&gt;Seeing code points in Excel&lt;/h3&gt;
      &lt;p&gt;Two functions convert between characters and their code points, and two more convert between decimal and hex:&lt;/p&gt;
      &lt;pre&gt;&lt;code&gt;=UNICODE(&quot;A&quot;)            → 65       character → code point (decimal)
=UNICHAR(9730)           → &amp;#x2602;        code point → character
=DEC2HEX(9730)           → 2602     decimal → hex, so this is U+2602
=UNICHAR(HEX2DEC(&quot;1F468&quot;)) → &amp;#x1F468;      from U+ notation back to the character&lt;/code&gt;&lt;/pre&gt;
      &lt;div class=&quot;callout&quot;&gt;&lt;span class=&quot;label&quot;&gt;Key idea&lt;/span&gt;&lt;p class=&quot;rule&quot;&gt;A code point is a character&amp;rsquo;s ID number. It doesn&amp;rsquo;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.&lt;/p&gt;&lt;/div&gt;
    &lt;/section&gt;

    &lt;!-- ================= 2 ================= --&gt;
    &lt;section id=&quot;puzzle&quot; class=&quot;prose&quot; aria-labelledby=&quot;pz-h&quot;&gt;
      &lt;h2 id=&quot;pz-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;&amp;#x1F468;&lt;/b&gt;&lt;i&gt;1F468&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;Why &lt;code style=&quot;font-size:.85em&quot;&gt;LEN&lt;/code&gt; says 8 (or 5)&lt;/span&gt;&lt;/h2&gt;
      &lt;p class=&quot;lede&quot;&gt;Back to the family emoji. We&amp;rsquo;ll count it three times, looking a little deeper into how it&amp;rsquo;s stored each time. Both of Excel&amp;rsquo;s answers will turn up along the way.&lt;/p&gt;
      &lt;div class=&quot;walk&quot;&gt;
        &lt;div class=&quot;stage s1&quot;&gt;
          &lt;div class=&quot;sn&quot;&gt;1&lt;/div&gt;
          &lt;div&gt;
            &lt;h4&gt;What you see &lt;span class=&quot;count&quot;&gt;1&lt;/span&gt;&lt;/h4&gt;
            &lt;p&gt;One picture of a family. It&amp;rsquo;s what you&amp;rsquo;d point at with your finger and call one character.&lt;/p&gt;
            &lt;div class=&quot;pieces&quot;&gt;&lt;div class=&quot;piece&quot;&gt;&lt;div class=&quot;pg&quot;&gt;&amp;#x1F468;&amp;#x200D;&amp;#x1F469;&amp;#x200D;&amp;#x1F467;&lt;/div&gt;&lt;div class=&quot;lab&quot;&gt;1 picture&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;stage s2&quot;&gt;
          &lt;div&gt;&lt;div class=&quot;sn&quot;&gt;2&lt;/div&gt;&lt;/div&gt;
          &lt;div&gt;
            &lt;h4&gt;What Unicode sees &lt;span class=&quot;count&quot;&gt;5&lt;/span&gt;&lt;/h4&gt;
            &lt;p&gt;Unicode has no single &amp;ldquo;family&amp;rdquo; code point for this. The picture is built from &lt;strong&gt;three people&lt;/strong&gt; joined by &lt;strong&gt;two invisible glue characters&lt;/strong&gt; called zero-width joiners (&lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-zwj&quot;&gt;ZWJ&lt;/a&gt;). Your phone or computer sees &lt;em&gt;man + glue + woman + glue + girl&lt;/em&gt; and draws one combined picture.&lt;/p&gt;
            &lt;div class=&quot;pieces&quot;&gt;
              &lt;div class=&quot;piece&quot;&gt;&lt;div class=&quot;pg&quot;&gt;&amp;#x1F468;&lt;/div&gt;&lt;div class=&quot;lab&quot;&gt;U+1F468&lt;/div&gt;&lt;/div&gt;
              &lt;div class=&quot;piece&quot;&gt;&lt;div class=&quot;pg&quot;&gt;&lt;span class=&quot;inv&quot;&gt;ZWJ&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;lab&quot;&gt;U+200D&lt;/div&gt;&lt;/div&gt;
              &lt;div class=&quot;piece&quot;&gt;&lt;div class=&quot;pg&quot;&gt;&amp;#x1F469;&lt;/div&gt;&lt;div class=&quot;lab&quot;&gt;U+1F469&lt;/div&gt;&lt;/div&gt;
              &lt;div class=&quot;piece&quot;&gt;&lt;div class=&quot;pg&quot;&gt;&lt;span class=&quot;inv&quot;&gt;ZWJ&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;lab&quot;&gt;U+200D&lt;/div&gt;&lt;/div&gt;
              &lt;div class=&quot;piece&quot;&gt;&lt;div class=&quot;pg&quot;&gt;&amp;#x1F467;&lt;/div&gt;&lt;div class=&quot;lab&quot;&gt;U+1F467&lt;/div&gt;&lt;/div&gt;
            &lt;/div&gt;
            &lt;p style=&quot;margin:.6rem 0 0&quot; class=&quot;small&quot;&gt;&lt;strong&gt;This is what &lt;code&gt;LEN&lt;/code&gt; reports in Compatibility Version 2 workbooks.&lt;/strong&gt;&lt;sup class=&quot;ref&quot; data-ref=&quot;compat&quot;&gt;&lt;/sup&gt;&lt;/p&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;stage s3&quot;&gt;
          &lt;div&gt;&lt;div class=&quot;sn&quot;&gt;3&lt;/div&gt;&lt;/div&gt;
          &lt;div&gt;
            &lt;h4&gt;How the text is stored &lt;span class=&quot;count&quot;&gt;8&lt;/span&gt;&lt;/h4&gt;
            &lt;p&gt;Excel stores text in &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-utf16&quot;&gt;UTF-16&lt;/a&gt;, which means every character goes into fixed 16-bit slots called &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-codeunit&quot;&gt;code units&lt;/a&gt;. Each slot holds exactly &lt;strong&gt;four hex digits&lt;/strong&gt;, up to &lt;code&gt;FFFF&lt;/code&gt;. The glue fits in one slot because &lt;code&gt;200D&lt;/code&gt; is four digits. Each person is five digits (&lt;code&gt;1F468&lt;/code&gt;) and doesn&amp;rsquo;t fit, so it&amp;rsquo;s split across &lt;strong&gt;two&lt;/strong&gt; slots.&lt;/p&gt;
            &lt;div class=&quot;pieces&quot;&gt;
              &lt;div class=&quot;piece&quot;&gt;&lt;div class=&quot;pg&quot;&gt;&amp;#x1F468;&lt;/div&gt;&lt;div class=&quot;boxes&quot;&gt;&lt;span class=&quot;box h&quot;&gt;D83D&lt;/span&gt;&lt;span class=&quot;box l&quot;&gt;DC68&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;lab&quot;&gt;2 slots&lt;/div&gt;&lt;/div&gt;
              &lt;div class=&quot;piece&quot;&gt;&lt;div class=&quot;pg&quot;&gt;&lt;span class=&quot;inv&quot;&gt;ZWJ&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;boxes&quot;&gt;&lt;span class=&quot;box&quot;&gt;200D&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;lab&quot;&gt;1 slot&lt;/div&gt;&lt;/div&gt;
              &lt;div class=&quot;piece&quot;&gt;&lt;div class=&quot;pg&quot;&gt;&amp;#x1F469;&lt;/div&gt;&lt;div class=&quot;boxes&quot;&gt;&lt;span class=&quot;box h&quot;&gt;D83D&lt;/span&gt;&lt;span class=&quot;box l&quot;&gt;DC69&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;lab&quot;&gt;2 slots&lt;/div&gt;&lt;/div&gt;
              &lt;div class=&quot;piece&quot;&gt;&lt;div class=&quot;pg&quot;&gt;&lt;span class=&quot;inv&quot;&gt;ZWJ&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;boxes&quot;&gt;&lt;span class=&quot;box&quot;&gt;200D&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;lab&quot;&gt;1 slot&lt;/div&gt;&lt;/div&gt;
              &lt;div class=&quot;piece&quot;&gt;&lt;div class=&quot;pg&quot;&gt;&amp;#x1F467;&lt;/div&gt;&lt;div class=&quot;boxes&quot;&gt;&lt;span class=&quot;box h&quot;&gt;D83D&lt;/span&gt;&lt;span class=&quot;box l&quot;&gt;DC67&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;lab&quot;&gt;2 slots&lt;/div&gt;&lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;tally&quot;&gt;2 + 1 + 2 + 1 + 2 = &lt;b&gt;8&lt;/b&gt; &amp;nbsp;&amp;larr; what &lt;code&gt;LEN&lt;/code&gt; reports in Compatibility Version 1&lt;sup class=&quot;ref&quot; data-ref=&quot;len&quot;&gt;&lt;/sup&gt;&lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;Neither answer is wrong. They answer different questions. In Version 1, &lt;code&gt;LEN&lt;/code&gt; answers &amp;ldquo;how many 16-bit slots does this take up?&amp;rdquo; and says 8. In Version 2, it answers &amp;ldquo;how many Unicode characters is this?&amp;rdquo; and says 5. Neither counts what you see. The split into two slots is called a &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-surrogate&quot;&gt;surrogate pair&lt;/a&gt; (&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#surrogates&quot;&gt;section 5&lt;/a&gt;). Which count your workbook uses depends on its &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-compat&quot;&gt;compatibility version&lt;/a&gt; (&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#versions&quot;&gt;section 4&lt;/a&gt;).&lt;/p&gt;
      &lt;p&gt;Check each stage yourself:&lt;/p&gt;
      &lt;div class=&quot;tablewrap&quot;&gt;
        &lt;table&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Formula&lt;/th&gt;&lt;th class=&quot;num&quot;&gt;Version 1&lt;/th&gt;&lt;th class=&quot;num&quot;&gt;Version 2&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td class=&quot;mono small&quot;&gt;=LEN(UNICHAR(128104))&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;2&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;mono small&quot;&gt;=LEN(UNICHAR(8205))&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;mono small&quot;&gt;=LEN(UNICHAR(128104)&amp;amp;UNICHAR(8205)&amp;amp;UNICHAR(128105)&amp;amp;UNICHAR(8205)&amp;amp;UNICHAR(128103))&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;8&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;5&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;p&gt;&lt;button class=&quot;try&quot; type=&quot;button&quot; data-cps=&quot;1F468 200D 1F469 200D 1F467&quot;&gt;Open the family in the workbench&lt;/button&gt;&lt;/p&gt;
    &lt;/section&gt;

    &lt;!-- ================= 3 ================= --&gt;
    &lt;section id=&quot;layers&quot; class=&quot;prose&quot; aria-labelledby=&quot;layers-h&quot;&gt;
      &lt;h2 id=&quot;layers-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;&amp;#xE9;&lt;/b&gt;&lt;i&gt;00E9&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;Three meanings of &amp;ldquo;character&amp;rdquo;&lt;/span&gt;&lt;/h2&gt;
      &lt;p class=&quot;lede&quot;&gt;The three counts you just saw have names. Most text bugs come from counting one of them while thinking about another.&lt;/p&gt;
      &lt;div class=&quot;tablewrap&quot;&gt;
        &lt;table&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;Plain English&lt;/th&gt;&lt;th class=&quot;num&quot;&gt;Family emoji&lt;/th&gt;&lt;th&gt;Excel counts it with&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td&gt;&lt;strong&gt;&lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-grapheme&quot;&gt;Grapheme&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;What a person sees and would call one character.&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td&gt;&lt;code&gt;REGEXEXTRACT&lt;/code&gt; + &lt;code&gt;&#92;X&lt;/code&gt; (&amp;sect;7)&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;strong&gt;&lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-codepoint&quot;&gt;Code point&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;One of Unicode&amp;rsquo;s numbered characters. A grapheme can be made of several.&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;5&lt;/td&gt;&lt;td&gt;&lt;code&gt;LEN&lt;/code&gt; in Version 2&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;strong&gt;&lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-codeunit&quot;&gt;Code unit&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;One storage slot. In UTF-16 a slot is 16 bits, and a code point takes one or two.&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;8&lt;/td&gt;&lt;td&gt;&lt;code&gt;LEN&lt;/code&gt; in Version 1&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;div class=&quot;analogy&quot;&gt;Think of typing &lt;code&gt;&amp;#xE9;&lt;/code&gt; on a keyboard that has no &amp;eacute; key: you press the accent key, then &lt;code&gt;e&lt;/code&gt;. That&amp;rsquo;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&amp;rsquo;re how much space those keystrokes take up in memory.&lt;/div&gt;
      &lt;div class=&quot;callout&quot;&gt;&lt;span class=&quot;label&quot;&gt;The rule&lt;/span&gt;&lt;p class=&quot;rule&quot;&gt;No version of &lt;code&gt;LEN&lt;/code&gt; counts graphemes. In Version 1 workbooks, &lt;code&gt;LEN&lt;/code&gt;, &lt;code&gt;MID&lt;/code&gt;, &lt;code&gt;FIND&lt;/code&gt;, &lt;code&gt;SEARCH&lt;/code&gt; and &lt;code&gt;REPLACE&lt;/code&gt; count code units. In Version 2 they count code points. Either way, invisible marks, skin tones and joiners all add to the count.&lt;/p&gt;&lt;/div&gt;
      &lt;p&gt;Here are more examples. Each one is a single grapheme, and they don&amp;rsquo;t all look like emoji:&lt;/p&gt;
      &lt;div class=&quot;tablewrap&quot;&gt;
        &lt;table&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Looks like&lt;/th&gt;&lt;th&gt;Made of&lt;/th&gt;&lt;th class=&quot;num&quot;&gt;Graphemes&lt;/th&gt;&lt;th class=&quot;num&quot;&gt;LEN v2&lt;br&gt;&lt;span style=&quot;text-transform:none&quot;&gt;code points&lt;/span&gt;&lt;/th&gt;&lt;th class=&quot;num&quot;&gt;LEN v1&lt;br&gt;&lt;span style=&quot;text-transform:none&quot;&gt;code units&lt;/span&gt;&lt;/th&gt;&lt;th&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;A&lt;/td&gt;&lt;td&gt;a plain letter&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x2602;&lt;/td&gt;&lt;td&gt;umbrella&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td&gt;&lt;button class=&quot;try&quot; type=&quot;button&quot; data-cps=&quot;2602&quot;&gt;Try&lt;/button&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x2602;&amp;#xFE0F;&lt;/td&gt;&lt;td&gt;umbrella + invisible &amp;ldquo;draw as emoji&amp;rdquo; marker&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;2&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;2&lt;/td&gt;&lt;td&gt;&lt;button class=&quot;try&quot; type=&quot;button&quot; data-cps=&quot;2602 FE0F&quot;&gt;Try&lt;/button&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x1F438;&lt;/td&gt;&lt;td&gt;frog, a five-digit code point&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;2&lt;/td&gt;&lt;td&gt;&lt;button class=&quot;try&quot; type=&quot;button&quot; data-cps=&quot;1F438&quot;&gt;Try&lt;/button&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;e&amp;#x301;&lt;/td&gt;&lt;td&gt;e + a combining accent&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;2&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;2&lt;/td&gt;&lt;td&gt;&lt;button class=&quot;try&quot; type=&quot;button&quot; data-cps=&quot;65 301&quot;&gt;Try&lt;/button&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x1F44B;&amp;#x1F3FD;&lt;/td&gt;&lt;td&gt;waving hand + skin tone&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;2&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;4&lt;/td&gt;&lt;td&gt;&lt;button class=&quot;try&quot; type=&quot;button&quot; data-cps=&quot;1F44B 1F3FD&quot;&gt;Try&lt;/button&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x1F1EF;&amp;#x1F1F5;&lt;/td&gt;&lt;td&gt;two &amp;ldquo;regional indicator&amp;rdquo; letters J + P = flag of Japan&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;2&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;4&lt;/td&gt;&lt;td&gt;&lt;button class=&quot;try&quot; type=&quot;button&quot; data-cps=&quot;1F1EF 1F1F5&quot;&gt;Try&lt;/button&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x1F468;&amp;#x200D;&amp;#x1F469;&amp;#x200D;&amp;#x1F467;&lt;/td&gt;&lt;td&gt;three people + two joiners&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;5&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;8&lt;/td&gt;&lt;td&gt;&lt;button class=&quot;try&quot; type=&quot;button&quot; data-cps=&quot;1F468 200D 1F469 200D 1F467&quot;&gt;Try&lt;/button&gt;&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;p class=&quot;small muted&quot;&gt;On Windows the flag shows as the letters &amp;ldquo;JP&amp;rdquo;, because Windows doesn&amp;rsquo;t include flag emoji. The data is fine; it&amp;rsquo;s still one grapheme.&lt;/p&gt;
    &lt;/section&gt;

    &lt;!-- ================= 4 ================= --&gt;
    &lt;section id=&quot;versions&quot; class=&quot;prose&quot; aria-labelledby=&quot;ver-h&quot;&gt;
      &lt;h2 id=&quot;ver-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;&amp;#x1F438;&lt;/b&gt;&lt;i&gt;1F438&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;Compatibility versions&lt;/span&gt;&lt;/h2&gt;
      &lt;p class=&quot;lede&quot;&gt;For decades, &lt;code&gt;LEN(&quot;&amp;#x1F600;&quot;)&lt;/code&gt; returned 2.&lt;sup class=&quot;ref&quot; data-ref=&quot;insider&quot;&gt;&lt;/sup&gt; Microsoft has now fixed that. To avoid silently changing the results of millions of existing spreadsheets, the fix is a per-workbook setting.&lt;/p&gt;
      &lt;p&gt;A &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-compat&quot;&gt;compatibility version&lt;/a&gt; is a workbook setting that decides which behaviour certain functions use. There are currently two:&lt;/p&gt;
      &lt;div class=&quot;versus&quot;&gt;
        &lt;div class=&quot;loose&quot;&gt;
          &lt;span class=&quot;tag&quot;&gt;Version 1&lt;/span&gt;
          &lt;h4&gt;Legacy&lt;/h4&gt;
          &lt;ul&gt;
            &lt;li&gt;Text functions count UTF-16 &lt;strong&gt;code units&lt;/strong&gt;, so an emoji outside the BMP counts as 2&lt;/li&gt;
            &lt;li&gt;Every Excel supports it&lt;/li&gt;
            &lt;li&gt;Workbooks created before the change open as Version 1&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/div&gt;
        &lt;div class=&quot;strict&quot;&gt;
          &lt;span class=&quot;tag&quot;&gt;Version 2&lt;/span&gt;
          &lt;h4&gt;Unicode-aware&lt;/h4&gt;
          &lt;ul&gt;
            &lt;li&gt;Text functions count &lt;strong&gt;code points&lt;/strong&gt;, so a surrogate pair counts as 1&lt;/li&gt;
            &lt;li&gt;Excel for Microsoft 365 (Windows and Mac) only&lt;/li&gt;
            &lt;li&gt;The default for new workbooks in the Current Channel since April 2026&lt;sup class=&quot;ref&quot; data-ref=&quot;compat&quot;&gt;&lt;/sup&gt;&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;h3&gt;What changes&lt;/h3&gt;
      &lt;p&gt;Microsoft lists exactly five functions: &lt;strong&gt;&lt;code&gt;LEN&lt;/code&gt;, &lt;code&gt;MID&lt;/code&gt;, &lt;code&gt;FIND&lt;/code&gt;, &lt;code&gt;SEARCH&lt;/code&gt; and &lt;code&gt;REPLACE&lt;/code&gt;&lt;/strong&gt;.&lt;sup class=&quot;ref&quot; data-ref=&quot;compat&quot;&gt;&lt;/sup&gt;&lt;sup class=&quot;ref&quot; data-ref=&quot;insider&quot;&gt;&lt;/sup&gt; In Version 2 they treat a surrogate pair as one character, so lengths and positions are counted in code points.&lt;/p&gt;
      &lt;div class=&quot;tablewrap&quot;&gt;
        &lt;table&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Text&lt;/th&gt;&lt;th&gt;Formula&lt;/th&gt;&lt;th class=&quot;num&quot;&gt;Version 1&lt;/th&gt;&lt;th class=&quot;num&quot;&gt;Version 2&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x1F438;&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;=LEN(UNICHAR(128056))&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;2&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x1F468;&amp;#x200D;&amp;#x1F469;&amp;#x200D;&amp;#x1F467;&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;LEN of the family&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;8&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;5&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x1F44B;&amp;#x1F3FD;&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;=LEN(UNICHAR(128075)&amp;amp;UNICHAR(127997))&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;4&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;2&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x2602;&amp;#xFE0F;&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;=LEN(UNICHAR(9730)&amp;amp;UNICHAR(65039))&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;2&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;2&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x1F438;&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;=MID(UNICHAR(128056), 1, 1)&lt;/td&gt;&lt;td&gt;half a frog (&amp;#xFFFD;)&lt;/td&gt;&lt;td&gt;the whole frog&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;h3&gt;What doesn&amp;rsquo;t change&lt;/h3&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;Version 2 still doesn&amp;rsquo;t count graphemes.&lt;/strong&gt; It counts code points. Microsoft specifically notes that variation selectors are still counted separately&lt;sup class=&quot;ref&quot; data-ref=&quot;len&quot;&gt;&lt;/sup&gt;, and so are skin tones and ZWJ joiners, which is why the family is still 5. For a grapheme count, see &lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#regex&quot;&gt;section 7&lt;/a&gt;.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Other text functions aren&amp;rsquo;t on the list.&lt;/strong&gt; Coverage of the change describes &lt;code&gt;LEFT&lt;/code&gt;, &lt;code&gt;RIGHT&lt;/code&gt;, &lt;code&gt;TEXTBEFORE&lt;/code&gt;, &lt;code&gt;TEXTAFTER&lt;/code&gt; and &lt;code&gt;TEXTSPLIT&lt;/code&gt; as already handling surrogate pairs correctly.&lt;sup class=&quot;ref&quot; data-ref=&quot;fm&quot;&gt;&lt;/sup&gt;&lt;sup class=&quot;ref&quot; data-ref=&quot;n-left&quot;&gt;&lt;/sup&gt; Comparisons (&lt;code&gt;=&lt;/code&gt;, &lt;code&gt;EXACT&lt;/code&gt;) and &lt;code&gt;SUBSTITUTE&lt;/code&gt; aren&amp;rsquo;t mentioned either, so section 10 applies in both versions.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;h3&gt;Checking and changing it&lt;/h3&gt;
      &lt;p&gt;Go to &lt;strong&gt;Formulas &amp;rsaquo; Calculation Options &amp;rsaquo; Compatibility Version&lt;/strong&gt;.&lt;sup class=&quot;ref&quot; data-ref=&quot;compat&quot;&gt;&lt;/sup&gt; The setting belongs to the workbook, not to your copy of Excel. Two workbooks open side by side can give different &lt;code&gt;LEN&lt;/code&gt; results for the same text.&lt;/p&gt;
      &lt;p&gt;There&amp;rsquo;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:&lt;/p&gt;
      &lt;pre&gt;&lt;code&gt;=IF(LEN(UNICHAR(128056)) = 1, &quot;Version 2&quot;, &quot;Version 1&quot;)&lt;/code&gt;&lt;/pre&gt;
      &lt;div class=&quot;callout warn&quot;&gt;&lt;span class=&quot;label&quot;&gt;Sharing workbooks&lt;/span&gt;
        &lt;p&gt;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.&lt;sup class=&quot;ref&quot; data-ref=&quot;fm&quot;&gt;&lt;/sup&gt; The five functions then fall back to counting code units. A formula like &lt;code&gt;=MID(A1, 3, 1)&lt;/code&gt; can return different text for the same cell depending on who opens the file.&lt;/p&gt;
      &lt;/div&gt;
      &lt;p class=&quot;small muted&quot;&gt;The rollout details are recent and may change. Microsoft&amp;rsquo;s Compatibility Versions page has the latest.&lt;sup class=&quot;ref&quot; data-ref=&quot;compat&quot;&gt;&lt;/sup&gt;&lt;/p&gt;
    &lt;/section&gt;

    &lt;!-- ================= WORKBENCH ================= --&gt;
    &lt;section id=&quot;workbench&quot; aria-labelledby=&quot;wb-h&quot;&gt;
      &lt;h2 id=&quot;wb-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;&amp;#x25CC;&lt;/b&gt;&lt;i&gt;25CC&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;Workbench&lt;/span&gt;&lt;/h2&gt;
      &lt;div class=&quot;prose&quot;&gt;
        &lt;p&gt;Now that you know the three counts, try your own text. Paste anything below, such as a cell&amp;rsquo;s contents, a name that won&amp;rsquo;t match, or an emoji. Everything runs in your browser and nothing is sent anywhere.&lt;/p&gt;
        &lt;details class=&quot;more&quot;&gt;
          &lt;summary&gt;How to read the inspector&lt;/summary&gt;
          &lt;div class=&quot;inner&quot;&gt;
            &lt;ul&gt;
              &lt;li&gt;&lt;strong&gt;The four numbers&lt;/strong&gt; are the counts from section 3. Code points are what &lt;code&gt;LEN&lt;/code&gt; returns in Version 2 workbooks; UTF-16 units are what it returns in Version 1.&lt;/li&gt;
              &lt;li&gt;&lt;strong&gt;The alignment strip&lt;/strong&gt; 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.&lt;/li&gt;
              &lt;li&gt;&lt;strong&gt;Orange dashed boxes&lt;/strong&gt; are invisible characters or characters that attach to the one before them. These are the usual suspects when text won&amp;rsquo;t match.&lt;/li&gt;
              &lt;li&gt;&lt;strong&gt;Click a row&lt;/strong&gt; to see each code point&amp;rsquo;s official name and number. &amp;ldquo;Break role&amp;rdquo; says whether it starts a new grapheme (Base) or attaches to the previous one (Extend, ZWJ).&lt;/li&gt;
              &lt;li&gt;&lt;strong&gt;The formula&lt;/strong&gt; at the bottom rebuilds your exact string in Excel, invisible characters included.&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/div&gt;
        &lt;/details&gt;
      &lt;/div&gt;

      &lt;div class=&quot;bench&quot;&gt;
        &lt;div class=&quot;card&quot; id=&quot;inspector&quot;&gt;
          &lt;h3&gt;Inspect a string&lt;/h3&gt;
          &lt;div class=&quot;sub&quot;&gt;One row per grapheme; click a row to see its code points.&lt;/div&gt;
          &lt;label for=&quot;insIn&quot; style=&quot;position:absolute;left:-9999px&quot;&gt;String to inspect&lt;/label&gt;
          &lt;div class=&quot;field&quot;&gt;
            &lt;input id=&quot;insIn&quot; class=&quot;input&quot; type=&quot;text&quot; spellcheck=&quot;false&quot; autocomplete=&quot;off&quot; autocapitalize=&quot;off&quot;&gt;
            &lt;button class=&quot;btn&quot; id=&quot;insClear&quot; type=&quot;button&quot; title=&quot;Clear&quot;&gt;Clear&lt;/button&gt;
          &lt;/div&gt;
          &lt;div class=&quot;presets&quot; id=&quot;insPresets&quot; aria-label=&quot;Examples&quot;&gt;&lt;/div&gt;

          &lt;div class=&quot;stats&quot; aria-live=&quot;polite&quot;&gt;
            &lt;div class=&quot;stat g1&quot;&gt;&lt;div class=&quot;v&quot; id=&quot;stG&quot;&gt;0&lt;/div&gt;&lt;div class=&quot;k&quot;&gt;Graphemes&lt;br&gt;what a person sees &amp;middot; &lt;code&gt;&#92;X&lt;/code&gt; count&lt;/div&gt;&lt;/div&gt;
            &lt;div class=&quot;stat g2&quot;&gt;&lt;div class=&quot;v&quot; id=&quot;stC&quot;&gt;0&lt;/div&gt;&lt;div class=&quot;k&quot;&gt;Code points&lt;br&gt;= &lt;code&gt;LEN&lt;/code&gt; in Version 2&lt;/div&gt;&lt;/div&gt;
            &lt;div class=&quot;stat g3&quot;&gt;&lt;div class=&quot;v&quot; id=&quot;stU&quot;&gt;0&lt;/div&gt;&lt;div class=&quot;k&quot;&gt;UTF-16 units&lt;br&gt;= &lt;code&gt;LEN&lt;/code&gt; in Version 1&lt;/div&gt;&lt;/div&gt;
            &lt;div class=&quot;stat&quot;&gt;&lt;div class=&quot;v&quot; id=&quot;stB&quot;&gt;0&lt;/div&gt;&lt;div class=&quot;k&quot;&gt;UTF-8 bytes&lt;br&gt;size in a CSV file&lt;/div&gt;&lt;/div&gt;
          &lt;/div&gt;

          &lt;div class=&quot;align&quot; id=&quot;insAlign&quot;&gt;&lt;/div&gt;
          &lt;div class=&quot;glist&quot; id=&quot;insList&quot;&gt;&lt;/div&gt;
          &lt;div class=&quot;excelf&quot; id=&quot;insExcel&quot; hidden=&quot;&quot;&gt;
            &lt;pre&gt;&lt;code id=&quot;insFormula&quot;&gt;&lt;/code&gt;&lt;/pre&gt;
            &lt;button class=&quot;btn&quot; id=&quot;insCopy&quot; type=&quot;button&quot;&gt;Copy&lt;/button&gt;
          &lt;/div&gt;
          &lt;p class=&quot;small muted&quot; id=&quot;insExcelNote&quot; style=&quot;margin:.5rem 0 0&quot; hidden=&quot;&quot;&gt;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 &lt;code&gt;EXACT&lt;/code&gt;.&lt;/p&gt;
        &lt;/div&gt;

        &lt;div class=&quot;card&quot; id=&quot;comparator&quot;&gt;
          &lt;h3&gt;Are these equal?&lt;/h3&gt;
          &lt;div class=&quot;sub&quot;&gt;Type two strings. Each box answers &amp;ldquo;are they equal?&amp;rdquo; by a different rule. The first works like Excel&amp;rsquo;s &lt;code&gt;EXACT()&lt;/code&gt; and the last models Excel&amp;rsquo;s &lt;code&gt;=&lt;/code&gt;. &lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#excel&quot;&gt;Section 10&lt;/a&gt; explains why they differ.&lt;/div&gt;
          &lt;div class=&quot;cmp-inputs&quot;&gt;
            &lt;div&gt;&lt;span class=&quot;lbl&quot;&gt;A&lt;/span&gt;&lt;input id=&quot;cmpA&quot; class=&quot;input&quot; type=&quot;text&quot; spellcheck=&quot;false&quot; autocomplete=&quot;off&quot; aria-label=&quot;String A&quot;&gt;&lt;/div&gt;
            &lt;button class=&quot;btn swap&quot; id=&quot;cmpSwap&quot; type=&quot;button&quot; title=&quot;Swap A and B&quot; aria-label=&quot;Swap A and B&quot;&gt;&amp;#x21C4;&lt;/button&gt;
            &lt;div&gt;&lt;span class=&quot;lbl&quot;&gt;B&lt;/span&gt;&lt;input id=&quot;cmpB&quot; class=&quot;input&quot; type=&quot;text&quot; spellcheck=&quot;false&quot; autocomplete=&quot;off&quot; aria-label=&quot;String B&quot;&gt;&lt;/div&gt;
          &lt;/div&gt;
          &lt;div class=&quot;presets&quot; id=&quot;cmpPresets&quot; aria-label=&quot;Example pairs&quot;&gt;&lt;/div&gt;
          &lt;div class=&quot;verdicts&quot; id=&quot;cmpOut&quot; aria-live=&quot;polite&quot;&gt;&lt;/div&gt;
          &lt;div class=&quot;diff&quot; id=&quot;cmpDiff&quot;&gt;&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/section&gt;

    &lt;!-- ================= 5 ================= --&gt;
    &lt;section id=&quot;surrogates&quot; class=&quot;prose&quot; aria-labelledby=&quot;sur-h&quot;&gt;
      &lt;h2 id=&quot;sur-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;&lt;span class=&quot;inv&quot;&gt;HI&lt;/span&gt;&lt;/b&gt;&lt;i&gt;D83D&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;Surrogate pairs&lt;/span&gt;&lt;/h2&gt;
      &lt;p class=&quot;lede&quot;&gt;This section explains how a code point too big for one slot gets split into two. It&amp;rsquo;s the reason for Version 1&amp;rsquo;s 8, and the thing Version 2 was built to hide.&lt;/p&gt;
      &lt;h3&gt;A little history&lt;/h3&gt;
      &lt;p&gt;Early Unicode assumed 65,536 characters (&lt;code&gt;0000&lt;/code&gt;&amp;ndash;&lt;code&gt;FFFF&lt;/code&gt;) 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 &lt;code&gt;10FFFF&lt;/code&gt;.&lt;sup class=&quot;ref&quot; data-ref=&quot;wiki-utf16&quot;&gt;&lt;/sup&gt;&lt;/p&gt;
      &lt;p&gt;Those systems couldn&amp;rsquo;t switch to bigger slots without breaking everything already written. So Unicode set aside 2,048 values in the 16-bit range that aren&amp;rsquo;t characters at all. Each one means &amp;ldquo;I&amp;rsquo;m half of a bigger number.&amp;rdquo; These are &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-surrogate&quot;&gt;surrogates&lt;/a&gt;&lt;sup class=&quot;ref&quot; data-ref=&quot;faq-utf&quot;&gt;&lt;/sup&gt;:&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;A &lt;strong&gt;high surrogate&lt;/strong&gt; (&lt;code&gt;D800&lt;/code&gt;&amp;ndash;&lt;code&gt;DBFF&lt;/code&gt;) always comes first and carries the top half of the number.&lt;/li&gt;
        &lt;li&gt;A &lt;strong&gt;low surrogate&lt;/strong&gt; (&lt;code&gt;DC00&lt;/code&gt;&amp;ndash;&lt;code&gt;DFFF&lt;/code&gt;) always comes second and carries the bottom half.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;Together they make a &lt;strong&gt;surrogate pair&lt;/strong&gt;: two slots that represent one code point. The two ranges don&amp;rsquo;t overlap, so any slot tells you whether it&amp;rsquo;s a first half, a second half, or a normal character.&lt;/p&gt;
      &lt;div class=&quot;analogy&quot;&gt;It works like writing a long number across two boxes on a paper form, where the boxes are different colours so nobody mixes up which half is which.&lt;/div&gt;
      &lt;p&gt;The code space is divided into 17 &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-plane&quot;&gt;planes&lt;/a&gt; of 65,536 code points each. Plane 0, the &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-bmp&quot;&gt;Basic Multilingual Plane&lt;/a&gt;, covers everything up to &lt;code&gt;FFFF&lt;/code&gt;. That includes almost all everyday text in most languages, and it fits in one slot. Planes 1&amp;ndash;16, sometimes called the &amp;ldquo;astral&amp;rdquo; planes, need a pair. &lt;strong&gt;Most emoji live in plane 1&lt;/strong&gt;, which is why a single emoji has a &lt;code&gt;LEN&lt;/code&gt; of 2 in Version 1 workbooks.&lt;/p&gt;

      &lt;h3&gt;Worked example: &lt;span class=&quot;g&quot;&gt;&amp;#x1F438;&lt;/span&gt; &lt;code class=&quot;cp&quot;&gt;U+1F438&lt;/code&gt; frog face&lt;/h3&gt;
      &lt;p&gt;&lt;code&gt;1F438&lt;/code&gt; is bigger than &lt;code&gt;FFFF&lt;/code&gt;, so it needs two slots. The recipe is: subtract &lt;code&gt;10000&lt;/code&gt;, split what&amp;rsquo;s left into two 10-bit halves, and add each half to the start of its surrogate range.&lt;sup class=&quot;ref&quot; data-ref=&quot;faq-utf&quot;&gt;&lt;/sup&gt;&lt;/p&gt;
      &lt;div class=&quot;card bits&quot; style=&quot;margin:1rem 0 1.25rem&quot;&gt;
        &lt;div class=&quot;row&quot;&gt;&lt;span&gt;code point&lt;/span&gt;&lt;span&gt;0x1F438 &amp;nbsp;= 128,056&lt;/span&gt;&lt;/div&gt;
        &lt;div class=&quot;row&quot;&gt;&lt;span&gt;&amp;minus; 0x10000&lt;/span&gt;&lt;span&gt;0xF438 &amp;nbsp;(now fits in 20 bits)&lt;/span&gt;&lt;/div&gt;
        &lt;div class=&quot;row&quot;&gt;&lt;span&gt;20 bits, split 10/10&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;b h&quot;&gt;0000111101&lt;/span&gt; &lt;span class=&quot;b l&quot;&gt;0000111000&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
        &lt;div class=&quot;row&quot;&gt;&lt;span&gt;top half + 0xD800&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;b h&quot;&gt;0x3D + 0xD800 = D83D&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
        &lt;div class=&quot;row&quot;&gt;&lt;span&gt;bottom half + 0xDC00&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;b l&quot;&gt;0x38 + 0xDC00 = DC38&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
        &lt;div class=&quot;row&quot;&gt;&lt;span&gt;stored as&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;b h&quot;&gt;D83D&lt;/span&gt; &lt;span class=&quot;b l&quot;&gt;DC38&lt;/span&gt; &amp;nbsp;&amp;rarr; 2 slots&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
      &lt;details class=&quot;more&quot;&gt;
        &lt;summary&gt;Going backwards: pair &amp;rarr; code point&lt;/summary&gt;
        &lt;div class=&quot;inner&quot;&gt;
          &lt;pre&gt;&lt;code&gt;cp = (hi − 0xD800) × 0x400 + (lo − 0xDC00) + 0x10000
   = (0xD83D − 0xD800) × 0x400 + (0xDC38 − 0xDC00) + 0x10000
   = 0x3D × 0x400 + 0x38 + 0x10000
   = 0x1F438  ✓&lt;/code&gt;&lt;/pre&gt;
          &lt;p class=&quot;small muted&quot;&gt;&lt;code&gt;0x400&lt;/code&gt; is 1,024 = 2&lt;sup&gt;10&lt;/sup&gt;, the size of each half. The &lt;code&gt;0x&lt;/code&gt; prefix just means &amp;ldquo;this number is hex.&amp;rdquo;&lt;/p&gt;
        &lt;/div&gt;
      &lt;/details&gt;
      &lt;p&gt;It&amp;rsquo;s still &lt;strong&gt;one&lt;/strong&gt; code point. The pair only exists in UTF-16. Other &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-encoding&quot;&gt;encodings&lt;/a&gt; store the same frog differently:&lt;/p&gt;
      &lt;div class=&quot;enc&quot;&gt;
        &lt;div&gt;&lt;b&gt;UTF-8&lt;/b&gt;&lt;span class=&quot;v&quot;&gt;F0 9F 90 B8&lt;/span&gt;&lt;small&gt;4 bytes. Used by the web and most CSV files.&lt;/small&gt;&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;UTF-16&lt;/b&gt;&lt;span class=&quot;v&quot;&gt;D83D DC38&lt;/span&gt;&lt;small&gt;2 &amp;times; 16-bit slots. Used by Excel, Windows and JavaScript.&lt;/small&gt;&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;UTF-32&lt;/b&gt;&lt;span class=&quot;v&quot;&gt;0001F438&lt;/span&gt;&lt;small&gt;1 &amp;times; 32-bit slot. Simple, but uses a lot of space.&lt;/small&gt;&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;callout warn&quot;&gt;
        &lt;span class=&quot;label&quot;&gt;Where this bites (Version 1 workbooks)&lt;/span&gt;
        &lt;p&gt;&lt;code&gt;MID&lt;/code&gt; counts slots, so it can cut a pair in half. &lt;code class=&quot;f&quot;&gt;=MID(UNICHAR(128056), 1, 1)&lt;/code&gt; returns only the high surrogate, which displays as &amp;#xFFFD; or an empty box. Passing that half to &lt;code&gt;UNICODE()&lt;/code&gt; gives &lt;code&gt;#VALUE!&lt;/code&gt;, because half a pair isn&amp;rsquo;t a character. &lt;code&gt;FIND&lt;/code&gt; and &lt;code&gt;SEARCH&lt;/code&gt; positions are also counted in slots, so a position found in one version can point at the wrong character in the other.&lt;/p&gt;
        &lt;p&gt;In Version 2, &lt;code&gt;MID&lt;/code&gt; counts code points and returns the whole frog. &lt;code&gt;UNICODE&lt;/code&gt; works in both versions when you give it a complete pair: &lt;code class=&quot;f&quot;&gt;=UNICODE(&quot;&amp;#x1F438;&quot;)&lt;/code&gt; returns 128056.&lt;/p&gt;
      &lt;/div&gt;
      &lt;p&gt;&lt;button class=&quot;try&quot; type=&quot;button&quot; data-cps=&quot;1F438&quot;&gt;Open the frog in the workbench&lt;/button&gt; &lt;button class=&quot;try&quot; type=&quot;button&quot; data-cps=&quot;D83D&quot;&gt;Open a lone high surrogate&lt;/button&gt;&lt;/p&gt;
    &lt;/section&gt;

    &lt;!-- ================= 6 ================= --&gt;
    &lt;section id=&quot;graphemes&quot; class=&quot;prose&quot; aria-labelledby=&quot;gr-h&quot;&gt;
      &lt;h2 id=&quot;gr-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;&amp;#x25CC;&amp;#x301;&lt;/b&gt;&lt;i&gt;0301&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;Grapheme clusters&lt;/span&gt;&lt;/h2&gt;
      &lt;p class=&quot;lede&quot;&gt;A grapheme is what a reader sees as one character. Unicode&amp;rsquo;s name for it is an &lt;em&gt;extended grapheme cluster&lt;/em&gt;: a base character plus everything attached to it.&lt;/p&gt;
      &lt;p&gt;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:&lt;/p&gt;
      &lt;div class=&quot;examples&quot;&gt;
        &lt;div class=&quot;ex&quot;&gt;&lt;div class=&quot;glyph&quot;&gt;e&amp;#x301;&lt;/div&gt;&lt;div class=&quot;seq&quot;&gt;&lt;span class=&quot;chip&quot;&gt;U+0065&lt;/span&gt;&lt;span class=&quot;plus&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;chip inv&quot;&gt;U+0301&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;desc&quot;&gt;The letter e, then a &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-combining&quot;&gt;combining&lt;/a&gt; acute accent that sits on top of it.&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;ex&quot;&gt;&lt;div class=&quot;glyph&quot;&gt;&amp;#x1F44B;&amp;#x1F3FD;&lt;/div&gt;&lt;div class=&quot;seq&quot;&gt;&lt;span class=&quot;chip&quot;&gt;U+1F44B&lt;/span&gt;&lt;span class=&quot;plus&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;chip inv&quot;&gt;U+1F3FD&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;desc&quot;&gt;A waving hand, then a skin-tone modifier. On its own the modifier is a colour swatch.&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;ex&quot;&gt;&lt;div class=&quot;glyph&quot;&gt;&amp;#x1F468;&amp;#x200D;&amp;#x1F469;&amp;#x200D;&amp;#x1F467;&lt;/div&gt;&lt;div class=&quot;seq&quot;&gt;&lt;span class=&quot;chip&quot;&gt;1F468&lt;/span&gt;&lt;span class=&quot;chip inv&quot;&gt;200D&lt;/span&gt;&lt;span class=&quot;chip&quot;&gt;1F469&lt;/span&gt;&lt;span class=&quot;chip inv&quot;&gt;200D&lt;/span&gt;&lt;span class=&quot;chip&quot;&gt;1F467&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;desc&quot;&gt;People joined by zero-width joiners (section 8).&lt;/div&gt;&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;Unicode doesn&amp;rsquo;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: &amp;ldquo;I&amp;rsquo;m a normal character,&amp;rdquo; &amp;ldquo;I attach to the previous one,&amp;rdquo; &amp;ldquo;I&amp;rsquo;m a joiner,&amp;rdquo; &amp;ldquo;I&amp;rsquo;m half a flag,&amp;rdquo; and so on. A short set of rules in a document called &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-uax29&quot;&gt;UAX #29&lt;/a&gt;&lt;sup class=&quot;ref&quot; data-ref=&quot;uax29&quot;&gt;&lt;/sup&gt; uses those properties to decide where one grapheme ends and the next begins. In short:&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;Accents and modifiers attach.&lt;/strong&gt; Combining marks, skin tones and the invisible emoji-style marker &lt;code&gt;U+FE0F&lt;/code&gt; stay with the character before them.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Joined emoji stay together.&lt;/strong&gt; Emoji + ZWJ + emoji forms one grapheme.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Flag letters pair up.&lt;/strong&gt; Regional-indicator letters join two at a time, so &lt;span class=&quot;g&quot;&gt;&amp;#x1F1EF;&amp;#x1F1F5;&amp;#x1F1EB;&amp;#x1F1F7;&lt;/span&gt; is two flags (Japan and France) and not one four-letter blob.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Everything else is its own grapheme.&lt;/strong&gt;&lt;/li&gt;
      &lt;/ul&gt;
      &lt;details class=&quot;more&quot;&gt;
        &lt;summary&gt;The actual rules (optional)&lt;/summary&gt;
        &lt;div class=&quot;inner&quot;&gt;
          &lt;p class=&quot;small&quot;&gt;&amp;times; means &amp;ldquo;don&amp;rsquo;t break here&amp;rdquo; and &amp;divide; means &amp;ldquo;break here.&amp;rdquo; The names (Extend, ZWJ, RI&amp;hellip;) are values of each code point&amp;rsquo;s &lt;em&gt;Grapheme_Cluster_Break&lt;/em&gt; property.&lt;/p&gt;
          &lt;div class=&quot;tablewrap&quot;&gt;
            &lt;table&gt;
              &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Rule&lt;/th&gt;&lt;th&gt;Pattern&lt;/th&gt;&lt;th&gt;Meaning&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
              &lt;tbody&gt;
                &lt;tr&gt;&lt;td class=&quot;mono&quot;&gt;GB3&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;CR &amp;times; LF&lt;/td&gt;&lt;td&gt;A Windows line break (&lt;code&gt;&#92;r&#92;n&lt;/code&gt;) is one grapheme.&lt;/td&gt;&lt;/tr&gt;
                &lt;tr&gt;&lt;td class=&quot;mono&quot;&gt;GB9&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;&amp;times; (Extend | ZWJ)&lt;/td&gt;&lt;td&gt;Never break before an accent, &lt;code&gt;U+FE0F&lt;/code&gt;, a skin tone or a ZWJ.&lt;/td&gt;&lt;/tr&gt;
                &lt;tr&gt;&lt;td class=&quot;mono&quot;&gt;GB9a/b&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;&amp;times; SpacingMark, Prepend &amp;times;&lt;/td&gt;&lt;td&gt;Keeps vowel signs attached in scripts such as Hindi and Thai.&lt;/td&gt;&lt;/tr&gt;
                &lt;tr&gt;&lt;td class=&quot;mono&quot;&gt;GB11&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;ExtPict Extend* ZWJ &amp;times; ExtPict&lt;/td&gt;&lt;td&gt;Emoji joined with a ZWJ stay together.&lt;/td&gt;&lt;/tr&gt;
                &lt;tr&gt;&lt;td class=&quot;mono&quot;&gt;GB12/13&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;RI RI &amp;times; RI RI&lt;/td&gt;&lt;td&gt;Flag letters pair up two at a time.&lt;/td&gt;&lt;/tr&gt;
                &lt;tr&gt;&lt;td class=&quot;mono&quot;&gt;GB999&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;Any &amp;divide; Any&lt;/td&gt;&lt;td&gt;Otherwise, break.&lt;/td&gt;&lt;/tr&gt;
              &lt;/tbody&gt;
            &lt;/table&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/details&gt;
      &lt;p&gt;No version of &lt;code&gt;LEN&lt;/code&gt; follows these rules. Excel does have a way to split text into graphemes, though, and it&amp;rsquo;s covered in the next section.&lt;/p&gt;
    &lt;/section&gt;

    &lt;!-- ================= 7 ================= --&gt;
    &lt;section id=&quot;regex&quot; class=&quot;prose&quot; aria-labelledby=&quot;rx-h&quot;&gt;
      &lt;h2 id=&quot;rx-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;X&lt;/b&gt;&lt;i&gt;0058&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;Counting graphemes with &lt;code style=&quot;font-size:.85em&quot;&gt;&amp;#92;X&lt;/code&gt;&lt;/span&gt;&lt;/h2&gt;
      &lt;p class=&quot;lede&quot;&gt;Excel for Microsoft 365 has three regular expression functions: &lt;code&gt;REGEXTEST&lt;/code&gt;, &lt;code&gt;REGEXEXTRACT&lt;/code&gt; and &lt;code&gt;REGEXREPLACE&lt;/code&gt;. Their regex language includes a pattern that matches exactly one grapheme.&lt;/p&gt;
      &lt;p&gt;A &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-regex&quot;&gt;regular expression&lt;/a&gt; (regex) is a small pattern language for describing text. Excel&amp;rsquo;s regex functions use a variety called &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-pcre2&quot;&gt;PCRE2&lt;/a&gt;&lt;sup class=&quot;ref&quot; data-ref=&quot;regexextract&quot;&gt;&lt;/sup&gt;, where &lt;code&gt;&amp;#92;X&lt;/code&gt; (capital X) means &amp;ldquo;one extended grapheme cluster.&amp;rdquo;&lt;sup class=&quot;ref&quot; data-ref=&quot;pcre2&quot;&gt;&lt;/sup&gt; That&amp;rsquo;s the same unit the grapheme rules in section 6 produce: what a reader sees as one character.&lt;/p&gt;
      &lt;p&gt;&lt;code&gt;REGEXEXTRACT(text, pattern, [return_mode])&lt;/code&gt; returns the text that matches the pattern. With &lt;code&gt;return_mode&lt;/code&gt; set to &lt;code&gt;1&lt;/code&gt; it returns &lt;em&gt;every&lt;/em&gt; match as an array that spills into neighbouring cells.&lt;sup class=&quot;ref&quot; data-ref=&quot;regexextract&quot;&gt;&lt;/sup&gt; Using &lt;code&gt;&amp;#92;X&lt;/code&gt; as the pattern returns one cell per grapheme:&lt;/p&gt;
      &lt;pre&gt;&lt;code&gt;A1:  &amp;#x1F468;&amp;#x200D;&amp;#x1F469;&amp;#x200D;&amp;#x1F467;&amp;#x1F438;e&amp;#x301;

=REGEXEXTRACT(A1, &quot;&amp;#92;X&quot;, 1)        → &amp;#x1F468;&amp;#x200D;&amp;#x1F469;&amp;#x200D;&amp;#x1F467; | &amp;#x1F438; | e&amp;#x301;     (spills 3 cells)&lt;/code&gt;&lt;/pre&gt;
      &lt;p&gt;Now you can do what &lt;code&gt;LEN&lt;/code&gt; never could:&lt;/p&gt;
      &lt;div class=&quot;tablewrap&quot;&gt;
        &lt;table&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Goal&lt;/th&gt;&lt;th&gt;Formula&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td&gt;Count graphemes&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;=IF(A1=&quot;&quot;, 0, COUNTA(REGEXEXTRACT(A1, &quot;&amp;#92;X&quot;, 1)))&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;First grapheme (a safe &lt;code&gt;LEFT(A1,1)&lt;/code&gt;)&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;=REGEXEXTRACT(A1, &quot;&amp;#92;X&quot;)&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;First &lt;em&gt;n&lt;/em&gt; graphemes&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;=CONCAT(TAKE(TOCOL(REGEXEXTRACT(A1, &quot;&amp;#92;X&quot;, 1)), n))&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Size of each grapheme&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;=LEN(REGEXEXTRACT(A1, &quot;&amp;#92;X&quot;, 1))&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Every code point number&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;=UNICODE(REGEXEXTRACT(A1, &quot;(?s).&quot;, 1))&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;ul class=&quot;small&quot;&gt;
        &lt;li&gt;&lt;strong&gt;The &lt;code&gt;IF&lt;/code&gt; guard.&lt;/strong&gt; When nothing matches (an empty cell), &lt;code&gt;REGEXEXTRACT&lt;/code&gt; returns an error, and &lt;code&gt;COUNTA&lt;/code&gt; would count that error as 1.&lt;sup class=&quot;ref&quot; data-ref=&quot;n-regex&quot;&gt;&lt;/sup&gt;&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;&lt;code&gt;TOCOL&lt;/code&gt;&lt;/strong&gt; makes the spill a single column whichever direction it comes out, so &lt;code&gt;TAKE&lt;/code&gt; always takes the first &lt;em&gt;n&lt;/em&gt; items.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;&lt;code&gt;&quot;(?s).&quot;&lt;/code&gt;&lt;/strong&gt;: a &lt;code&gt;.&lt;/code&gt; matches one code point, never half a surrogate pair, so this lists code points the same way in Version 1 and Version 2. &lt;code&gt;(?s)&lt;/code&gt; lets &lt;code&gt;.&lt;/code&gt; match line breaks too.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;No escaping needed.&lt;/strong&gt; Backslashes are ordinary characters in Excel strings, so type &lt;code&gt;&quot;&amp;#92;X&quot;&lt;/code&gt; exactly as shown. Only double quotes need doubling.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;The family emoji, all three ways:&lt;/p&gt;
      &lt;div class=&quot;tablewrap&quot;&gt;
        &lt;table&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Formula&lt;/th&gt;&lt;th&gt;Counts&lt;/th&gt;&lt;th class=&quot;num&quot;&gt;Result&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td class=&quot;mono small&quot;&gt;=LEN(A1)&lt;/td&gt;&lt;td&gt;code units (Version 1)&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;8&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;mono small&quot;&gt;=LEN(A1)&lt;/td&gt;&lt;td&gt;code points (Version 2)&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;5&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;mono small&quot;&gt;=COUNTA(REGEXEXTRACT(A1, &quot;&amp;#92;X&quot;, 1))&lt;/td&gt;&lt;td&gt;graphemes (either version)&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;div class=&quot;callout warn&quot;&gt;&lt;span class=&quot;label&quot;&gt;Caveats&lt;/span&gt;
        &lt;p&gt;&amp;bull; The regex functions are only in Excel for Microsoft 365 (Windows and Mac). Older versions show &lt;code&gt;#NAME?&lt;/code&gt;.&lt;/p&gt;
        &lt;p&gt;&amp;bull; 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.&lt;/p&gt;
        &lt;p&gt;&amp;bull; &lt;code&gt;&amp;#92;X&lt;/code&gt; 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).&lt;/p&gt;
      &lt;/div&gt;
      &lt;p&gt;Regex is also a compact way to delete invisible characters. This removes all 16 variation selectors in one go:&lt;/p&gt;
      &lt;pre&gt;&lt;code&gt;=REGEXREPLACE(A1, &quot;[&amp;#92;x{FE00}-&amp;#92;x{FE0F}]&quot;, &quot;&quot;)&lt;/code&gt;&lt;/pre&gt;
      &lt;p class=&quot;small muted&quot;&gt;&lt;code&gt;&amp;#92;x{FE0F}&lt;/code&gt; is PCRE2&amp;rsquo;s way to write a code point in hex, and &lt;code&gt;[&amp;hellip;-&amp;hellip;]&lt;/code&gt; matches anything in that range.&lt;/p&gt;
    &lt;/section&gt;

    &lt;!-- ================= 8 ================= --&gt;
    &lt;section id=&quot;zwj&quot; class=&quot;prose&quot; aria-labelledby=&quot;zwj-h&quot;&gt;
      &lt;h2 id=&quot;zwj-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;&lt;span class=&quot;inv&quot;&gt;ZWJ&lt;/span&gt;&lt;/b&gt;&lt;i&gt;200D&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;The zero-width joiner &lt;code class=&quot;cp&quot; style=&quot;font-size:.6em&quot;&gt;U+200D&lt;/code&gt;&lt;/span&gt;&lt;/h2&gt;
      &lt;p class=&quot;lede&quot;&gt;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.&lt;/p&gt;
      &lt;div class=&quot;examples&quot;&gt;
        &lt;div class=&quot;ex&quot;&gt;&lt;div class=&quot;glyph&quot;&gt;&amp;#x1F469;&amp;#x200D;&amp;#x1F4BB;&lt;/div&gt;&lt;div class=&quot;seq&quot;&gt;&lt;span class=&quot;chip&quot;&gt;1F469&lt;/span&gt;&lt;span class=&quot;chip inv&quot;&gt;200D&lt;/span&gt;&lt;span class=&quot;chip&quot;&gt;1F4BB&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;desc&quot;&gt;woman + ZWJ + laptop = woman technologist. &lt;code&gt;LEN&lt;/code&gt; 5 (v1) or 3 (v2). &lt;button class=&quot;try&quot; type=&quot;button&quot; data-cps=&quot;1F469 200D 1F4BB&quot;&gt;Try&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;ex&quot;&gt;&lt;div class=&quot;glyph&quot;&gt;&amp;#x2764;&amp;#xFE0F;&amp;#x200D;&amp;#x1F525;&lt;/div&gt;&lt;div class=&quot;seq&quot;&gt;&lt;span class=&quot;chip&quot;&gt;2764&lt;/span&gt;&lt;span class=&quot;chip inv&quot;&gt;FE0F&lt;/span&gt;&lt;span class=&quot;chip inv&quot;&gt;200D&lt;/span&gt;&lt;span class=&quot;chip&quot;&gt;1F525&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;desc&quot;&gt;heart + emoji-style marker + ZWJ + fire = heart on fire. &lt;code&gt;LEN&lt;/code&gt; 5 (v1) or 4 (v2). &lt;button class=&quot;try&quot; type=&quot;button&quot; data-cps=&quot;2764 FE0F 200D 1F525&quot;&gt;Try&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;ex&quot;&gt;&lt;div class=&quot;glyph&quot;&gt;&amp;#x1F438;&amp;#x200D;&amp;#x2602;&amp;#xFE0F;&lt;/div&gt;&lt;div class=&quot;seq&quot;&gt;&lt;span class=&quot;chip&quot;&gt;1F438&lt;/span&gt;&lt;span class=&quot;chip inv&quot;&gt;200D&lt;/span&gt;&lt;span class=&quot;chip&quot;&gt;2602&lt;/span&gt;&lt;span class=&quot;chip inv&quot;&gt;FE0F&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;desc&quot;&gt;frog + ZWJ + umbrella. No such emoji exists, so you see two pictures. &lt;button class=&quot;try&quot; type=&quot;button&quot; data-cps=&quot;1F438 200D 2602 FE0F&quot;&gt;Try&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;callout&quot;&gt;&lt;span class=&quot;label&quot;&gt;The rule&lt;/span&gt;&lt;p class=&quot;rule&quot;&gt;Joined emoji only merge into one picture when Unicode has approved that exact combination and your font includes a drawing of it. You can&amp;rsquo;t make a new emoji by putting a ZWJ between any two.&lt;/p&gt;&lt;/div&gt;
      &lt;p&gt;The approved combinations are listed in a file called &lt;code&gt;emoji-zwj-sequences.txt&lt;/code&gt;&lt;sup class=&quot;ref&quot; data-ref=&quot;zwj-file&quot;&gt;&lt;/sup&gt;, and the set is called &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-rgi&quot;&gt;RGI&lt;/a&gt; (&amp;ldquo;recommended for general interchange&amp;rdquo;).&lt;sup class=&quot;ref&quot; data-ref=&quot;uts51&quot;&gt;&lt;/sup&gt; If a combination isn&amp;rsquo;t on the list, or your font doesn&amp;rsquo;t have it, the pieces are drawn side by side.&lt;/p&gt;
      &lt;p&gt;There&amp;rsquo;s a wrinkle. The grapheme rules only look at properties (&amp;ldquo;this is an emoji, this is a ZWJ&amp;rdquo;) and never check the approved list. So the frog-umbrella counts as &lt;strong&gt;one grapheme&lt;/strong&gt; in the workbench and with &lt;code&gt;&amp;#92;X&lt;/code&gt;, even though you see two pictures. Most of the time, what you see and the grapheme count agree. When they don&amp;rsquo;t, the grapheme count follows the rules and the picture follows the font.&lt;/p&gt;
      &lt;p&gt;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 &lt;code&gt;LEN&lt;/code&gt; higher than it should be.&lt;/p&gt;
    &lt;/section&gt;

    &lt;!-- ================= 9 ================= --&gt;
    &lt;section id=&quot;presentation&quot; class=&quot;prose&quot; aria-labelledby=&quot;pr-h&quot;&gt;
      &lt;h2 id=&quot;pr-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;&lt;span class=&quot;inv&quot;&gt;VS16&lt;/span&gt;&lt;/b&gt;&lt;i&gt;FE0F&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;Text vs emoji style&lt;/span&gt;&lt;/h2&gt;
      &lt;p class=&quot;lede&quot;&gt;Many symbols can be drawn two ways: a plain black-and-white symbol (&amp;ldquo;text style&amp;rdquo;) or a colourful emoji (&amp;ldquo;emoji style&amp;rdquo;). It&amp;rsquo;s the same code point either way.&lt;/p&gt;
      &lt;p&gt;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 &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-vs&quot;&gt;variation selector&lt;/a&gt; right after the symbol:&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;code class=&quot;cp&quot;&gt;U+FE0F&lt;/code&gt; (&amp;ldquo;VS16&amp;rdquo;) means &lt;strong&gt;draw it as an emoji.&lt;/strong&gt;&lt;/li&gt;
        &lt;li&gt;&lt;code class=&quot;cp&quot;&gt;U+FE0E&lt;/code&gt; (&amp;ldquo;VS15&amp;rdquo;) means &lt;strong&gt;draw it as text.&lt;/strong&gt;&lt;/li&gt;
      &lt;/ul&gt;
      &lt;div class=&quot;tablewrap&quot;&gt;
        &lt;table&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Rendered&lt;/th&gt;&lt;th&gt;Code points&lt;/th&gt;&lt;th&gt;Style&lt;/th&gt;&lt;th&gt;Why&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x2602;&lt;/td&gt;&lt;td&gt;&lt;span class=&quot;seq&quot;&gt;&lt;span class=&quot;chip&quot;&gt;2602&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;text&lt;/td&gt;&lt;td&gt;Umbrella defaults to text style&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x2602;&amp;#xFE0F;&lt;/td&gt;&lt;td&gt;&lt;span class=&quot;seq&quot;&gt;&lt;span class=&quot;chip&quot;&gt;2602&lt;/span&gt;&lt;span class=&quot;chip inv&quot;&gt;FE0F&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;emoji&lt;/td&gt;&lt;td&gt;VS16 asks for emoji&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x2614;&lt;/td&gt;&lt;td&gt;&lt;span class=&quot;seq&quot;&gt;&lt;span class=&quot;chip&quot;&gt;2614&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;emoji&lt;/td&gt;&lt;td&gt;Umbrella &lt;em&gt;with rain drops&lt;/em&gt; defaults to emoji style&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x26F5;&lt;/td&gt;&lt;td&gt;&lt;span class=&quot;seq&quot;&gt;&lt;span class=&quot;chip&quot;&gt;26F5&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;emoji&lt;/td&gt;&lt;td&gt;Sailboat defaults to emoji style&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x26F5;&amp;#xFE0E;&lt;/td&gt;&lt;td&gt;&lt;span class=&quot;seq&quot;&gt;&lt;span class=&quot;chip&quot;&gt;26F5&lt;/span&gt;&lt;span class=&quot;chip inv&quot;&gt;FE0E&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;text&lt;/td&gt;&lt;td&gt;VS15 asks for text (not every font supports this)&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;p class=&quot;small muted&quot;&gt;The default is recorded in a Unicode property called &lt;em&gt;Emoji_Presentation&lt;/em&gt;.&lt;sup class=&quot;ref&quot; data-ref=&quot;uts51&quot;&gt;&lt;/sup&gt; Your screen may not match the table: many fonts, including Windows&amp;rsquo; 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.&lt;/p&gt;
      &lt;div class=&quot;callout warn&quot;&gt;&lt;span class=&quot;label&quot;&gt;Why this matters&lt;/span&gt;&lt;p&gt;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. &lt;span class=&quot;g&quot;&gt;&amp;#x2602;&lt;/span&gt; and &lt;span class=&quot;g&quot;&gt;&amp;#x2602;&amp;#xFE0F;&lt;/span&gt; have different lengths in both compatibility versions and don&amp;rsquo;t pass &lt;code&gt;EXACT&lt;/code&gt;. That&amp;rsquo;s the classic &amp;ldquo;identical text won&amp;rsquo;t match&amp;rdquo; problem, and the next section is about how to deal with it.&lt;/p&gt;&lt;/div&gt;
    &lt;/section&gt;

    &lt;!-- ================= 10 ================= --&gt;
    &lt;section id=&quot;excel&quot; aria-labelledby=&quot;xl-h&quot;&gt;
      &lt;h2 id=&quot;xl-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;=&lt;/b&gt;&lt;i&gt;003D&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;Excel&amp;rsquo;s two kinds of equal&lt;/span&gt;&lt;/h2&gt;
      &lt;div class=&quot;payoff&quot;&gt;
        &lt;p class=&quot;lede&quot; style=&quot;max-width:46rem&quot;&gt;Excel has two ways to check whether text matches, and they don&amp;rsquo;t agree. Most &amp;ldquo;identical text won&amp;rsquo;t match&amp;rdquo; problems come from using both in the same workbook.&lt;/p&gt;
        &lt;div class=&quot;versus&quot;&gt;
          &lt;div class=&quot;loose&quot;&gt;
            &lt;span class=&quot;tag&quot;&gt;Loose&lt;/span&gt;
            &lt;h4&gt;A1 = B1&lt;/h4&gt;
            &lt;ul&gt;
              &lt;li&gt;Ignores the invisible emoji marker &lt;code class=&quot;cp&quot;&gt;U+FE0F&lt;/code&gt;: &lt;span class=&quot;g&quot;&gt;&amp;#x2602;&lt;/span&gt; = &lt;span class=&quot;g&quot;&gt;&amp;#x2602;&amp;#xFE0F;&lt;/span&gt;&lt;/li&gt;
              &lt;li&gt;Treats both spellings of an accented letter as equal: &lt;span class=&quot;g&quot;&gt;&amp;#xE9;&lt;/span&gt; (one code point) = &lt;span class=&quot;g&quot;&gt;e&amp;#x301;&lt;/span&gt; (e + accent)&lt;/li&gt;
              &lt;li&gt;Ignores upper and lower case: &lt;code&gt;a&lt;/code&gt; = &lt;code&gt;A&lt;/code&gt;&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/div&gt;
          &lt;div class=&quot;strict&quot;&gt;
            &lt;span class=&quot;tag&quot;&gt;Strict&lt;/span&gt;
            &lt;h4&gt;EXACT(A1, B1)&lt;/h4&gt;
            &lt;ul&gt;
              &lt;li&gt;Compares the text code point by code point&lt;/li&gt;
              &lt;li&gt;Case matters&lt;/li&gt;
              &lt;li&gt;Treats all of the pairs on the left as different&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;p&gt;These functions are strict like &lt;code&gt;EXACT&lt;/code&gt;: &lt;code&gt;SUBSTITUTE&lt;/code&gt;, &lt;code&gt;FIND&lt;/code&gt; and &lt;code&gt;EXACT&lt;/code&gt; itself. They look for the exact same code points. Compatibility versions change how &lt;code&gt;FIND&lt;/code&gt; counts positions, not what it considers a match.&lt;/p&gt;
        &lt;p&gt;Paste these into a sheet to see the difference:&lt;/p&gt;
        &lt;div class=&quot;tablewrap&quot;&gt;
          &lt;table&gt;
            &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Pair&lt;/th&gt;&lt;th&gt;Formula&lt;/th&gt;&lt;th&gt;=&lt;/th&gt;&lt;th&gt;EXACT&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
            &lt;tbody&gt;
              &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#x2602; vs &amp;#x2602;&amp;#xFE0F;&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;=UNICHAR(9730) = UNICHAR(9730)&amp;amp;UNICHAR(65039)&lt;/td&gt;&lt;td class=&quot;t&quot;&gt;TRUE&lt;/td&gt;&lt;td class=&quot;fa&quot;&gt;FALSE&lt;/td&gt;&lt;/tr&gt;
              &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;&amp;#xE9; vs e&amp;#x301;&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;=UNICHAR(233) = &quot;e&quot;&amp;amp;UNICHAR(769)&lt;/td&gt;&lt;td class=&quot;t&quot;&gt;TRUE&lt;/td&gt;&lt;td class=&quot;fa&quot;&gt;FALSE&lt;/td&gt;&lt;/tr&gt;
              &lt;tr&gt;&lt;td class=&quot;g&quot;&gt;a vs A&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;=&quot;a&quot; = &quot;A&quot;&lt;/td&gt;&lt;td class=&quot;t&quot;&gt;TRUE&lt;/td&gt;&lt;td class=&quot;fa&quot;&gt;FALSE&lt;/td&gt;&lt;/tr&gt;
            &lt;/tbody&gt;
          &lt;/table&gt;
        &lt;/div&gt;
        &lt;p class=&quot;small muted&quot; style=&quot;margin-top:-.6rem&quot;&gt;For the &lt;code&gt;EXACT&lt;/code&gt; column, wrap the same two values: &lt;code class=&quot;f&quot;&gt;=EXACT(UNICHAR(9730), UNICHAR(9730)&amp;amp;UNICHAR(65039))&lt;/code&gt;. The &lt;code&gt;=&lt;/code&gt; results were observed in Excel for Microsoft 365, so try them on your own version to confirm.&lt;sup class=&quot;ref&quot; data-ref=&quot;n-eq&quot;&gt;&lt;/sup&gt;&lt;/p&gt;

        &lt;h3&gt;Why mixing them causes trouble&lt;/h3&gt;
        &lt;p&gt;Say A1 holds the plain umbrella and B1 holds the emoji-style one. A formula that &lt;em&gt;checks&lt;/em&gt; with &lt;code&gt;=&lt;/code&gt; and then &lt;em&gt;acts&lt;/em&gt; with &lt;code&gt;SUBSTITUTE&lt;/code&gt; gets two different answers:&lt;/p&gt;
        &lt;div class=&quot;lanes&quot;&gt;
          &lt;div class=&quot;lane&quot;&gt;
            &lt;h4&gt;Step 1 &amp;middot; Check&lt;/h4&gt;
            &lt;div class=&quot;step&quot;&gt;=IF(A1 = B1, …)&lt;/div&gt;
            &lt;div class=&quot;arrow&quot;&gt;&amp;darr; loose: ignores the marker&lt;/div&gt;
            &lt;div class=&quot;verdict ok&quot;&gt;TRUE &amp;mdash; &amp;ldquo;they match&amp;rdquo;&lt;/div&gt;
          &lt;/div&gt;
          &lt;div class=&quot;lane&quot;&gt;
            &lt;h4&gt;Step 2 &amp;middot; Act&lt;/h4&gt;
            &lt;div class=&quot;step&quot;&gt;=SUBSTITUTE(A1, B1, &quot;&quot;)&lt;/div&gt;
            &lt;div class=&quot;arrow&quot;&gt;&amp;darr; strict: needs an exact copy&lt;/div&gt;
            &lt;div class=&quot;verdict bad&quot;&gt;no exact copy &amp;mdash; nothing removed&lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;pre&gt;&lt;code&gt;A1: =REPT(UNICHAR(9730), 2)            &amp;#x2602;&amp;#x2602;  (plain)
B1: =UNICHAR(9730) &amp;amp; UNICHAR(65039)   &amp;#x2602;&amp;#xFE0F;  (with marker)
=A1 = REPT(B1, 2)                      → TRUE
=SUBSTITUTE(A1, B1, &quot;&quot;)                → &amp;#x2602;&amp;#x2602;   unchanged&lt;/code&gt;&lt;/pre&gt;
        &lt;div class=&quot;callout warn&quot;&gt;
          &lt;span class=&quot;label&quot;&gt;The other direction is worse&lt;/span&gt;
          &lt;p&gt;Swap them, so the text holds &lt;span class=&quot;g&quot;&gt;&amp;#x2602;&amp;#xFE0F;&lt;/span&gt; and you remove the plain &lt;span class=&quot;g&quot;&gt;&amp;#x2602;&lt;/span&gt;. &lt;code&gt;SUBSTITUTE&lt;/code&gt; &lt;em&gt;does&lt;/em&gt; find it inside the longer text and deletes it, leaving the invisible marker &lt;code class=&quot;cp&quot;&gt;U+FE0F&lt;/code&gt; behind. You can&amp;rsquo;t see it, but &lt;code&gt;LEN&lt;/code&gt; counts it, and every later comparison with that cell fails.&lt;/p&gt;
        &lt;/div&gt;
        &lt;p&gt;&lt;button class=&quot;try&quot; type=&quot;button&quot; data-a=&quot;2602&quot; data-b=&quot;2602 FE0F&quot;&gt;Compare the two umbrellas&lt;/button&gt;&lt;/p&gt;
        &lt;div class=&quot;callout ok&quot;&gt;&lt;span class=&quot;label&quot;&gt;The fix&lt;/span&gt;
          &lt;p&gt;Use one definition of &amp;ldquo;equal&amp;rdquo; throughout. Either clean the data once so every copy is stored the same way, or clean both sides right before comparing (&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#normalization&quot;&gt;section 11&lt;/a&gt; shows how). For emoji-style markers, removing every VS16 is often enough:&lt;/p&gt;
          &lt;pre style=&quot;margin-bottom:0&quot;&gt;&lt;code&gt;=SUBSTITUTE(A1, UNICHAR(65039), &quot;&quot;)&lt;/code&gt;&lt;/pre&gt;
        &lt;/div&gt;
        &lt;h3&gt;More Excel gotchas&lt;/h3&gt;
        &lt;ul&gt;
          &lt;li&gt;Excel has &lt;strong&gt;no function to clean up Unicode&lt;/strong&gt;. &lt;code&gt;=&lt;/code&gt; does some of this cleanup internally for its comparison, but you can&amp;rsquo;t get the cleaned text back out.&lt;/li&gt;
          &lt;li&gt;&lt;code class=&quot;f&quot;&gt;DEC2HEX(cp, 4)&lt;/code&gt; returns &lt;code&gt;#NUM!&lt;/code&gt; for emoji. The optional 4 means &amp;ldquo;pad to 4 digits,&amp;rdquo; and a code point like &lt;code&gt;1F438&lt;/code&gt; needs 5, the same 4-digit limit from section 2. Leave the 4 out: &lt;code class=&quot;f&quot;&gt;DEC2HEX(cp)&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;&lt;code&gt;CODE&lt;/code&gt; and &lt;code&gt;CHAR&lt;/code&gt; predate Unicode and only know an old Windows character set. For anything outside that set, &lt;code class=&quot;f&quot;&gt;CODE&lt;/code&gt; returns 63, the code for &lt;code&gt;?&lt;/code&gt;. Use &lt;code&gt;UNICODE&lt;/code&gt; and &lt;code&gt;UNICHAR&lt;/code&gt; instead.&lt;/li&gt;
          &lt;li&gt;&lt;code&gt;SEARCH&lt;/code&gt; ignores case and &lt;code&gt;FIND&lt;/code&gt; doesn&amp;rsquo;t. &lt;code&gt;XLOOKUP&lt;/code&gt; and &lt;code&gt;MATCH&lt;/code&gt; ignore case too. For a lookup that respects case, use &lt;code class=&quot;f&quot;&gt;=XLOOKUP(TRUE, EXACT(keys, x), values)&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;The usual way to count occurrences, &lt;code class=&quot;f&quot;&gt;(LEN(s) − LEN(SUBSTITUTE(s, x, &quot;&quot;))) / LEN(x)&lt;/code&gt;, gives wrong answers when some copies of &lt;code&gt;x&lt;/code&gt; have the &lt;code&gt;FE0F&lt;/code&gt; marker and others don&amp;rsquo;t. Count them with &lt;code class=&quot;f&quot;&gt;COUNTA(REGEXEXTRACT(s, …, 1))&lt;/code&gt; after cleaning instead.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/div&gt;
    &lt;/section&gt;

    &lt;!-- ================= 11 ================= --&gt;
    &lt;section id=&quot;normalization&quot; class=&quot;prose&quot; aria-labelledby=&quot;nm-h&quot;&gt;
      &lt;h2 id=&quot;nm-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;&amp;#x212B;&lt;/b&gt;&lt;i&gt;212B&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;Normalization: making equal text look equal&lt;/span&gt;&lt;/h2&gt;
      &lt;p class=&quot;lede&quot;&gt;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 &lt;code&gt;EXACT&lt;/code&gt; agree.&lt;/p&gt;
      &lt;p&gt;There are two kinds of cleanup, and they need very different tools. Some you can work out from the code point&amp;rsquo;s number alone. The rest needs a lookup table.&lt;/p&gt;

      &lt;h3&gt;Kind 1: the number tells you (a formula can do it)&lt;/h3&gt;
      &lt;p&gt;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:&lt;/p&gt;
      &lt;div class=&quot;tablewrap&quot;&gt;
        &lt;table&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Delete&lt;/th&gt;&lt;th&gt;Range&lt;/th&gt;&lt;th&gt;Decimal (for UNICHAR)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td&gt;Variation selectors (the emoji/text markers)&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;U+FE00 &amp;ndash; U+FE0F&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;65024 &amp;ndash; 65039&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Skin-tone modifiers&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;U+1F3FB &amp;ndash; U+1F3FF&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;127995 &amp;ndash; 127999&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Zero-width joiner&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;U+200D&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;8205&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;p&gt;With the regex functions it&amp;rsquo;s one line, because a character class can hold several ranges:&lt;/p&gt;
      &lt;pre&gt;&lt;code&gt;=REGEXREPLACE(A1, &quot;[&amp;#92;x{FE00}-&amp;#92;x{FE0F}&amp;#92;x{1F3FB}-&amp;#92;x{1F3FF}&amp;#92;x{200D}]&quot;, &quot;&quot;)&lt;/code&gt;&lt;/pre&gt;
      &lt;p&gt;Without them (any Excel 365), loop over the list of numbers and remove each character in turn:&lt;/p&gt;
      &lt;pre&gt;&lt;code&gt;=LET(
  strip, VSTACK(SEQUENCE(16,,65024), 8205, SEQUENCE(5,,127995)),
  REDUCE(A1, strip, LAMBDA(s, c, SUBSTITUTE(s, UNICHAR(c), &quot;&quot;)))
)&lt;/code&gt;&lt;/pre&gt;
      &lt;p class=&quot;small muted&quot;&gt;Save either one as a named &lt;code&gt;LAMBDA&lt;/code&gt; (for example &lt;code&gt;FOLDKEY&lt;/code&gt;) and compare &lt;code class=&quot;f&quot;&gt;FOLDKEY(A1) = FOLDKEY(B1)&lt;/code&gt;. Use it only for comparing. Removing the ZWJ turns &amp;#x1F469;&amp;#x200D;&amp;#x1F4BB; into &amp;#x1F469;&amp;#x1F4BB;, which is not something you want to store.&lt;/p&gt;

      &lt;h3&gt;Kind 2: needs a lookup table (a formula can&amp;rsquo;t)&lt;/h3&gt;
      &lt;p&gt;Remember the two ways to write &lt;span class=&quot;g&quot;&gt;&amp;#xE9;&lt;/span&gt;? Unicode says they&amp;rsquo;re &lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-canonical&quot;&gt;canonically equivalent&lt;/a&gt;, which means officially the same text:&lt;sup class=&quot;ref&quot; data-ref=&quot;uax15&quot;&gt;&lt;/sup&gt;&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;Composed:&lt;/strong&gt; one code point, &lt;code class=&quot;cp&quot;&gt;U+00E9&lt;/code&gt; (233).&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Decomposed:&lt;/strong&gt; two code points, &lt;code class=&quot;cp&quot;&gt;U+0065&lt;/code&gt; e (101) + &lt;code class=&quot;cp&quot;&gt;U+0301&lt;/code&gt; combining accent (769).&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;You can&amp;rsquo;t get from 233 to &amp;ldquo;101 + 769&amp;rdquo; with arithmetic. The pairing was simply decided by the Unicode Consortium and recorded in a table (&lt;code&gt;UnicodeData.txt&lt;/code&gt;).&lt;sup class=&quot;ref&quot; data-ref=&quot;ucd&quot;&gt;&lt;/sup&gt; Some pairings are even odder: &lt;span class=&quot;g&quot;&gt;&amp;#x212B;&lt;/span&gt; &lt;code class=&quot;cp&quot;&gt;U+212B&lt;/code&gt; ANGSTROM SIGN is officially the same as &lt;span class=&quot;g&quot;&gt;&amp;#xC5;&lt;/span&gt; &lt;code class=&quot;cp&quot;&gt;U+00C5&lt;/code&gt;. The standard forms have names:&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a class=&quot;term&quot; href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#g-nfc&quot;&gt;&lt;strong&gt;NFC&lt;/strong&gt;&lt;/a&gt; (&amp;ldquo;composed&amp;rdquo;) turns everything into the one-code-point form where one exists. It&amp;rsquo;s the most common choice.&lt;sup class=&quot;ref&quot; data-ref=&quot;uax15&quot;&gt;&lt;/sup&gt;&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;NFD&lt;/strong&gt; (&amp;ldquo;decomposed&amp;rdquo;) splits everything into base + accents.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;div class=&quot;callout&quot;&gt;&lt;span class=&quot;label&quot;&gt;The rule&lt;/span&gt;&lt;p class=&quot;rule&quot;&gt;A formula alone can&amp;rsquo;t convert text to NFC or NFD, because that needs Unicode&amp;rsquo;s table. Excel&amp;rsquo;s &lt;code&gt;=&lt;/code&gt; uses the table internally, but only to compare, and never gives you the converted text.&lt;/p&gt;&lt;/div&gt;
      &lt;p class=&quot;small muted&quot;&gt;One fun exception: the 11,172 Korean Hangul syllables &lt;em&gt;are&lt;/em&gt; arithmetic. Each syllable&amp;rsquo;s number encodes its parts, and the workbench uses that to name them. Try 한 (U+D55C, &amp;ldquo;HAN&amp;rdquo;).&lt;/p&gt;
      &lt;h3&gt;Where to do it instead&lt;/h3&gt;
      &lt;p&gt;Normalize the text before it reaches the grid, or with a tool that has the table built in:&lt;sup class=&quot;ref&quot; data-ref=&quot;normalizestring&quot;&gt;&lt;/sup&gt;&lt;/p&gt;
      &lt;pre&gt;&lt;code&gt;// JavaScript
&quot;e&amp;#92;u0301&quot;.normalize(&quot;NFC&quot;) === &quot;&amp;#92;u00E9&quot;     // true

# Python
unicodedata.normalize(&quot;NFC&quot;, s)

&#39; VBA: call the Windows API
Declare PtrSafe Function NormalizeString Lib &quot;kernel32&quot; ( _
    ByVal NormForm As Long, ByVal lpSrc As LongPtr, ByVal cwSrc As Long, _
    ByVal lpDst As LongPtr, ByVal cwDst As Long) As Long   &#39; NormForm 1 = NFC&lt;/code&gt;&lt;/pre&gt;
      &lt;p&gt;&lt;button class=&quot;try&quot; type=&quot;button&quot; data-a=&quot;E9&quot; data-b=&quot;65 301&quot;&gt;Compare é with e + accent&lt;/button&gt; &lt;button class=&quot;try&quot; type=&quot;button&quot; data-a=&quot;212B&quot; data-b=&quot;C5&quot;&gt;Compare the two Å&lt;/button&gt;&lt;/p&gt;
    &lt;/section&gt;

    &lt;!-- ================= 12 ================= --&gt;
    &lt;section id=&quot;counting&quot; class=&quot;prose&quot; aria-labelledby=&quot;ct-h&quot;&gt;
      &lt;h2 id=&quot;ct-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;&amp;#xFFFD;&lt;/b&gt;&lt;i&gt;FFFD&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;How many characters are there, really?&lt;/span&gt;&lt;/h2&gt;
      &lt;p class=&quot;lede&quot;&gt;Unicode has room for 1,114,112 code points (17 planes &amp;times; 65,536). Most of that room is empty.&lt;/p&gt;
      &lt;div class=&quot;tablewrap&quot;&gt;
        &lt;table&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Slice&lt;/th&gt;&lt;th class=&quot;num&quot;&gt;Count&lt;/th&gt;&lt;th&gt;What it is&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td&gt;Total code space&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;1,114,112&lt;/td&gt;&lt;td&gt;Every number from U+0000 to U+10FFFF&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Characters in Unicode 16.0 (2024)&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;154,998&lt;/td&gt;&lt;td&gt;Actual characters with names: letters, symbols, emoji&amp;hellip;&lt;sup class=&quot;ref&quot; data-ref=&quot;u16&quot;&gt;&lt;/sup&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Characters in Unicode 17.0 (2025)&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;159,801&lt;/td&gt;&lt;td&gt;4,803 added&lt;sup class=&quot;ref&quot; data-ref=&quot;u17&quot;&gt;&lt;/sup&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Surrogates&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;2,048&lt;/td&gt;&lt;td&gt;The &amp;ldquo;half of a pair&amp;rdquo; values from section 5. They exist only to make UTF-16 work and are never characters.&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Private use&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;137,468&lt;/td&gt;&lt;td&gt;Left empty on purpose so companies and apps can put their own symbols there (icon fonts, for example)&lt;sup class=&quot;ref&quot; data-ref=&quot;faq-pua&quot;&gt;&lt;/sup&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Noncharacters&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;66&lt;/td&gt;&lt;td&gt;Permanently reserved for programs&amp;rsquo; internal use&lt;sup class=&quot;ref&quot; data-ref=&quot;faq-pua&quot;&gt;&lt;/sup&gt;&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;p&gt;So about &lt;strong&gt;86%&lt;/strong&gt; of the code space holds no standard character (1 &amp;minus; 154,998 / 1,114,112). If you don&amp;rsquo;t count the reserved areas (surrogates, private use and noncharacters), about 73% is still completely unassigned. There&amp;rsquo;s plenty of room for new emoji.&lt;/p&gt;
      &lt;div class=&quot;callout warn&quot;&gt;&lt;span class=&quot;label&quot;&gt;Watch the definitions&lt;/span&gt;
        &lt;p&gt;You&amp;rsquo;ll sometimes see &lt;strong&gt;~297,000&lt;/strong&gt; 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&amp;rsquo;t compare it with the ~155,000 figure for 16.0. Counted the same way, 17.0 added about 4,800 characters.&lt;/p&gt;
      &lt;/div&gt;
      &lt;p&gt;Reserved code points don&amp;rsquo;t have names. Tools show placeholders like &lt;code&gt;&amp;lt;private-use-E000&amp;gt;&lt;/code&gt; instead.&lt;/p&gt;
      &lt;p&gt;The official character list, &lt;code&gt;UnicodeData.txt&lt;/code&gt;, 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 (&lt;code&gt;20000&lt;/code&gt;&amp;ndash;&lt;code&gt;2A6DF&lt;/code&gt;) take two lines:&lt;/p&gt;
      &lt;pre&gt;&lt;code&gt;20000;&amp;lt;CJK Ideograph Extension B, First&amp;gt;;Lo;0;L;;;;;N;;;;;
2A6DF;&amp;lt;CJK Ideograph Extension B, Last&amp;gt;;Lo;0;L;;;;;N;;;;;&lt;/code&gt;&lt;/pre&gt;
    &lt;/section&gt;

    &lt;!-- ================= 13 ================= --&gt;
    &lt;section id=&quot;cheatsheet&quot; aria-labelledby=&quot;cs-h&quot;&gt;
      &lt;h2 id=&quot;cs-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;&amp;#x2713;&lt;/b&gt;&lt;i&gt;2713&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;Cheat sheet&lt;/span&gt;&lt;/h2&gt;
      &lt;p class=&quot;prose&quot;&gt;For when you already know the concepts and just need the fix. The section numbers point back to the explanations.&lt;/p&gt;
      &lt;div class=&quot;tablewrap cheat&quot;&gt;
        &lt;table&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Symptom&lt;/th&gt;&lt;th&gt;Likely cause&lt;/th&gt;&lt;th&gt;Fix&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td&gt;Identical-looking text won&amp;rsquo;t match&lt;/td&gt;&lt;td&gt;Invisible &lt;code&gt;U+FE0F&lt;/code&gt; or &lt;code&gt;U+FE0E&lt;/code&gt; on one side (&amp;sect;9)&lt;/td&gt;&lt;td&gt;Remove variation selectors from both sides (&amp;sect;11), or store one consistent form&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;code&gt;=&lt;/code&gt; says TRUE but &lt;code&gt;SUBSTITUTE&lt;/code&gt;/&lt;code&gt;FIND&lt;/code&gt; find nothing&lt;/td&gt;&lt;td&gt;&lt;code&gt;=&lt;/code&gt; is loose and those functions are strict (&amp;sect;10)&lt;/td&gt;&lt;td&gt;Clean both sides before substituting, and test with &lt;code&gt;EXACT&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;code&gt;EXACT&lt;/code&gt; is FALSE for &amp;ldquo;the same&amp;rdquo; accented word&lt;/td&gt;&lt;td&gt;One side stores the accent separately, as &lt;code&gt;e&lt;/code&gt; + &lt;code&gt;U+0301&lt;/code&gt; (&amp;sect;11)&lt;/td&gt;&lt;td&gt;Normalize to NFC before the data reaches Excel&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Same text, different &lt;code&gt;LEN&lt;/code&gt; in two workbooks&lt;/td&gt;&lt;td&gt;The workbooks use different compatibility versions (&amp;sect;4)&lt;/td&gt;&lt;td&gt;Formulas &amp;rsaquo; Calculation Options &amp;rsaquo; Compatibility Version&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;code&gt;LEN&lt;/code&gt; says 2 for one emoji&lt;/td&gt;&lt;td&gt;Version 1 workbook: surrogate pair counted as 2 slots (&amp;sect;5)&lt;/td&gt;&lt;td&gt;Switch to Version 2, or count graphemes with &lt;code&gt;&#92;X&lt;/code&gt; (&amp;sect;7)&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;code&gt;LEN&lt;/code&gt; says 2+ for one emoji even in Version 2&lt;/td&gt;&lt;td&gt;Skin tone, ZWJ joins or VS16 markers are separate code points (&amp;sect;6, &amp;sect;8)&lt;/td&gt;&lt;td&gt;Count with &lt;code&gt;COUNTA(REGEXEXTRACT(A1,&quot;&#92;X&quot;,1))&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;code&gt;LEN&lt;/code&gt; is one more than expected&lt;/td&gt;&lt;td&gt;A leftover &lt;code&gt;U+FE0F&lt;/code&gt; from an earlier substitution (&amp;sect;10)&lt;/td&gt;&lt;td&gt;Remove &lt;code&gt;UNICHAR(65039)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Counting occurrences with SUBSTITUTE gives the wrong number&lt;/td&gt;&lt;td&gt;Only some copies have FE0F, or the search text has several parts&lt;/td&gt;&lt;td&gt;Clean both strings first, then count&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;code&gt;MID&lt;/code&gt; shows &amp;#xFFFD; or a box&lt;/td&gt;&lt;td&gt;Version 1: cut a surrogate pair in half (&amp;sect;5)&lt;/td&gt;&lt;td&gt;Use Version 2, or take whole graphemes with &lt;code&gt;REGEXEXTRACT&lt;/code&gt; (&amp;sect;7)&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;code&gt;UNICODE()&lt;/code&gt; returns &lt;code&gt;#VALUE!&lt;/code&gt;&lt;/td&gt;&lt;td&gt;It was given half a surrogate pair (&amp;sect;5)&lt;/td&gt;&lt;td&gt;Fix the cut that produced it&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;code&gt;FIND&lt;/code&gt; position is off after sharing the file&lt;/td&gt;&lt;td&gt;Positions count slots in Version 1 and code points in Version 2 (&amp;sect;4)&lt;/td&gt;&lt;td&gt;Don&amp;rsquo;t store positions; recompute them in the workbook that uses them&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;code&gt;DEC2HEX(cp,4)&lt;/code&gt; returns &lt;code&gt;#NUM!&lt;/code&gt;&lt;/td&gt;&lt;td&gt;The code point needs 5 hex digits (&amp;sect;10)&lt;/td&gt;&lt;td&gt;Leave out the &lt;code&gt;4&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;code&gt;CODE()&lt;/code&gt; returns 63&lt;/td&gt;&lt;td&gt;The character isn&amp;rsquo;t in the old Windows character set&lt;/td&gt;&lt;td&gt;Use &lt;code&gt;UNICODE()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Emoji shows black-and-white&lt;/td&gt;&lt;td&gt;Text-style default and no VS16 (&amp;sect;9)&lt;/td&gt;&lt;td&gt;Append &lt;code&gt;UNICHAR(65039)&lt;/code&gt;, and do it the same way everywhere&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Two emoji appear where one should&lt;/td&gt;&lt;td&gt;Unapproved ZWJ combination, or the font doesn&amp;rsquo;t have it (&amp;sect;8)&lt;/td&gt;&lt;td&gt;Check &lt;code&gt;emoji-zwj-sequences.txt&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Flag shows as two letters&lt;/td&gt;&lt;td&gt;Windows has no flag emoji&lt;/td&gt;&lt;td&gt;Not a data problem; the text is correct&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Lookup ignores case when you need it to match exactly&lt;/td&gt;&lt;td&gt;&lt;code&gt;XLOOKUP&lt;/code&gt;/&lt;code&gt;MATCH&lt;/code&gt; ignore case&lt;/td&gt;&lt;td&gt;&lt;code&gt;XLOOKUP(TRUE, EXACT(keys, x), vals)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;p class=&quot;small muted&quot;&gt;Handy numbers for &lt;code&gt;UNICHAR&lt;/code&gt;: 65039 VS16 (emoji style) &amp;middot; 65038 VS15 (text style) &amp;middot; 8205 ZWJ &amp;middot; 769 combining acute accent &amp;middot; 127995&amp;ndash;127999 skin tones. Version check: &lt;code&gt;=IF(LEN(UNICHAR(128056))=1, &quot;Version 2&quot;, &quot;Version 1&quot;)&lt;/code&gt;.&lt;/p&gt;
    &lt;/section&gt;

    &lt;!-- ================= 14 ================= --&gt;
    &lt;section id=&quot;glossary&quot; aria-labelledby=&quot;gl-h&quot;&gt;
      &lt;h2 id=&quot;gl-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;&amp;#xB6;&lt;/b&gt;&lt;i&gt;00B6&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;Glossary&lt;/span&gt;&lt;/h2&gt;
      &lt;dl class=&quot;gloss&quot;&gt;
        &lt;dt id=&quot;g-ascii&quot;&gt;ASCII&lt;/dt&gt;&lt;dd&gt;A 1963 character set of 128 characters: English letters, digits and punctuation. Unicode&amp;rsquo;s first 128 code points are the same.&lt;/dd&gt;
        &lt;dt id=&quot;g-bit&quot;&gt;Bit&lt;/dt&gt;&lt;dd&gt;A single 0 or 1. 16 bits can represent 2&lt;sup&gt;16&lt;/sup&gt; = 65,536 different values. One hex digit is exactly 4 bits.&lt;/dd&gt;
        &lt;dt id=&quot;g-bmp&quot;&gt;BMP (Basic Multilingual Plane)&lt;/dt&gt;&lt;dd&gt;Plane 0, U+0000&amp;ndash;U+FFFF. Everything here fits in one UTF-16 slot.&lt;/dd&gt;
        &lt;dt id=&quot;g-canonical&quot;&gt;Canonical equivalence&lt;/dt&gt;&lt;dd&gt;Unicode&amp;rsquo;s official statement that two different code point sequences mean the same text, such as &lt;span class=&quot;g&quot;&gt;&amp;#xE9;&lt;/span&gt; and e + U+0301.&lt;/dd&gt;
        &lt;dt id=&quot;g-codepoint&quot;&gt;Code point&lt;/dt&gt;&lt;dd&gt;A character&amp;rsquo;s number in Unicode, written U+ and hex, e.g. U+2602. Ranges from U+0000 to U+10FFFF. &lt;code&gt;LEN&lt;/code&gt; counts these in Version 2 workbooks.&lt;/dd&gt;
        &lt;dt id=&quot;g-codeunit&quot;&gt;Code unit&lt;/dt&gt;&lt;dd&gt;One storage slot in an encoding. In UTF-16 a slot is 16 bits. &lt;code&gt;LEN&lt;/code&gt; counts these in Version 1 workbooks.&lt;/dd&gt;
        &lt;dt id=&quot;g-combining&quot;&gt;Combining mark&lt;/dt&gt;&lt;dd&gt;A code point that attaches to the character before it, such as an accent. It has no meaning on its own.&lt;/dd&gt;
        &lt;dt id=&quot;g-compat&quot;&gt;Compatibility version&lt;/dt&gt;&lt;dd&gt;A per-workbook Excel setting (Formulas &amp;rsaquo; Calculation Options) that picks old or new behaviour for certain functions. In Version 2, &lt;code&gt;LEN&lt;/code&gt;, &lt;code&gt;MID&lt;/code&gt;, &lt;code&gt;FIND&lt;/code&gt;, &lt;code&gt;SEARCH&lt;/code&gt; and &lt;code&gt;REPLACE&lt;/code&gt; count code points instead of code units.&lt;/dd&gt;
        &lt;dt id=&quot;g-encoding&quot;&gt;Encoding&lt;/dt&gt;&lt;dd&gt;The method for storing code points as bytes. UTF-8, UTF-16 and UTF-32 all store the same code points in different ways.&lt;/dd&gt;
        &lt;dt id=&quot;g-grapheme&quot;&gt;Grapheme (cluster)&lt;/dt&gt;&lt;dd&gt;What a reader sees as one character. It can be made of many code points. In Excel, &lt;code&gt;REGEXEXTRACT&lt;/code&gt; with &lt;code&gt;&#92;X&lt;/code&gt; finds them.&lt;/dd&gt;
        &lt;dt id=&quot;g-hex&quot;&gt;Hexadecimal (hex)&lt;/dt&gt;&lt;dd&gt;Base-16 numbers using the digits 0&amp;ndash;9 and A&amp;ndash;F. Written with a &lt;code&gt;0x&lt;/code&gt; or &lt;code&gt;U+&lt;/code&gt; prefix. Excel converts with &lt;code&gt;DEC2HEX&lt;/code&gt; and &lt;code&gt;HEX2DEC&lt;/code&gt;.&lt;/dd&gt;
        &lt;dt id=&quot;g-nfc&quot;&gt;NFC / NFD&lt;/dt&gt;&lt;dd&gt;Standard &amp;ldquo;normal forms&amp;rdquo; of text. NFC uses the one-code-point form where possible and NFD splits into base + marks. Converting to one is called normalizing.&lt;/dd&gt;
        &lt;dt id=&quot;g-pcre2&quot;&gt;PCRE2&lt;/dt&gt;&lt;dd&gt;&amp;ldquo;Perl Compatible Regular Expressions,&amp;rdquo; version 2. The regex engine behind Excel&amp;rsquo;s &lt;code&gt;REGEX&lt;/code&gt; functions. In PCRE2, &lt;code&gt;&#92;X&lt;/code&gt; matches one grapheme.&lt;/dd&gt;
        &lt;dt id=&quot;g-plane&quot;&gt;Plane&lt;/dt&gt;&lt;dd&gt;A block of 65,536 code points. There are 17 of them (0&amp;ndash;16).&lt;/dd&gt;
        &lt;dt id=&quot;g-regex&quot;&gt;Regular expression (regex)&lt;/dt&gt;&lt;dd&gt;A pattern language for matching text. For example, &lt;code&gt;&#92;d+&lt;/code&gt; means &amp;ldquo;one or more digits.&amp;rdquo;&lt;/dd&gt;
        &lt;dt id=&quot;g-rgi&quot;&gt;RGI&lt;/dt&gt;&lt;dd&gt;&amp;ldquo;Recommended for general interchange&amp;rdquo;: Unicode&amp;rsquo;s list of emoji and emoji combinations that platforms are expected to support.&lt;/dd&gt;
        &lt;dt id=&quot;g-surrogate&quot;&gt;Surrogate pair&lt;/dt&gt;&lt;dd&gt;Two UTF-16 slots (high D800&amp;ndash;DBFF, then low DC00&amp;ndash;DFFF) that together store one code point above U+FFFF.&lt;/dd&gt;
        &lt;dt id=&quot;g-uax29&quot;&gt;UAX #29&lt;/dt&gt;&lt;dd&gt;The Unicode document that defines how to split text into graphemes, words and sentences.&lt;/dd&gt;
        &lt;dt id=&quot;g-utf16&quot;&gt;UTF-16&lt;/dt&gt;&lt;dd&gt;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.&lt;/dd&gt;
        &lt;dt id=&quot;g-vs&quot;&gt;Variation selector&lt;/dt&gt;&lt;dd&gt;An invisible code point that picks a drawing style for the character before it. U+FE0F = emoji style, U+FE0E = text style.&lt;/dd&gt;
        &lt;dt id=&quot;g-zwj&quot;&gt;ZWJ (zero-width joiner)&lt;/dt&gt;&lt;dd&gt;U+200D. Invisible glue that asks the font to combine the emoji on either side into one picture.&lt;/dd&gt;
      &lt;/dl&gt;
    &lt;/section&gt;

    &lt;!-- ================= 15 ================= --&gt;
    &lt;section id=&quot;references&quot; aria-labelledby=&quot;ref-h&quot;&gt;
      &lt;h2 id=&quot;ref-h&quot;&gt;&lt;span class=&quot;mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;b&gt;&amp;#x2020;&lt;/b&gt;&lt;i&gt;2020&lt;/i&gt;&lt;/span&gt;&lt;span class=&quot;htext&quot;&gt;References&lt;/span&gt;&lt;/h2&gt;
      &lt;h3 style=&quot;margin-top:0&quot;&gt;Notes&lt;/h3&gt;
      &lt;ol class=&quot;reflist notes&quot; id=&quot;notelist&quot;&gt;
          &lt;li data-key=&quot;n-left&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;Microsoft&amp;rsquo;s own pages list only the five functions that change. The statement that &lt;code&gt;LEFT&lt;/code&gt;, &lt;code&gt;RIGHT&lt;/code&gt;, &lt;code&gt;TEXTBEFORE&lt;/code&gt;, &lt;code&gt;TEXTAFTER&lt;/code&gt; and &lt;code&gt;TEXTSPLIT&lt;/code&gt; already handled surrogate pairs comes from FM Magazine&amp;rsquo;s coverage.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;n-regex&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;Microsoft&amp;rsquo;s &lt;code&gt;REGEXEXTRACT&lt;/code&gt; documentation doesn&amp;rsquo;t say what is returned when nothing matches, or which direction the results spill. The &lt;code&gt;IF&lt;/code&gt; and &lt;code&gt;TOCOL&lt;/code&gt; wrappers guard against both.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;n-eq&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;Microsoft doesn&amp;rsquo;t document that &lt;code&gt;=&lt;/code&gt; ignores &lt;code&gt;U+FE0F&lt;/code&gt; and treats decomposed accents as equal. These results come from testing in Excel for Microsoft 365 and could change.&lt;/span&gt;&lt;/li&gt;
      &lt;/ol&gt;
      &lt;h3&gt;Citations&lt;/h3&gt;
      &lt;ol class=&quot;reflist&quot; id=&quot;reflist&quot;&gt;
          &lt;li data-key=&quot;wiki-ascii&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/ASCII&quot;&gt;&amp;ldquo;ASCII&amp;rdquo;&lt;/a&gt;. &lt;i&gt;Wikipedia&lt;/i&gt;.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;wiki-mojibake&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Mojibake&quot;&gt;&amp;ldquo;Mojibake&amp;rdquo;&lt;/a&gt;. &lt;i&gt;Wikipedia&lt;/i&gt;.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;wiki-unicode&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Unicode&quot;&gt;&amp;ldquo;Unicode&amp;rdquo;&lt;/a&gt;. &lt;i&gt;Wikipedia&lt;/i&gt;.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;compat&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://support.microsoft.com/en-us/excel/compatibility-versions&quot;&gt;&amp;ldquo;Compatibility Versions&amp;rdquo;&lt;/a&gt;. &lt;i&gt;Microsoft Support&lt;/i&gt;. Microsoft.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;len&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://support.microsoft.com/en-US/Excel/functions/len-function&quot;&gt;&amp;ldquo;LEN function&amp;rdquo;&lt;/a&gt;. &lt;i&gt;Microsoft Support&lt;/i&gt;. Microsoft.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;insider&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://techcommunity.microsoft.com/blog/microsoft365insiderblog/improving-five-excel-text-functions-len-mid-search-find-and-replace--compatibili/4339080&quot;&gt;&amp;ldquo;Improving five Excel text functions: LEN, MID, SEARCH, FIND, and REPLACE + Compatibility Versions&amp;rdquo;&lt;/a&gt;. &lt;i&gt;Microsoft 365 Insider Blog&lt;/i&gt;. Microsoft Tech Community.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;fm&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://www.fm-magazine.com/issues/2025/nov/compatibility-versions-in-excel-what-you-need-to-know/&quot;&gt;&amp;ldquo;Compatibility Versions in Excel: What you need to know&amp;rdquo;&lt;/a&gt;. &lt;i&gt;FM Magazine&lt;/i&gt;. November 2025.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;wiki-utf16&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/UTF-16&quot;&gt;&amp;ldquo;UTF-16&amp;rdquo;&lt;/a&gt;. &lt;i&gt;Wikipedia&lt;/i&gt;.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;faq-utf&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://www.unicode.org/faq/utf_bom.html&quot;&gt;&amp;ldquo;FAQ: UTF-8, UTF-16, UTF-32 &amp;amp; BOM&amp;rdquo;&lt;/a&gt;. The Unicode Consortium.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;uax29&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://www.unicode.org/reports/tr29/&quot;&gt;&amp;ldquo;Unicode Standard Annex #29: Unicode Text Segmentation&amp;rdquo;&lt;/a&gt;. The Unicode Consortium.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;regexextract&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://support.microsoft.com/en-us/excel/functions/regexextract-function&quot;&gt;&amp;ldquo;REGEXEXTRACT function&amp;rdquo;&lt;/a&gt;. &lt;i&gt;Microsoft Support&lt;/i&gt;. Microsoft.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;pcre2&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://www.pcre.org/current/doc/html/pcre2pattern.html&quot;&gt;&amp;ldquo;pcre2pattern: Perl-compatible regular expression syntax&amp;rdquo;&lt;/a&gt;. &lt;i&gt;PCRE2 documentation&lt;/i&gt;.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;zwj-file&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://www.unicode.org/Public/emoji/latest/emoji-zwj-sequences.txt&quot;&gt;&amp;ldquo;emoji-zwj-sequences.txt&amp;rdquo;&lt;/a&gt;. The Unicode Consortium.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;uts51&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://www.unicode.org/reports/tr51/&quot;&gt;&amp;ldquo;Unicode Technical Standard #51: Unicode Emoji&amp;rdquo;&lt;/a&gt;. The Unicode Consortium.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;uax15&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://www.unicode.org/reports/tr15/&quot;&gt;&amp;ldquo;Unicode Standard Annex #15: Unicode Normalization Forms&amp;rdquo;&lt;/a&gt;. The Unicode Consortium.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;ucd&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt&quot;&gt;&amp;ldquo;UnicodeData.txt&amp;rdquo;&lt;/a&gt;. &lt;i&gt;Unicode Character Database&lt;/i&gt;. The Unicode Consortium.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;normalizestring&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-normalizestring&quot;&gt;&amp;ldquo;NormalizeString function (winnls.h)&amp;rdquo;&lt;/a&gt;. &lt;i&gt;Microsoft Learn&lt;/i&gt;. Microsoft.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;u16&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://www.unicode.org/versions/Unicode16.0.0/&quot;&gt;&amp;ldquo;Unicode 16.0.0&amp;rdquo;&lt;/a&gt;. The Unicode Consortium. September 2024. &amp;ldquo;Unicode 16.0 adds 5185 characters, for a total of 154,998 characters.&amp;rdquo;&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;u17&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://www.unicode.org/versions/Unicode17.0.0/&quot;&gt;&amp;ldquo;Unicode 17.0.0&amp;rdquo;&lt;/a&gt;. The Unicode Consortium. September 2025. &amp;ldquo;Unicode 17.0 adds 4803 characters, for a total of 159,801 characters.&amp;rdquo;&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;faq-pua&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://www.unicode.org/faq/private_use.html&quot;&gt;&amp;ldquo;FAQ: Private-Use Characters, Noncharacters &amp;amp; Sentinels&amp;rdquo;&lt;/a&gt;. The Unicode Consortium.&lt;/span&gt;&lt;/li&gt;
          &lt;li data-key=&quot;segmenter&quot;&gt;&lt;span class=&quot;back&quot;&gt;&lt;/span&gt; &lt;span class=&quot;cite&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter&quot;&gt;&amp;ldquo;Intl.Segmenter&amp;rdquo;&lt;/a&gt;. &lt;i&gt;MDN Web Docs&lt;/i&gt;. Mozilla.&lt;/span&gt;&lt;/li&gt;
      &lt;/ol&gt;
      &lt;h3&gt;Cite this page&lt;/h3&gt;
      &lt;p class=&quot;cite-this&quot;&gt;Freelove, Jeremy (2026). &lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/&quot;&gt;&amp;ldquo;Why LEN seems wrong sometimes: An introduction to Unicode, for spreadsheet people&amp;rdquo;&lt;/a&gt;. &lt;i&gt;jeremyfreelove.com&lt;/i&gt;. Updated 18 September 2026.&lt;/p&gt;
      &lt;p class=&quot;small muted&quot;&gt;Links point to the latest version of each page. Microsoft&amp;rsquo;s pages on Compatibility Versions describe a feature that is still rolling out, so check them again if something here doesn&amp;rsquo;t match your Excel.&lt;/p&gt;
    &lt;/section&gt;

    &lt;footer&gt;
      &lt;p&gt;The workbench splits graphemes with your browser&amp;rsquo;s &lt;code&gt;Intl.Segmenter&lt;/code&gt;&lt;sup class=&quot;ref&quot; data-ref=&quot;segmenter&quot;&gt;&lt;/sup&gt;, so it follows whatever Unicode version your browser supports. Character names come from a small built-in list plus naming rules; other characters show their category. Compatibility version details are from Microsoft&amp;rsquo;s documentation as of 2026 (&lt;a href=&quot;https://jeremyfreelove.com/writing/why-len-seems-wrong/#versions&quot;&gt;section 4&lt;/a&gt;).&lt;/p&gt;
    &lt;/footer&gt;
  &lt;/main&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
  &#39;use strict&#39;;
  var root = document.documentElement;
  var $ = function (id) { return document.getElementById(id); };

  /* ---------------- Unicode helpers ---------------- */
  var hex = function (n, w) { return n.toString(16).toUpperCase().padStart(w || 4, &#39;0&#39;); };
  var U = function (cp) { return &#39;U+&#39; + hex(cp); };
  var esc = function (s) { return String(s).replace(/[&amp;&lt;&gt;&quot;]/g, function (c) { return { &#39;&amp;&#39;: &#39;&amp;amp;&#39;, &#39;&lt;&#39;: &#39;&amp;lt;&#39;, &#39;&gt;&#39;: &#39;&amp;gt;&#39;, &#39;&quot;&#39;: &#39;&amp;quot;&#39; }[c]; }); };
  var cpsOf = function (s) { return Array.from(s, function (c) { return c.codePointAt(0); }); };
  var fromCps = function (arr) { return String.fromCodePoint.apply(null, arr); };

  var segmenter = (typeof Intl !== &#39;undefined&#39; &amp;&amp; Intl.Segmenter) ? new Intl.Segmenter(undefined, { granularity: &#39;grapheme&#39; }) : null;
  function graphemes(s) {
    if (segmenter) return Array.from(segmenter.segment(s), function (x) { return x.segment; });
    return Array.from(s); // fallback: code points
  }
  function re(src) { try { return new RegExp(src, &#39;u&#39;); } catch (e) { return null; } }
  var reExtPict = re(&#39;^&#92;&#92;p{Extended_Pictographic}$&#39;);
  var reEmojiPres = re(&#39;^&#92;&#92;p{Emoji_Presentation}$&#39;);
  var reGrExt = re(&#39;^&#92;&#92;p{Grapheme_Extend}$&#39;);
  var reUnified = re(&#39;^&#92;&#92;p{Unified_Ideograph}$&#39;);
  var test = function (r, ch) { return !!(r &amp;&amp; r.test(ch)); };

  var GC = [
    [&#39;Lu&#39;, &#39;Letter, uppercase&#39;], [&#39;Ll&#39;, &#39;Letter, lowercase&#39;], [&#39;Lt&#39;, &#39;Letter, titlecase&#39;], [&#39;Lm&#39;, &#39;Letter, modifier&#39;], [&#39;Lo&#39;, &#39;Letter, other&#39;],
    [&#39;Mn&#39;, &#39;Mark, nonspacing&#39;], [&#39;Mc&#39;, &#39;Mark, spacing combining&#39;], [&#39;Me&#39;, &#39;Mark, enclosing&#39;],
    [&#39;Nd&#39;, &#39;Number, decimal digit&#39;], [&#39;Nl&#39;, &#39;Number, letter&#39;], [&#39;No&#39;, &#39;Number, other&#39;],
    [&#39;Pc&#39;, &#39;Punctuation, connector&#39;], [&#39;Pd&#39;, &#39;Punctuation, dash&#39;], [&#39;Ps&#39;, &#39;Punctuation, open&#39;], [&#39;Pe&#39;, &#39;Punctuation, close&#39;],
    [&#39;Pi&#39;, &#39;Punctuation, initial quote&#39;], [&#39;Pf&#39;, &#39;Punctuation, final quote&#39;], [&#39;Po&#39;, &#39;Punctuation, other&#39;],
    [&#39;Sm&#39;, &#39;Symbol, math&#39;], [&#39;Sc&#39;, &#39;Symbol, currency&#39;], [&#39;Sk&#39;, &#39;Symbol, modifier&#39;], [&#39;So&#39;, &#39;Symbol, other&#39;],
    [&#39;Zs&#39;, &#39;Separator, space&#39;], [&#39;Zl&#39;, &#39;Separator, line&#39;], [&#39;Zp&#39;, &#39;Separator, paragraph&#39;],
    [&#39;Cc&#39;, &#39;Control&#39;], [&#39;Cf&#39;, &#39;Format&#39;], [&#39;Cs&#39;, &#39;Surrogate&#39;], [&#39;Co&#39;, &#39;Private use&#39;], [&#39;Cn&#39;, &#39;Unassigned&#39;]
  ].map(function (g) { return { code: g[0], label: g[1], re: re(&#39;^&#92;&#92;p{gc=&#39; + g[0] + &#39;}$&#39;) }; });
  function genCat(cp) {
    if (cp &gt;= 0xD800 &amp;&amp; cp &lt;= 0xDFFF) return GC[27];
    var ch = String.fromCodePoint(cp);
    for (var i = 0; i &lt; GC.length; i++) if (GC[i].re &amp;&amp; GC[i].re.test(ch)) return GC[i];
    return { code: &#39;?&#39;, label: &#39;Unknown&#39; };
  }

  var NAMES = {
    0x09: &#39;CHARACTER TABULATION&#39;, 0x0A: &#39;LINE FEED (LF)&#39;, 0x0D: &#39;CARRIAGE RETURN (CR)&#39;, 0xA0: &#39;NO-BREAK SPACE&#39;,
    0xA9: &#39;COPYRIGHT SIGN&#39;, 0xAE: &#39;REGISTERED SIGN&#39;, 0xDF: &#39;LATIN SMALL LETTER SHARP S&#39;, 0xAD: &#39;SOFT HYPHEN&#39;,
    0x200B: &#39;ZERO WIDTH SPACE&#39;, 0x200C: &#39;ZERO WIDTH NON-JOINER&#39;, 0x200D: &#39;ZERO WIDTH JOINER&#39;,
    0x200E: &#39;LEFT-TO-RIGHT MARK&#39;, 0x200F: &#39;RIGHT-TO-LEFT MARK&#39;, 0x2060: &#39;WORD JOINER&#39;, 0xFEFF: &#39;ZERO WIDTH NO-BREAK SPACE&#39;,
    0x2013: &#39;EN DASH&#39;, 0x2014: &#39;EM DASH&#39;, 0x2018: &#39;LEFT SINGLE QUOTATION MARK&#39;, 0x2019: &#39;RIGHT SINGLE QUOTATION MARK&#39;,
    0x201C: &#39;LEFT DOUBLE QUOTATION MARK&#39;, 0x201D: &#39;RIGHT DOUBLE QUOTATION MARK&#39;, 0x2026: &#39;HORIZONTAL ELLIPSIS&#39;,
    0x20AC: &#39;EURO SIGN&#39;, 0x2122: &#39;TRADE MARK SIGN&#39;, 0x212A: &#39;KELVIN SIGN&#39;, 0x212B: &#39;ANGSTROM SIGN&#39;, 0x25CC: &#39;DOTTED CIRCLE&#39;,
    0x2600: &#39;BLACK SUN WITH RAYS&#39;, 0x2601: &#39;CLOUD&#39;, 0x2602: &#39;UMBRELLA&#39;, 0x2603: &#39;SNOWMAN&#39;, 0x2614: &#39;UMBRELLA WITH RAIN DROPS&#39;,
    0x2640: &#39;FEMALE SIGN&#39;, 0x2642: &#39;MALE SIGN&#39;, 0x26A0: &#39;WARNING SIGN&#39;, 0x26F5: &#39;SAILBOAT&#39;, 0x2713: &#39;CHECK MARK&#39;,
    0x2714: &#39;HEAVY CHECK MARK&#39;, 0x274C: &#39;CROSS MARK&#39;, 0x2764: &#39;HEAVY BLACK HEART&#39;, 0x2B50: &#39;WHITE MEDIUM STAR&#39;,
    0x20E3: &#39;COMBINING ENCLOSING KEYCAP&#39;, 0xFB01: &#39;LATIN SMALL LIGATURE FI&#39;, 0xFFFD: &#39;REPLACEMENT CHARACTER&#39;,
    0x1F308: &#39;RAINBOW&#39;, 0x1F3F3: &#39;WAVING WHITE FLAG&#39;, 0x1F3F4: &#39;WAVING BLACK FLAG&#39;, 0x1F438: &#39;FROG FACE&#39;,
    0x1F44B: &#39;WAVING HAND SIGN&#39;, 0x1F44D: &#39;THUMBS UP SIGN&#39;, 0x1F466: &#39;BOY&#39;, 0x1F467: &#39;GIRL&#39;, 0x1F468: &#39;MAN&#39;, 0x1F469: &#39;WOMAN&#39;,
    0x1F4BB: &#39;PERSONAL COMPUTER&#39;, 0x1F525: &#39;FIRE&#39;, 0x1F600: &#39;GRINNING FACE&#39;, 0x1F602: &#39;FACE WITH TEARS OF JOY&#39;, 0x1F680: &#39;ROCKET&#39;
  };
  var MARKS = {
    0x0300: &#39;GRAVE ACCENT&#39;, 0x0301: &#39;ACUTE ACCENT&#39;, 0x0302: &#39;CIRCUMFLEX ACCENT&#39;, 0x0303: &#39;TILDE&#39;, 0x0304: &#39;MACRON&#39;,
    0x0306: &#39;BREVE&#39;, 0x0307: &#39;DOT ABOVE&#39;, 0x0308: &#39;DIAERESIS&#39;, 0x030A: &#39;RING ABOVE&#39;, 0x030B: &#39;DOUBLE ACUTE ACCENT&#39;,
    0x030C: &#39;CARON&#39;, 0x0323: &#39;DOT BELOW&#39;, 0x0327: &#39;CEDILLA&#39;, 0x0328: &#39;OGONEK&#39;
  };
  var ASCII = {
    &#39; &#39;: &#39;SPACE&#39;, &#39;!&#39;: &#39;EXCLAMATION MARK&#39;, &#39;&quot;&#39;: &#39;QUOTATION MARK&#39;, &#39;#&#39;: &#39;NUMBER SIGN&#39;, &#39;$&#39;: &#39;DOLLAR SIGN&#39;, &#39;%&#39;: &#39;PERCENT SIGN&#39;,
    &#39;&amp;&#39;: &#39;AMPERSAND&#39;, &quot;&#39;&quot;: &#39;APOSTROPHE&#39;, &#39;(&#39;: &#39;LEFT PARENTHESIS&#39;, &#39;)&#39;: &#39;RIGHT PARENTHESIS&#39;, &#39;*&#39;: &#39;ASTERISK&#39;, &#39;+&#39;: &#39;PLUS SIGN&#39;,
    &#39;,&#39;: &#39;COMMA&#39;, &#39;-&#39;: &#39;HYPHEN-MINUS&#39;, &#39;.&#39;: &#39;FULL STOP&#39;, &#39;/&#39;: &#39;SOLIDUS&#39;, &#39;:&#39;: &#39;COLON&#39;, &#39;;&#39;: &#39;SEMICOLON&#39;, &#39;&lt;&#39;: &#39;LESS-THAN SIGN&#39;,
    &#39;=&#39;: &#39;EQUALS SIGN&#39;, &#39;&gt;&#39;: &#39;GREATER-THAN SIGN&#39;, &#39;?&#39;: &#39;QUESTION MARK&#39;, &#39;@&#39;: &#39;COMMERCIAL AT&#39;, &#39;[&#39;: &#39;LEFT SQUARE BRACKET&#39;,
    &#39;&#92;&#92;&#39;: &#39;REVERSE SOLIDUS&#39;, &#39;]&#39;: &#39;RIGHT SQUARE BRACKET&#39;, &#39;^&#39;: &#39;CIRCUMFLEX ACCENT&#39;, &#39;_&#39;: &#39;LOW LINE&#39;, &#39;`&#39;: &#39;GRAVE ACCENT&#39;,
    &#39;{&#39;: &#39;LEFT CURLY BRACKET&#39;, &#39;|&#39;: &#39;VERTICAL LINE&#39;, &#39;}&#39;: &#39;RIGHT CURLY BRACKET&#39;, &#39;~&#39;: &#39;TILDE&#39;
  };
  var DIGITS = [&#39;ZERO&#39;, &#39;ONE&#39;, &#39;TWO&#39;, &#39;THREE&#39;, &#39;FOUR&#39;, &#39;FIVE&#39;, &#39;SIX&#39;, &#39;SEVEN&#39;, &#39;EIGHT&#39;, &#39;NINE&#39;];
  var HL = [&#39;G&#39;, &#39;GG&#39;, &#39;N&#39;, &#39;D&#39;, &#39;DD&#39;, &#39;R&#39;, &#39;M&#39;, &#39;B&#39;, &#39;BB&#39;, &#39;S&#39;, &#39;SS&#39;, &#39;&#39;, &#39;J&#39;, &#39;JJ&#39;, &#39;C&#39;, &#39;K&#39;, &#39;T&#39;, &#39;P&#39;, &#39;H&#39;];
  var HV = [&#39;A&#39;, &#39;AE&#39;, &#39;YA&#39;, &#39;YAE&#39;, &#39;EO&#39;, &#39;E&#39;, &#39;YEO&#39;, &#39;YE&#39;, &#39;O&#39;, &#39;WA&#39;, &#39;WAE&#39;, &#39;OE&#39;, &#39;YO&#39;, &#39;U&#39;, &#39;WEO&#39;, &#39;WE&#39;, &#39;WI&#39;, &#39;YU&#39;, &#39;EU&#39;, &#39;YI&#39;, &#39;I&#39;];
  var HT = [&#39;&#39;, &#39;G&#39;, &#39;GG&#39;, &#39;GS&#39;, &#39;N&#39;, &#39;NJ&#39;, &#39;NH&#39;, &#39;D&#39;, &#39;L&#39;, &#39;LG&#39;, &#39;LM&#39;, &#39;LB&#39;, &#39;LS&#39;, &#39;LT&#39;, &#39;LP&#39;, &#39;LH&#39;, &#39;M&#39;, &#39;B&#39;, &#39;BS&#39;, &#39;S&#39;, &#39;SS&#39;, &#39;NG&#39;, &#39;J&#39;, &#39;C&#39;, &#39;K&#39;, &#39;T&#39;, &#39;P&#39;, &#39;H&#39;];

  function isNonchar(cp) { return (cp &gt;= 0xFDD0 &amp;&amp; cp &lt;= 0xFDEF) || (cp &amp; 0xFFFE) === 0xFFFE; }
  function isPUA(cp) { return (cp &gt;= 0xE000 &amp;&amp; cp &lt;= 0xF8FF) || (cp &gt;= 0xF0000 &amp;&amp; cp &lt;= 0xFFFFD) || (cp &gt;= 0x100000 &amp;&amp; cp &lt;= 0x10FFFD); }

  // Returns [name, how] where how is &#39;table&#39; | &#39;rule&#39; | &#39;label&#39; | null
  function nameOf(cp) {
    if (NAMES[cp]) return [NAMES[cp], &#39;table&#39;];
    if (MARKS[cp]) return [&#39;COMBINING &#39; + MARKS[cp], &#39;table&#39;];
    var ch = String.fromCodePoint(cp);
    if (cp &gt;= 0x41 &amp;&amp; cp &lt;= 0x5A) return [&#39;LATIN CAPITAL LETTER &#39; + ch, &#39;rule&#39;];
    if (cp &gt;= 0x61 &amp;&amp; cp &lt;= 0x7A) return [&#39;LATIN SMALL LETTER &#39; + ch.toUpperCase(), &#39;rule&#39;];
    if (cp &gt;= 0x30 &amp;&amp; cp &lt;= 0x39) return [&#39;DIGIT &#39; + DIGITS[cp - 0x30], &#39;rule&#39;];
    if (cp &lt; 0x80 &amp;&amp; ASCII[ch]) return [ASCII[ch], &#39;table&#39;];
    if (cp &gt;= 0xFE00 &amp;&amp; cp &lt;= 0xFE0F) return [&#39;VARIATION SELECTOR-&#39; + (cp - 0xFE00 + 1), &#39;rule&#39;];
    if (cp &gt;= 0xE0100 &amp;&amp; cp &lt;= 0xE01EF) return [&#39;VARIATION SELECTOR-&#39; + (cp - 0xE0100 + 17), &#39;rule&#39;];
    if (cp &gt;= 0x1F3FB &amp;&amp; cp &lt;= 0x1F3FF) return [&#39;EMOJI MODIFIER FITZPATRICK TYPE-&#39; + [&#39;1-2&#39;, &#39;3&#39;, &#39;4&#39;, &#39;5&#39;, &#39;6&#39;][cp - 0x1F3FB], &#39;rule&#39;];
    if (cp &gt;= 0x1F1E6 &amp;&amp; cp &lt;= 0x1F1FF) return [&#39;REGIONAL INDICATOR SYMBOL LETTER &#39; + String.fromCharCode(65 + cp - 0x1F1E6), &#39;rule&#39;];
    if (cp === 0xE007F) return [&#39;CANCEL TAG&#39;, &#39;rule&#39;];
    if (cp &gt;= 0xE0061 &amp;&amp; cp &lt;= 0xE007A) return [&#39;TAG LATIN SMALL LETTER &#39; + String.fromCharCode(65 + cp - 0xE0061), &#39;rule&#39;];
    if (cp &gt;= 0xE0030 &amp;&amp; cp &lt;= 0xE0039) return [&#39;TAG DIGIT &#39; + DIGITS[cp - 0xE0030], &#39;rule&#39;];
    if (cp &gt;= 0xAC00 &amp;&amp; cp &lt;= 0xD7A3) {
      var s = cp - 0xAC00;
      return [&#39;HANGUL SYLLABLE &#39; + HL[Math.floor(s / 588)] + HV[Math.floor((s % 588) / 28)] + HT[s % 28], &#39;rule&#39;];
    }
    if (test(reUnified, ch)) return [&#39;CJK UNIFIED IDEOGRAPH-&#39; + hex(cp), &#39;rule&#39;];
    if (cp &gt;= 0xD800 &amp;&amp; cp &lt;= 0xDBFF) return [&#39;&lt;lone high surrogate&gt;&#39;, &#39;label&#39;];
    if (cp &gt;= 0xDC00 &amp;&amp; cp &lt;= 0xDFFF) return [&#39;&lt;lone low surrogate&gt;&#39;, &#39;label&#39;];
    if (isPUA(cp)) return [&#39;&lt;private-use-&#39; + hex(cp) + &#39;&gt;&#39;, &#39;label&#39;];
    if (isNonchar(cp)) return [&#39;&lt;noncharacter-&#39; + hex(cp) + &#39;&gt;&#39;, &#39;label&#39;];
    if (cp &lt; 0x20 || (cp &gt;= 0x7F &amp;&amp; cp &lt;= 0x9F)) return [&#39;&lt;control-&#39; + hex(cp) + &#39;&gt;&#39;, &#39;label&#39;];
    // Derive &quot;LATIN ... LETTER X WITH &lt;MARK&gt;&quot; from a canonical decomposition: base ASCII letter + one mark.
    var d = cpsOf(ch.normalize(&#39;NFD&#39;));
    if (d.length === 2 &amp;&amp; MARKS[d[1]] &amp;&amp; /[A-Za-z]/.test(String.fromCodePoint(d[0]))) {
      var base = nameOf(d[0])[0];
      return [base + &#39; WITH &#39; + MARKS[d[1]].replace(/ ACCENT$/, &#39;&#39;), &#39;rule&#39;];
    }
    return [null, null];
  }

  var INVIS = { 0x200D: &#39;ZWJ&#39;, 0x200B: &#39;ZWSP&#39;, 0x200C: &#39;ZWNJ&#39;, 0x200E: &#39;LRM&#39;, 0x200F: &#39;RLM&#39;, 0x2060: &#39;WJ&#39;, 0xFEFF: &#39;BOM&#39;, 0x20: &#39;SP&#39;, 0xA0: &#39;NBSP&#39;, 0x09: &#39;TAB&#39;, 0x0A: &#39;LF&#39;, 0x0D: &#39;CR&#39;, 0xAD: &#39;SHY&#39; };
  function isInvisible(cp) {
    return INVIS[cp] !== undefined || (cp &gt;= 0xFE00 &amp;&amp; cp &lt;= 0xFE0F) || (cp &gt;= 0xE0000 &amp;&amp; cp &lt;= 0xE01EF) ||
      (cp &gt;= 0xD800 &amp;&amp; cp &lt;= 0xDFFF) || cp &lt; 0x20 || (cp &gt;= 0x7F &amp;&amp; cp &lt;= 0x9F);
  }
  // HTML for a single code point in isolation
  function glyphHTML(cp) {
    if (INVIS[cp] !== undefined) return &#39;&lt;span class=&quot;inv&quot;&gt;&#39; + INVIS[cp] + &#39;&lt;/span&gt;&#39;;
    if (cp &gt;= 0xFE00 &amp;&amp; cp &lt;= 0xFE0F) return &#39;&lt;span class=&quot;inv&quot;&gt;VS&#39; + (cp - 0xFE00 + 1) + &#39;&lt;/span&gt;&#39;;
    if (cp &gt;= 0xE0100 &amp;&amp; cp &lt;= 0xE01EF) return &#39;&lt;span class=&quot;inv&quot;&gt;VS&#39; + (cp - 0xE0100 + 17) + &#39;&lt;/span&gt;&#39;;
    if (cp &gt;= 0xE0000 &amp;&amp; cp &lt;= 0xE007F) return &#39;&lt;span class=&quot;inv&quot;&gt;TAG&lt;/span&gt;&#39;;
    if (cp &gt;= 0xD800 &amp;&amp; cp &lt;= 0xDFFF) return &#39;&lt;span class=&quot;inv&quot;&gt;LONE&lt;/span&gt;&#39;;
    if (cp &lt; 0x20 || (cp &gt;= 0x7F &amp;&amp; cp &lt;= 0x9F)) return &#39;&lt;span class=&quot;inv&quot;&gt;C&#39; + hex(cp, 2) + &#39;&lt;/span&gt;&#39;;
    var ch = String.fromCodePoint(cp);
    if (test(reGrExt, ch) &amp;&amp; !(cp &gt;= 0x1F3FB &amp;&amp; cp &lt;= 0x1F3FF)) return esc(&#39;&#92;u25CC&#39; + ch);
    return esc(ch);
  }
  function graphemeHTML(g) {
    var list = cpsOf(g);
    if (list.every(isInvisible)) return list.map(glyphHTML).join(&#39;&#39;);
    return esc(g);
  }
  function role(cp) {
    var ch = String.fromCodePoint(cp);
    if (cp === 0x200D) return &#39;ZWJ&#39;;
    if (cp === 0x0D) return &#39;CR&#39;;
    if (cp === 0x0A) return &#39;LF&#39;;
    if (cp &gt;= 0x1F1E6 &amp;&amp; cp &lt;= 0x1F1FF) return &#39;Regional_Indicator&#39;;
    if (cp &gt;= 0x1F3FB &amp;&amp; cp &lt;= 0x1F3FF) return &#39;Extend (modifier)&#39;;
    if (test(reGrExt, ch)) return &#39;Extend&#39;;
    if (cp &gt;= 0xE0020 &amp;&amp; cp &lt;= 0xE007F) return &#39;Extend (tag)&#39;;
    var g = genCat(cp).code;
    if (g === &#39;Mc&#39;) return &#39;SpacingMark&#39;;
    if (g === &#39;Cc&#39; || g === &#39;Zl&#39; || g === &#39;Zp&#39; || (g === &#39;Cf&#39; &amp;&amp; cp !== 0x200C)) return &#39;Control&#39;;
    if (test(reExtPict, ch)) return &#39;Base · Ext_Pict&#39;;
    return &#39;Base&#39;;
  }
  function utf8(cp) {
    if (cp &gt;= 0xD800 &amp;&amp; cp &lt;= 0xDFFF) return [0xEF, 0xBF, 0xBD]; // encoders substitute U+FFFD
    if (cp &lt; 0x80) return [cp];
    if (cp &lt; 0x800) return [0xC0 | cp &gt;&gt; 6, 0x80 | cp &amp; 63];
    if (cp &lt; 0x10000) return [0xE0 | cp &gt;&gt; 12, 0x80 | (cp &gt;&gt; 6) &amp; 63, 0x80 | cp &amp; 63];
    return [0xF0 | cp &gt;&gt; 18, 0x80 | (cp &gt;&gt; 12) &amp; 63, 0x80 | (cp &gt;&gt; 6) &amp; 63, 0x80 | cp &amp; 63];
  }
  function u16(cp) {
    if (cp &lt; 0x10000) return [cp];
    var v = cp - 0x10000;
    return [0xD800 + (v &gt;&gt; 10), 0xDC00 + (v &amp; 0x3FF)];
  }

  function excelFormula(s) {
    var parts = [], buf = &#39;&#39;;
    cpsOf(s).forEach(function (cp) {
      if (cp &gt;= 0x20 &amp;&amp; cp &lt; 0x7F &amp;&amp; cp !== 0x22) { buf += String.fromCharCode(cp); return; }
      if (buf) { parts.push(&#39;&quot;&#39; + buf + &#39;&quot;&#39;); buf = &#39;&#39;; }
      parts.push(&#39;UNICHAR(&#39; + cp + &#39;)&#39;);
    });
    if (buf) parts.push(&#39;&quot;&#39; + buf + &#39;&quot;&#39;);
    return parts.length ? &#39;=&#39; + parts.join(&#39;&amp;&#39;) : &#39;=&quot;&quot;&#39;;
  }

  /* ---------------- Inspector ---------------- */
  var insIn = $(&#39;insIn&#39;);
  var PRESETS = [
    [[0x2602], &#39;bare&#39;],
    [[0x2602, 0xFE0F], &#39;emoji&#39;],
    [[0x1F438], &#39;&#39;],
    [[0x65, 0x301], &#39;decomposed&#39;],
    [[0x1F44B, 0x1F3FD], &#39;&#39;],
    [[0x1F468, 0x200D, 0x1F469, 0x200D, 0x1F467], &#39;&#39;],
    [[0x1F1EF, 0x1F1F5], &#39;&#39;],
    [[0x1F3F3, 0xFE0F, 0x200D, 0x1F308], &#39;&#39;],
    [[0x23, 0xFE0F, 0x20E3], &#39;keycap&#39;],
    [[0x1F438, 0x200D, 0x2602, 0xFE0F], &#39;non-RGI&#39;],
    [[0xD55C, 0xAE00], &#39;Hangul&#39;],
    [[0x212B, 0x20, 0x76, 0x73, 0x20, 0xC5], &#39;&#39;],
    [[0xD83D], &#39;lone surrogate&#39;]
  ];
  var insPresets = $(&#39;insPresets&#39;);
  PRESETS.forEach(function (p) {
    var b = document.createElement(&#39;button&#39;);
    b.type = &#39;button&#39;; b.className = &#39;preset&#39;;
    b.textContent = (p[0][0] &gt;= 0xD800 &amp;&amp; p[0][0] &lt;= 0xDFFF ? &#39;D83D&#39; : fromCps(p[0])) + (p[1] ? &#39; &#39; + p[1] : &#39;&#39;);
    b.addEventListener(&#39;click&#39;, function () { insIn.value = fromCps(p[0]); renderInspector(); insIn.focus(); });
    insPresets.appendChild(b);
  });

  function renderInspector() {
    var s = insIn.value;
    var gs = graphemes(s);
    var allCps = cpsOf(s);
    var bytes = allCps.reduce(function (n, cp) { return n + utf8(cp).length; }, 0);
    $(&#39;stG&#39;).textContent = gs.length;
    $(&#39;stC&#39;).textContent = allCps.length;
    $(&#39;stU&#39;).textContent = s.length;
    $(&#39;stB&#39;).textContent = bytes;

    // Alignment strip
    var align = $(&#39;insAlign&#39;);
    if (!s) { align.innerHTML = &#39;&#39;; }
    else if (s.length &gt; 80) { align.innerHTML = &#39;&lt;p class=&quot;small muted&quot;&gt;The alignment strip is hidden for strings longer than 80 UTF-16 units.&lt;/p&gt;&#39;; }
    else {
      var cols = s.length;
      var h = &#39;&lt;div class=&quot;grid&quot; style=&quot;grid-template-columns:auto repeat(&#39; + cols + &#39;, minmax(3.1rem, 1fr))&quot;&gt;&#39;;
      var col = 2;
      h += &#39;&lt;div class=&quot;rowlabel&quot; style=&quot;grid-row:1;grid-column:1&quot;&gt;Grapheme&lt;/div&gt;&#39;;
      gs.forEach(function (g) {
        h += &#39;&lt;div class=&quot;cell cg&quot; style=&quot;grid-row:1;grid-column:&#39; + col + &#39; / span &#39; + g.length + &#39;&quot; title=&quot;&#39; + g.length + &#39; unit(s)&quot;&gt;&#39; + graphemeHTML(g) + &#39;&lt;/div&gt;&#39;;
        col += g.length;
      });
      col = 2;
      h += &#39;&lt;div class=&quot;rowlabel&quot; style=&quot;grid-row:2;grid-column:1&quot;&gt;Code pt&lt;/div&gt;&#39;;
      allCps.forEach(function (cp) {
        var w = cp &gt;= 0x10000 ? 2 : 1;
        h += &#39;&lt;div class=&quot;cell cc&#39; + (isInvisible(cp) || (cp &gt;= 0x1F3FB &amp;&amp; cp &lt;= 0x1F3FF) || test(reGrExt, String.fromCodePoint(cp)) ? &#39; inv&#39; : &#39;&#39;) + &#39;&quot; style=&quot;grid-row:2;grid-column:&#39; + col + &#39; / span &#39; + w + &#39;&quot;&gt;&#39; + U(cp) + &#39;&lt;/div&gt;&#39;;
        col += w;
      });
      col = 2;
      h += &#39;&lt;div class=&quot;rowlabel&quot; style=&quot;grid-row:3;grid-column:1&quot;&gt;UTF-16&lt;/div&gt;&#39;;
      for (var i = 0; i &lt; s.length; i++) {
        var u = s.charCodeAt(i);
        var cls = u &gt;= 0xD800 &amp;&amp; u &lt;= 0xDBFF ? &#39; h&#39; : (u &gt;= 0xDC00 &amp;&amp; u &lt;= 0xDFFF ? &#39; l&#39; : &#39;&#39;);
        h += &#39;&lt;div class=&quot;cell cu&#39; + cls + &#39;&quot; style=&quot;grid-row:3;grid-column:&#39; + (col++) + &#39;&quot;&gt;&#39; + hex(u) + &#39;&lt;/div&gt;&#39;;
      }
      h += &#39;&lt;/div&gt;&lt;div class=&quot;legend&quot;&gt;&lt;span&gt;&lt;i style=&quot;background:var(--hi-soft);border:1px solid var(--hi)&quot;&gt;&lt;/i&gt;high surrogate&lt;/span&gt;&lt;span&gt;&lt;i style=&quot;background:var(--lo-soft);border:1px solid var(--lo)&quot;&gt;&lt;/i&gt;low surrogate&lt;/span&gt;&lt;span&gt;&lt;i style=&quot;background:var(--warn-soft);border:1px solid var(--warn)&quot;&gt;&lt;/i&gt;invisible / attaching&lt;/span&gt;&lt;/div&gt;&#39;;
      align.innerHTML = h;
    }

    // Grapheme list
    var list = $(&#39;insList&#39;);
    if (!s) { list.innerHTML = &#39;&lt;div class=&quot;empty&quot;&gt;Type or paste something above, or pick an example.&lt;/div&gt;&#39;; }
    else {
      var openByDefault = gs.length &lt;= 3;
      var shown = gs.slice(0, 300);
      list.innerHTML = shown.map(function (g, gi) {
        var cps = cpsOf(g);
        var flags = [];
        if (cps.indexOf(0xFE0F) &gt;= 0) flags.push([&#39;VS16 · emoji&#39;, &#39;w&#39;]);
        if (cps.indexOf(0xFE0E) &gt;= 0) flags.push([&#39;VS15 · text&#39;, &#39;w&#39;]);
        if (cps.indexOf(0x200D) &gt;= 0) flags.push([&#39;ZWJ sequence&#39;, &#39;w&#39;]);
        if (cps.some(function (c) { return c &gt;= 0x1F3FB &amp;&amp; c &lt;= 0x1F3FF; })) flags.push([&#39;skin tone&#39;, &#39;w&#39;]);
        if (cps.length === 2 &amp;&amp; cps.every(function (c) { return c &gt;= 0x1F1E6 &amp;&amp; c &lt;= 0x1F1FF; })) flags.push([&#39;flag&#39;, &#39;a&#39;]);
        if (cps.some(function (c) { return c &gt;= 0x10000; })) flags.push([&#39;astral · surrogates&#39;, &#39;a&#39;]);
        if (cps.some(function (c) { return c &gt;= 0xD800 &amp;&amp; c &lt;= 0xDFFF; })) flags.push([&#39;lone surrogate&#39;, &#39;b&#39;]);
        try { if (g.normalize(&#39;NFC&#39;) !== g) flags.push([&#39;not NFC&#39;, &#39;b&#39;]); } catch (e) {}
        if (cps.length &gt; 1 &amp;&amp; !flags.length) flags.push([&#39;combining&#39;, &#39;&#39;]);

        var rows = cps.map(function (cp) {
          var nm = nameOf(cp), gc = genCat(cp);
          var nameCell = nm[0] ? esc(nm[0]) : &#39;&lt;span class=&quot;muted&quot;&gt;(name not in built-in table)&lt;/span&gt;&#39;;
          var sub = gc.code + &#39; · &#39; + gc.label + (test(reEmojiPres, String.fromCodePoint(cp)) ? &#39; · Emoji_Presentation&#39; : &#39;&#39;);
          var units = u16(cp);
          return &#39;&lt;tr&gt;&lt;td class=&quot;gl&quot;&gt;&#39; + glyphHTML(cp) + &#39;&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;&#39; + U(cp) + &#39;&lt;/td&gt;&lt;td class=&quot;num&quot;&gt;&#39; + cp +
            &#39;&lt;/td&gt;&lt;td class=&quot;name&quot;&gt;&#39; + nameCell + &#39;&lt;span class=&quot;gcn&quot;&gt;&#39; + esc(sub) + &#39;&lt;/span&gt;&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;&#39; + esc(role(cp)) +
            &#39;&lt;/td&gt;&lt;td class=&quot;mono&quot;&gt;&#39; + units.map(function (x) { return hex(x); }).join(&#39; &#39;) + &#39; &lt;span class=&quot;muted&quot;&gt;(&#39; + units.length + &#39;)&lt;/span&gt;&lt;/td&gt;&lt;td class=&quot;mono small&quot;&gt;&#39; +
            utf8(cp).map(function (b) { return hex(b, 2); }).join(&#39; &#39;) + &#39;&lt;/td&gt;&lt;/tr&gt;&#39;;
        }).join(&#39;&#39;);

        return &#39;&lt;details class=&quot;gitem&quot;&#39; + (openByDefault ? &#39; open&#39; : &#39;&#39;) + &#39;&gt;&lt;summary&gt;&#39; +
          &#39;&lt;span class=&quot;idx&quot;&gt;#&#39; + (gi + 1) + &#39;&lt;/span&gt;&#39; +
          &#39;&lt;span class=&quot;big&quot;&gt;&#39; + graphemeHTML(g) + &#39;&lt;/span&gt;&#39; +
          &#39;&lt;span style=&quot;min-width:0&quot;&gt;&lt;span class=&quot;seq&quot;&gt;&#39; + cps.map(function (cp) {
            return &#39;&lt;span class=&quot;chip&#39; + (isInvisible(cp) || (cp &gt;= 0x1F3FB &amp;&amp; cp &lt;= 0x1F3FF) || test(reGrExt, String.fromCodePoint(cp)) ? &#39; inv&#39; : &#39;&#39;) + &#39;&quot;&gt;&#39; + U(cp) + &#39;&lt;/span&gt;&#39;;
          }).join(&#39;&#39;) + &#39;&lt;/span&gt;&#39; +
          (flags.length ? &#39;&lt;span class=&quot;flags&quot;&gt;&#39; + flags.map(function (f) { return &#39;&lt;span class=&quot;flag &#39; + f[1] + &#39;&quot;&gt;&#39; + f[0] + &#39;&lt;/span&gt;&#39;; }).join(&#39;&#39;) + &#39;&lt;/span&gt;&#39; : &#39;&#39;) +
          &#39;&lt;/span&gt;&#39; +
          &#39;&lt;span class=&quot;meta&quot; title=&quot;LEN in compatibility Version 1 / Version 2&quot;&gt;LEN v1 &#39; + g.length + &#39;&lt;br&gt;LEN v2 &#39; + cps.length + &#39;&lt;/span&gt;&#39; +
          &#39;&lt;/summary&gt;&lt;div class=&quot;detail&quot;&gt;&lt;div style=&quot;overflow-x:auto&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Glyph&lt;/th&gt;&lt;th&gt;Hex&lt;/th&gt;&lt;th class=&quot;num&quot;&gt;Dec&lt;/th&gt;&lt;th&gt;Name / category&lt;/th&gt;&lt;th&gt;Break role&lt;/th&gt;&lt;th&gt;UTF-16&lt;/th&gt;&lt;th&gt;UTF-8&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&#39; +
          rows + &#39;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;/details&gt;&#39;;
      }).join(&#39;&#39;) + (gs.length &gt; shown.length ? &#39;&lt;p class=&quot;small muted&quot;&gt;Showing first &#39; + shown.length + &#39; of &#39; + gs.length + &#39; graphemes.&lt;/p&gt;&#39; : &#39;&#39;);
    }

    var show = !!s;
    $(&#39;insExcel&#39;).hidden = !show; $(&#39;insExcelNote&#39;).hidden = !show;
    if (show) $(&#39;insFormula&#39;).textContent = excelFormula(fromCps(cpsOf(s).slice(0, 300)));
  }
  insIn.addEventListener(&#39;input&#39;, renderInspector);
  $(&#39;insClear&#39;).addEventListener(&#39;click&#39;, function () { insIn.value = &#39;&#39;; renderInspector(); insIn.focus(); });
  $(&#39;insCopy&#39;).addEventListener(&#39;click&#39;, function () {
    var t = $(&#39;insFormula&#39;).textContent, b = this;
    function done(ok) { b.textContent = ok ? &#39;Copied&#39; : &#39;Select &amp; copy&#39;; setTimeout(function () { b.textContent = &#39;Copy&#39;; }, 1400); }
    try { navigator.clipboard.writeText(t).then(function () { done(true); }, function () { done(false); }); } catch (e) { done(false); }
  });
  if (!segmenter) {
    $(&#39;insList&#39;).insertAdjacentHTML(&#39;beforebegin&#39;, &#39;&lt;p class=&quot;callout warn small&quot;&gt;This browser lacks &lt;code&gt;Intl.Segmenter&lt;/code&gt;; graphemes fall back to code points.&lt;/p&gt;&#39;);
  }
  insIn.value = fromCps([0x2602, 0x2602, 0xFE0F, 0x1F438, 0x65, 0x301, 0x1F468, 0x200D, 0x1F469, 0x200D, 0x1F467]);
  renderInspector();

  /* ---------------- Comparator ---------------- */
  var A = $(&#39;cmpA&#39;), B = $(&#39;cmpB&#39;);
  var PAIRS = [
    [[0x2602], [0x2602, 0xFE0F]],
    [[0xE9], [0x65, 0x301]],
    [[0x41, 0x70, 0x70, 0x6C, 0x65], [0x61, 0x70, 0x70, 0x6C, 0x65]],
    [[0x1F44B], [0x1F44B, 0x1F3FD]],
    [[0x212B], [0xC5]],
    [[0xFB01], [0x66, 0x69]],
    [[0x1F469, 0x200D, 0x1F4BB], [0x1F469, 0x1F4BB]],
    [[0x43, 0x41, 0x46, 0xC9], [0x63, 0x61, 0x66, 0x65, 0x301]]
  ];
  var cmpPresets = $(&#39;cmpPresets&#39;);
  PAIRS.forEach(function (p) {
    var b = document.createElement(&#39;button&#39;);
    b.type = &#39;button&#39;; b.className = &#39;preset&#39;; b.textContent = fromCps(p[0]) + &#39; / &#39; + fromCps(p[1]);
    b.addEventListener(&#39;click&#39;, function () { A.value = fromCps(p[0]); B.value = fromCps(p[1]); renderCompare(); });
    cmpPresets.appendChild(b);
  });
  $(&#39;cmpSwap&#39;).addEventListener(&#39;click&#39;, function () { var t = A.value; A.value = B.value; B.value = t; renderCompare(); });

  var nfc = function (s) { try { return s.normalize(&#39;NFC&#39;); } catch (e) { return s; } };
  var MODES = [
    { name: &#39;Strict code point&#39;, xl: &#39;EXACT(A, B)&#39;, fn: function (a, b) { return a === b; } },
    { name: &#39;NFC-normalized&#39;, xl: &#39;canonical equivalents folded&#39;, fn: function (a, b) { return nfc(a) === nfc(b); } },
    { name: &#39;NFC + case-insensitive&#39;, xl: &#39;plus a = A&#39;, fn: function (a, b) { return nfc(a).toLowerCase() === nfc(b).toLowerCase(); } },
    { name: &#39;Excel = (model)&#39;, xl: &#39;NFC + case + ignore U+FE0F&#39;, fn: function (a, b) {
        var k = function (s) { return nfc(s.replace(/&#92;uFE0F/g, &#39;&#39;)).toLowerCase(); };
        return k(a) === k(b);
      } }
  ];

  function lcsDiff(a, b) {
    var n = a.length, m = b.length, i, j;
    var dp = [];
    for (i = 0; i &lt;= n; i++) { dp.push(new Array(m + 1).fill(0)); }
    for (i = n - 1; i &gt;= 0; i--) for (j = m - 1; j &gt;= 0; j--)
      dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
    var ra = [], rb = [];
    i = 0; j = 0;
    while (i &lt; n &amp;&amp; j &lt; m) {
      if (a[i] === b[j]) { ra.push([a[i], &#39;same&#39;]); rb.push([b[j], &#39;same&#39;]); i++; j++; }
      else if (dp[i + 1][j] &gt;= dp[i][j + 1]) { ra.push([a[i], &#39;del&#39;]); i++; }
      else { rb.push([b[j], &#39;add&#39;]); j++; }
    }
    while (i &lt; n) ra.push([a[i++], &#39;del&#39;]);
    while (j &lt; m) rb.push([b[j++], &#39;add&#39;]);
    return [ra, rb];
  }

  function renderCompare() {
    var a = A.value, b = B.value;
    $(&#39;cmpOut&#39;).innerHTML = MODES.map(function (m) {
      var r = m.fn(a, b);
      return &#39;&lt;div class=&quot;vd &#39; + (r ? &#39;yes&#39; : &#39;no&#39;) + &#39;&quot;&gt;&lt;div class=&quot;res&quot;&gt;&#39; + (r ? &#39;TRUE&#39; : &#39;FALSE&#39;) + &#39;&lt;/div&gt;&lt;div class=&quot;name&quot;&gt;&#39; + m.name + &#39;&lt;/div&gt;&lt;div class=&quot;xl&quot;&gt;&#39; + esc(m.xl) + &#39;&lt;/div&gt;&lt;/div&gt;&#39;;
    }).join(&#39;&#39;);
    var ca = cpsOf(a), cb = cpsOf(b);
    if (ca.length &gt; 200 || cb.length &gt; 200) { $(&#39;cmpDiff&#39;).innerHTML = &#39;&lt;p class=&quot;small muted&quot;&gt;Code-point diff hidden for strings over 200 code points.&lt;/p&gt;&#39;; return; }
    var d = lcsDiff(ca, cb);
    var line = function (label, items, s) {
      return &#39;&lt;div class=&quot;line&quot;&gt;&lt;b&gt;&#39; + label + &#39;&lt;/b&gt;&lt;div&gt;&lt;span class=&quot;seq&quot;&gt;&#39; +
        (items.length ? items.map(function (x) { return &#39;&lt;span class=&quot;chip &#39; + x[1] + &#39;&quot; title=&quot;&#39; + esc(nameOf(x[0])[0] || genCat(x[0]).label) + &#39;&quot;&gt;&#39; + U(x[0]) + &#39;&lt;/span&gt;&#39;; }).join(&#39;&#39;) : &#39;&lt;span class=&quot;muted small&quot;&gt;(empty)&lt;/span&gt;&#39;) +
        &#39;&lt;/span&gt; &lt;span class=&quot;muted small mono&quot;&gt;&amp;nbsp;LEN &#39; + s.length + &#39;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&#39;;
    };
    $(&#39;cmpDiff&#39;).innerHTML = line(&#39;A&#39;, d[0], a) + line(&#39;B&#39;, d[1], b) +
      &#39;&lt;p class=&quot;small muted&quot; style=&quot;margin:.2rem 0 0&quot;&gt;Red code points are only in A and green ones are only in B. Hover a chip to see its name.&lt;/p&gt;&#39;;
  }
  A.addEventListener(&#39;input&#39;, renderCompare);
  B.addEventListener(&#39;input&#39;, renderCompare);
  A.value = fromCps([0x2602]); B.value = fromCps([0x2602, 0xFE0F]);
  renderCompare();
  /* ---------------- &quot;Try&quot; buttons: load examples into the workbench ---------------- */
  var parseCps = function (str) { return str.trim().split(/&#92;s+/).map(function (h) { return parseInt(h, 16); }); };
  document.addEventListener(&#39;click&#39;, function (e) {
    var btn = e.target.closest(&#39;.try&#39;);
    if (!btn) return;
    var target;
    if (btn.dataset.cps) { insIn.value = fromCps(parseCps(btn.dataset.cps)); renderInspector(); target = $(&#39;inspector&#39;); }
    else { A.value = fromCps(parseCps(btn.dataset.a)); B.value = fromCps(parseCps(btn.dataset.b)); renderCompare(); target = $(&#39;comparator&#39;); }
    target.scrollIntoView({ block: &#39;start&#39; });
  });
  /* ---------------- Citations (Wikipedia style) ---------------- */
  (function () {
    var groups = { ref: { list: $(&#39;reflist&#39;), order: [], label: function (i) { return String(i + 1); } },
                   note: { list: $(&#39;notelist&#39;), order: [], label: function (i) { return String.fromCharCode(97 + i); } } };
    var uses = {};
    document.querySelectorAll(&#39;sup.ref&#39;).forEach(function (sup) {
      var key = sup.dataset.ref, g = groups[key.indexOf(&#39;n-&#39;) === 0 ? &#39;note&#39; : &#39;ref&#39;];
      if (!uses[key]) { uses[key] = []; g.order.push(key); }
      var id = &#39;cite-ref-&#39; + key + &#39;-&#39; + uses[key].length;
      uses[key].push(id);
      sup.id = id;
      var label = g.label(g.order.indexOf(key));
      sup.innerHTML = &#39;&lt;a href=&quot;#cite-&#39; + key + &#39;&quot; title=&quot;&#39; + (g === groups.note ? &#39;Note &#39; : &#39;Reference &#39;) + label + &#39;&quot;&gt;[&#39; + label + &#39;]&lt;/a&gt;&#39;;
    });
    Object.keys(groups).forEach(function (name) {
      var g = groups[name];
      var items = {};
      Array.prototype.forEach.call(g.list.children, function (li) { items[li.dataset.key] = li; });
      g.order.forEach(function (key) {
        var li = items[key];
        if (!li) return;
        li.id = &#39;cite-&#39; + key;
        var ids = uses[key];
        li.querySelector(&#39;.back&#39;).innerHTML = ids.length === 1
          ? &#39;&lt;a href=&quot;#&#39; + ids[0] + &#39;&quot; title=&quot;Jump up&quot;&gt;^&lt;/a&gt;&#39;
          : &#39;^ &#39; + ids.map(function (id, i) { return &#39;&lt;sup&gt;&lt;a href=&quot;#&#39; + id + &#39;&quot; title=&quot;Jump up&quot;&gt;&#39; + String.fromCharCode(97 + i) + &#39;&lt;/a&gt;&lt;/sup&gt;&#39;; }).join(&#39; &#39;);
        g.list.appendChild(li);
        delete items[key];
      });
      Object.keys(items).forEach(function (k) { items[k].remove(); }); // drop uncited entries
    });
  })();
})();
&lt;/script&gt;
</content>
  </entry>
</feed>