Shift_JIS と CP932 の違い: 誰もが混同するエンコーディング
Shift_JIS と CP932 の正確な技術的違いをバイトレベルで解説。
歴史的背景: 2つのエンコーディングが分岐するまで
Shift_JIS は 1982 年にアスキー社とマイクロソフトの協力により、パーソナルコンピュータで日本語を扱うために作られました。既存の制御コードとの衝突を避けつつ、1 バイトの ASCII 互換文字と 2 バイトの JIS X 0208 文字を巧みに混在させる設計です。
マイクロソフトは MS-DOS と Windows に Shift_JIS を採用しましたが、ユーザーの要望に応じて徐々に独自の文字を追加していきました。この拡張版は内部的に「コードページ 932」(CP932)と呼ばれ、後に IANA で Windows-31J として標準化されました。問題は、マイクロソフトが独自拡張と元の規格を明確に区別しなかったことで、「Shift_JIS」という名前が曖昧になりました。
| 名称 | 標準化団体 | ベース | 拡張 |
|---|---|---|---|
| Shift_JIS | JIS (JIS X 0208) | JIS X 0201 + JIS X 0208 | なし |
| CP932 / Windows-31J | Microsoft / IANA | Shift_JIS | NEC特殊文字、NEC選定IBM拡張、IBM拡張 |
| Shift_JIS-2004 | JIS (JIS X 0213) | JIS X 0201 + JIS X 0213 | 第四水準漢字 |
実際には、ソフトウェアが「Shift_JIS」を使用すると主張する場合、ほぼ確実に CP932 を意味します。JIS 規格準拠の純粋な Shift_JIS は現実ではほとんど見かけません。これがエンコーディング検出と変換における恒常的な混乱の原因です。
区(Row)のカバー範囲比較
Shift_JIS と CP932 は基本構造を共有しています: 1 バイト文字(0x00-0x7F と半角カタカナ用 0xA1-0xDF)に加え、JIS X 0208 の「区」と「点」に配置された 2 バイト文字。違いは、どの区が使用されているかです:
| 区 (ku) | Shift_JIS (JIS X 0208) | CP932 (Windows-31J) |
|---|---|---|
| 1-8区 | 記号・数字・ラテン・かな | 同じ |
| 9-12区 | 未割り当て | 未割り当て |
| 13区 | 未割り当て | NEC特殊文字(①②③ など) |
| 14区 | 未割り当て | 未割り当て |
| 15区 | 未割り当て | 未割り当て |
| 16-84区 | JIS第一・第二水準漢字 | 同じ |
| 85-88区 | 未割り当て | 未割り当て |
| 89-92区 | 未割り当て | NEC選定IBM拡張文字 |
| 93-94区 | 未割り当て | 未割り当て |
| 95-120区 | JIS X 0208 に存在しない | IBM拡張文字(ユーザー定義領域) |
核心部分である 1-8 区と 16-84 区は両エンコーディングで完全に同一です。分岐は「隙間」— JIS X 0208 が未割り当てのまま残した区にマイクロソフトがベンダー拡張を埋めた部分 — で生じます。日常的な日本語テキストの大部分が両エンコーディングで同じように動作する一方、厳密な Shift_JIS デコーダが CP932 専用文字に遭遇するとエラーになる理由はここにあります。
NEC 特殊文字(13区)
13 区は CP932 の拡張の中で最も有名(悪名高い)です。NEC が 1980 年代に PC-9801 シリーズ向けに導入し、マイクロソフトが CP932 に取り込みました。丸数字、ローマ数字、単位記号など、よく要望された文字を含みます:
| バイト範囲 | 文字 | Unicode マッピング |
|---|---|---|
| 0x8740-0x875D | ①②③...⑳ | U+2460-U+2473 |
| 0x875F-0x8775 | Ⅰ Ⅱ Ⅲ ... Ⅹ ⅰ ⅱ ⅲ ... ⅹ | U+2160-U+2179 |
| 0x8780-0x878F | ㍉ ㌔ ㌢ ㍍ ㌘ ㌧ ㌃ ㌶ 等 | U+3349, U+3314, ... |
| 0x8790-0x879C | ㍻ ㍼ ㍽ ㍾(元号) | U+337B-U+337E |
これらの文字は標準 Shift_JIS には存在しません。①(丸数字1、バイト 0x8740)を含む文書は CP932 では完全にデコードできますが、厳密な Shift_JIS デコーダではエラーまたは置換文字になります。Windows と Unix/Mac 間で日本語テキストを交換する際のエンコーディング問題の最大原因がこれです。
丸数字は特に問題になりやすい文字です。日本のビジネス文書、法律文書、日常的な文章で広く使われているためです。これらの「基本的」に見える文字が実はベンダー拡張であることを知って驚くユーザーは少なくありません。
IBM 拡張と重複問題
CP932 は IBM 由来の文字を 2 系統含んでおり、これがユニークな問題を生みます: 一部の文字が、同じ Unicode コードポイントにマッピングされる異なる 2 つのバイト列を持つのです。
| 出典 | 区 | 文字数 | 例 |
|---|---|---|---|
| NEC選定IBM拡張 | 89-92区 | 約374文字 | 髙 﨑 (NEC: 0xEEEF, 0xEEFC) |
| IBM拡張 | 115-119区 | 約388文字 | 髙 﨑 (IBM: 0xFBFC, 0xFBF2) |
髙(タカ、「高」の異体字)は CP932 において NEC 系のバイト列でも IBM 系のバイト列でもエンコードできます。どちらも Unicode では U+9AD9 にデコードされます。しかし、Unicode から CP932 に逆変換する際、エンコーダはどちらか一方を選ぶ必要があります。マイクロソフトはラウンドトリップ互換性のため NEC 系を優先しましたが、他の実装は異なる選択をする場合があります。
この重複は微妙なバグを引き起こします: バイトレベルの文字列比較では、同一テキストを含む 2 つの CP932 文字列が「異なる」と判定される可能性があります。ハッシュベースの検索、重複排除、二分探索が壊れる原因になります。解決策は、比較時には必ず Unicode に変換し、CP932 のバイト列を直接比較しないことです。
// 重複エンコーディング問題: // 髙 (U+9AD9) の CP932 表現: // NEC 89区: 0xEEEF // IBM 115区: 0xFBFC // どちらも有効な CP932 で、同じ Unicode 文字にマッピング。 // // バイト比較: 0xEEEF ≠ 0xFBFC → 「異なる」 // Unicode 比較: U+9AD9 === U+9AD9 → 「同じ」 // // 比較は必ず Unicode に変換してから!
WHATWG の現実: Web では Shift_JIS = CP932
Web ブラウザの文字エンコーディング処理を規定する WHATWG Encoding Standard は、実用的な判断を下しました: 「Shift_JIS」というラベルを Windows-31J(CP932)デコーダのエイリアスとして扱うのです。Web ページが charset=Shift_JIS を宣言すると、ブラウザは CP932 のルールでデコードします。
| HTML のラベル | 実際に使用されるデコーダ | 規格 |
|---|---|---|
| Shift_JIS | Windows-31J (CP932) | WHATWG |
| shift_jis | Windows-31J (CP932) | WHATWG |
| windows-31j | Windows-31J (CP932) | WHATWG |
| csshiftjis | Windows-31J (CP932) | WHATWG |
| ms_kanji | Windows-31J (CP932) | WHATWG |
| x-sjis | Windows-31J (CP932) | WHATWG |
つまり Web 上では、Shift_JIS と CP932 の区別は事実上消滅しています。上記 6 つのラベルはすべて同じデコーダを呼び出します。WHATWG がこの選択をしたのは、Web 上の「Shift_JIS」コンテンツのほぼすべてが実際には CP932 であり、厳密な Shift_JIS デコーダを使うと NEC 特殊文字を含む数百万のページが壊れるためです。
ただし、この Web 中心の統一はすべてに適用されるわけではありません。メール(MIME)、プログラミング言語、データベースシステムでは今でも両者を区別する場合があります。Python の shift_jis コーデックは cp932 コーデックより厳密です。Java の Shift_JIS は JIS X 0208 にマッピングされ、MS932 は CP932 にマッピングされます。ブラウザ外でデータを処理する際には、この違いが重要です。
# Python でのエンコーディング動作の違い:
text = "①" # 丸数字1 (U+2460)
# CP932: 問題なし
text.encode('cp932') # b'\x87\x40'
# 厳密な Shift_JIS: エラー!
text.encode('shift_jis') # UnicodeEncodeError
# Java の場合:
# Charset.forName("Shift_JIS") → JIS X 0208 ベース
# Charset.forName("MS932") → CP932 / Windows-31JUnicode マッピングの相違
Shift_JIS と CP932 の両方に同じ文字が含まれている場合でも、異なる Unicode コードポイントにマッピングされることがあります。最も有名な例が波ダッシュ問題です:
| JIS 文字 | JIS X 0208 → Unicode | CP932 → Unicode | 違い |
|---|---|---|---|
| 波ダッシュ (1区33点) | U+301C 〜 | U+FF5E ~ | WAVE DASH vs 全角チルダ |
| 双柱 (1区34点) | U+2016 ‖ | U+2225 ∥ | DOUBLE VERTICAL LINE vs PARALLEL TO |
| マイナス (1区61点) | U+2212 − | U+FF0D - | MINUS SIGN vs 全角ハイフンマイナス |
| セント記号 (1区81点) | U+00A2 ¢ | U+FFE0 ¢ | CENT SIGN vs 全角セント |
| ポンド記号 (1区82点) | U+00A3 £ | U+FFE1 £ | POUND SIGN vs 全角ポンド |
| 否定記号 (1区76点) | U+00AC ¬ | U+FFE2 ¬ | NOT SIGN vs 全角否定 |
| ダッシュ (1区29点) | U+2014 — | U+2015 ― | EM DASH vs HORIZONTAL BAR |
これら 7 つのマッピング不一致(総称して「波ダッシュ問題」と呼ばれることもある)はラウンドトリップ変換の失敗を引き起こします。テキストを CP932 から Unicode に変換し、さらに JIS 規格準拠の Shift_JIS に変換する(またはその逆)と、これらの文字が別の文字に変わってしまいます。波ダッシュ(〜 vs ~)は最も目立ち、最も頻繁に遭遇します — 日本語の価格帯、日付、表現に無数に使われています。
根本原因は、マイクロソフトと JIS 委員会が同じ見た目の文字に対して独立に異なる Unicode コードポイントを選択したことです。どちらの選択も「間違い」ではなく、異なるマッピング哲学を反映しています。マイクロソフトは全角互換形式を好み、JIS は意味的に正確なコードポイントを好みました。