文字化けクイズ(解答編)

問題編をまだ見ていない人は文字化けクイズ(問題編)を先にご覧ください。


  • Q1(初級): 「こんにちは、世界」と表示されるはずなのになぜか「縺薙」などの難しい漢字が表示された。何が起きたか。
  • A1: UTF-8エンコードされているバイト列をShift_JISだと思ってデコードするとこうなりますね。よく見かけるのがUTF-8で保存されているウェブページをブラウザで開いたときに自動判定に失敗してしまったケース。UTF-8の有名なサイトとしてはWikipediaなんかがあるので、これを開いてエンコーディングShift_JISにすることで簡単に体験できます。Firefoxだとメニューから「表示」→「文字エンコーディング」→「日本語(Shift_JIS)」ね。


Pythonでこの文字化けを再現するコードはこちら:

>>> print u"こんにちは".encode("utf-8").decode("sjis")
縺薙s縺ォ縺。縺ッ


採点基準: UTF-8(10点), Shift_JIS(10点) の合計20点。utf8, sjis, cp932などでもOK。

  • Q2(初級): 「こんにちは、世界」と表示されるはずなのになぜか「ã」(aの上に~)などが表示された。何が起きたか。
  • A2: 同じくUTF-8エンコードされているバイト列をlatin-1(ISO-8859-1)だとおもってデコードするとこうなります。同じようにWikipediaを開いて「西欧(ISO-8859-1)」を選べば体験できます。


Pythonでこの文字化けを再現するコードはこちら:

>>> print u"こんにちは".encode("utf-8").decode("latin-1")
(はてなダイアリーだと〓に置き換えられてしまうので省略)


採点基準: UTF-8(10点), ISO-8859-1(10点) の合計20点。latin1などの表記でもOK。


あんまりブラウザでの文字化けの話ばっかりでも面白くないので省きましたが、半角カナだらけになる化け方もよく見かけますね。これはEUC-JPでエンコードされているものをShift_JISでデコードしたときに起きる現象。はてなEUCなのでShift_JISで表示させると半角カナだらけの化け方を体験できます。

  • Q3(中級): ブラウザであるリンクをクリックしたところ「臼NG」で始まる謎の文字列が表示された。何が起きたか。
  • A3: これはPNG形式の画像ファイルをShift_JISで開いたときに起きる現象ですね。臼NGはPNGのヘッダの最初の4バイトをShift_JISでデコードした文字列です。サーバの設定がおかしくてContent-typeヘッダが適切な値になっていないなどが原因ですが、サーバ側の問題なのでユーザ側でできる対処としては例えばファイルに保存して拡張子をpngにしてから開くとかになるでしょうか。


採点基準: PNG(10点), 「サーバ側で(5点)適切にファイルの種類(5点)を返していない」という趣旨の記述。合計20点。

  • Q4(上級): 「こんにちは」と表示されるはずなのになぜか「S?kao」と表示された。何が起きたか。
  • A4: これは「こんにちは」が16ビットのUCS-2で表現されているにもかかわらずそれを受け取る関数が8ビットのバイト列として文字列が渡されることを期待していて、16ビットのうちの下8ビットだけを使って残りを捨ててしまったときに起こる現象です。僕がこれを最初に見たのはJython2.1でPythonユニコード文字列をSwingのJFrameのコンストラクタに渡したときです。その他にもマルチバイト圏以外で作られたソフトやウェブサービスを使うとたまに目にするように思います。


この現象を再現するPythonコードは:

>>> print "".join(chr(ord(c) % 256) for c in u"こんにちは")
S?kao


採点基準: UCS-2または16bit(10点), 上位バイトが失われるという趣旨の記述(10点)の合計20点。

  • Q5(上級): 「ファイルが見つかりません」と表示されるはずなのになぜか斜め四角に囲まれた疑問符などが表示された。何が起きたか。なお参考までに表示された文字列は20文字であり、表示されたウェブページのエンコーディングはutf8だった。(追記:6文字目までがが?t?@?Cになっている、という情報も付け加えるとエンコーディングまで含めて特定できるか。そこまで求める問題のつもりじゃないけど。)
  • A5: あるバイト列をユニコード文字列にデコードする際に変換テーブルにない文字が出現した場合、Pythonでは例外(UnicodeDecodeError)を投げるstrictモードがデフォルトになっていますが、他の言語環境では無視して続行する(ignore)や適当な文字で置き換える(replace)などのモードがデフォルトの場合があります。問題文に書いてある「斜めの四角に囲われた疑問符」はユニコードのREPLACEMENT CHARACTER(U+FFFD)で、replaceモードのときに置き換える対象としてこの文字を使うケースが多いです。Unicode Specials - Wikipedia, the free encyclopediaを参照。この文字はUTF-8エンコードした場合に3バイトになるので、例えば1バイト目や2バイト目で範囲外になった場合出力のバイト数や文字数が入力のバイト数や文字数の整数倍にならなくなります。

僕がこの現象を見たのは他のマシンでJavaプログラムがコマンドを実行した結果をネットワーク越しに送信して別のマシンで集計表示するというシチュエーションで、シェルが返した実行結果(8bit列)をそのまま別のマシンに送ってしまっていて、メッセージがASCIIの時や、受け手と送り手が同じデフォルトエンコーディングの場合には発覚しないけども、送り手がWindowsSJISで送り、受け手がLinuxUTF-8として解釈してしまった場合には化けるというケースだったかと思います。と記憶をたよりに書いたもののいま当時のメールを発掘してみると

こ(82 B1)
↓
E3 83 BB EF BD BD
E3 83 BB EF BD BD

表(95 5C)
↓
E3 83 BB EF BD BD
5C

なんて書いてあるので出題と全然違いますね。あれれ。どうりで当時の記憶より難易度が低く感じると…。


さてこの現象を再現するためのPythonのコードはこちら:

>>> import codecs
>>> codecs.lookup("utf-8").decode(u"ファイルが見つかりません".encode("sjis"), errors="replace")
(u'\ufffdt\ufffd@\ufffdC\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\x82\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd', 24)


採点基準: replaceモードに関する言及(10点), REPLACEMENT CHARACTERまたはU+FFFD(5点), シェルの出力エンコーディングは環境依存である件の指摘(5点)の20点。WindowsまたはSJISという言及があればボーナス10点。
追加点: 3番をのぞく各問題ごとに、問題文の現象を再現するコードを1言語ごとに10点


解説とおおざっぱな採点基準を書いてみましたが楽しんでもらえたでしょうか。こんなのを書いている筆者も文字コードの話はあまり得意ではないので、なにか間違いや不正確な点などがありましたらご指摘ください。


おすすめ書籍