UTF-8からSJISに文字化けすると糸偏の漢字がよく出てくる

# はじめに

Twitterで、文字化けネタを幾つかつぶやきました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">サッちゃんはね<br>サチコっていうんだ<br>ほんとはね<br>だけど ちっちゃいから<br>自分のこと SJISで 保存するんだよ<br>おかしいな 繧ｵ繝ちゃん</p>&mdash; ロボ太 (@kaityo256) <a href="https://twitter.com/kaityo256/status/917760282960609280?ref_src=twsrc%5Etfw">2017年10月10日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">「私 魔女のキキです。こっちはSJISの繧ｸ繧ｸ」</p>&mdash; ロボ太 (@kaityo256) <a href="https://twitter.com/kaityo256/status/949470936054431746?ref_src=twsrc%5Etfw">2018年1月6日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">UTF-8「もしかして…」<br>SJIS「私達…」<br>「「入れ替わ縺縺ｦ繧九≦縲懶ｼ†」」</p>&mdash; ロボ太 (@kaityo256) <a href="https://twitter.com/kaityo256/status/963399631525765121?ref_src=twsrc%5Etfw">2018年2月13日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


どれもUTF-8で保存された文字をSJISとして解釈したための文字化けを表現したものですが、パッと見で「糸偏の漢字が多いな」ということがわかるかと思います。なぜそうなるかを簡単に説明してみようと思います。

なお、文字コードはいろいろ面倒なので、ここではざっくりとしたことしか言いません。詳細はちゃんと仕様にあたってください。


# UTF-8の構造

UTF-8は、いわゆるASCII文字以外を2バイト〜6バイトで符号化する仕組みですが、とりあえず日常よく使う日本語の文字は、3バイトに符号化されます。UTF-8は、最初に「何バイトで符号化したか」を、1バイト目の連続する1のビット数で表現します。3バイトで表現するなら「1110」となります。その後、2バイト目以降は頭の「10」が予約されており、それ以降が文字を表現するビットになります。

したがって、3バイト、つまり24ビットのうち、8ビットが予約されており、残りの16ビットで文字を表現することになります。

ビットの構造としてはこんな感じです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/79744/6e347d09-b56f-c94c-ab9e-ece012ff5ba5.png)


ここで、AAAABBBBCCCCDDDDは、Unicode4桁のビット表現です。Unicodeの三桁目のCCCCが2ビットずつ2バイト目と3バイト目に別れることに注意してください。

例えば「あ」という文字はU+3042ですが、これを4桁ずつビットにバラすと、

```
0011 3
0000 0
0100 4
0010 2
```

となります。これを上からAAAA〜DDDDだと思って、先程のUTF-8の3バイト表現に突っ込むと、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/79744/875292e0-3cde-f14f-bcf8-75e929221f9b.png)

これを16進表現すると「E3 81 82」となり、これが「あ」をUTF-8で符号化したものになります。

# ひらがなとカタカナ

さて、Unicodeにおいて、ひらがなとカタカナは、

* 「ぁ」(U+3041)〜「ん」(U+3093)
* 「ァ」(U+30A1)〜「ヶ」(U+30F6)

と、U+30XXという形をしています。この時点で、UTF-8で符号化したときの1バイト目全部と、2バイト目の上位6ビットが確定していることがわかります。特に、Unicode4桁の一桁目が3であるために、1バイト目がE3になることは確定です。また、2バイト目の上位4ビットも「8」で確定です。

さて、先ほどの「あ(U+3042)」の例を考えましょう。UTF-8だと「E3 81 82」となるのでした。この最初の2バイトを無理やりSJISだと解釈すると、「E3 81」で一つの文字になります。これは「縺」という文字に対応します。同様に「い(U+3044)」はUTF-8だと「E3 81 84」となるため、やはり最初の2バイトは「E3 81」、つまり「縺」が出てきます。

カタカナはどうでしょうか。「ア(U+30A2」をUTF-8で符号化してみましょう。まずは4ビットずつにバラします。

```
0011 3
0000 0
1010 A
0010 2
```

これを先程のAAAA〜DDDDに突っ込むと、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/79744/1cdf61ca-7b30-5d70-5391-4c3ced6b5bc9.png)

と、「E3 82 A2」となります。先頭2バイトをSJISだと思うと「E3 82」となり、これは「縺(E3 81)」の隣の文字、「繧 (E3 82)」となります。

以上のように、UTF-8のひらがな、カタカナ3バイトの上位2バイトをSJISで解釈した時、1バイト目はE3で確定、2バイト目の上位4bitは8で確定、残りの下位4bitとしてとり得るのは「1」「2」「3」の三種類です。

というわけで「文字化け表」を作ると、以下のような感じです。

* 「ぁ」(U+3041)〜「み」(U+307F) → 「縺 (E3 81)」
* 「む」(U+3080)〜「タ」(U+30BF) → 「繧 (E3 82)」
* 「ダ」(U+30C0)〜「ヶ」(U+30F6) → 「繝 (E3 83)」

# まとめ

UTF-8のひらがなやカタカナをSJISで解釈すると「縺」「繧」「繝」の三文字が出て来ることがわかりました。もちろん漢字は違う文字として解釈されますが、日本語の文章は少なくともひらがなを含むことがほとんどですので、UTF-8→SJISの方向で文字化けすると「縺」か「繧」は出てくることになりますね。

冒頭の「サッちゃん」ネタでは、「サッ」がUTF-8からSJISに化けたという想定でした。「文字化け表」を見ると、「サ(U+30B5)」は「繧」に、「ッ(U+30C3)」は「繝」になります。

さらに言うと、「サッ」はUTF-8では「E3 82 B5 E3 83 83」ですが、これをSJISで解釈しようとすると「繧 (E3 82)」の直後の「B5」が半角カナの「オ」として解釈されます。最後の「83」はSJISでは文字が割り当てられていないため、解釈できません。以上から、最終的に「サッ」は「繧ｵ繝」になったわけです。

いや、だからどうしたと言われたらそれまでなんですけど。