Shift_JISとCP932

## はじめに

[Unicode - 恩恵と厄介事](https://qiita.com/chai0917/items/16fa57fc3078c5314d8d) で、軽く触れた Shift_JIS（シフトJIS、SJIS） と CP932 について、少し詳しくまとめてみようと思います。

### 参考情報
下記情報を参考にさせて頂きました。

- [とほほの文字コード入門](https://www.tohoho-web.com/ex/charset.html)
- [小形克宏の「文字の海、ビットの舟」――文字コードが私たちに問いかけるもの](https://internet.watch.impress.co.jp/www/column/ogata/)
- [Shift_JIS と Windows-31J (MS932) の違いを整理してみよう](https://weblabo.oscasierra.net/shift_jis-windows31j/)
- [IBM 拡張文字・NEC 選定 IBM 拡張文字・NEC 特殊文字 - Mr.XRAY](http://mrxray.on.coocan.jp/Delphi/Others/IBM_NEC_Kanji.htm)


## Shift_JIS

まずは、標準規格に基づく情報として整理します。

### JIS X 0208

Shift_JIS は、JIS X 0208 の付属書で「シフト符号化表現」という名称で定義されています。

符号化文字集合 - JIS X 0201、JIS X 0208 を、SI/SO（シフトイン/シフトアウト）などの文字種切り替え制御コードを用いることなく、使い勝手良く配置した文字符号化方式が Shift_JIS です。
Shift_JIS では、JIS X 0208 は２バイトコード、JIS X 0201 は１バイトコードで表現されます。

ビットマップで等幅フォントが一般的だった時代、JIS X 0208 フォントサイズは正方形で、JIS X 0201 - ASCII/カタカナは漢字に対して横幅が半分にデザインされていました。
この横幅の比率から、JIS X 0208 を全角、JIS X 0201 を半角と呼んでいました。
また、全角を 2、半角を 1 とした値の指標を「文字幅」として利用していました。

Shift_JIS は、下記関係が成り立つので、日本語処理として使い勝手が良い文字符号化方式となっています。

```
文字列のバイト長 = 文字幅
```


### JIS X 0213

JIS X 0208（非漢字、第一水準漢字、第二水準漢字）収録文字不足の解消を目的として、JIS X 0212（補助漢字）を制定しましたが、Shift_JIS では符号化方式の制約により JIS X 0212 は利用できませんでした。

パーソナルユースで絶大な地位を占めていた Windows （Shift_JIS ベースの CP932）で利用できないこともあり、JIS X 0212 は、広く利用されることはありませんでした。

::: note
JIS X 0212 は、UNIX系で利用されていた文字符号化方式 EUC で、下記のように利用されています。

- JIS X 0201 - ASCII は、１バイトコード
- JIX X 0208 は、２バイトコード
- JIS X 0201 - カタカナは、冒頭に制御文字 SS2（0x8E）を付与して２バイトコード
- JIS X 0212 は、冒頭に制御文字 SS3（0x8F）を付与して３バイトコード

SI/SO は、文字種切り替えシーケンスでしたが、SS（シングルシフト）は、直後１文字にのみ有効な切り替えコードとなっています。
:::

そこで、Shift_JIS を念頭に置いて、収録文字の拡大をはかった規格が JIS X 0213 です。
JIS X 0213 は、JIS X 0208 に対して、非漢字、第三水準漢字、第四水準漢字を追加し、Shift_JIS-2004 として利用可能とした規格となっています。

このような目的があるので、JIS X 02013 は、符号化文字集合であることに加えて、文字符号化方式 - Shift_JIS-2004 を規定しています。

JIS X 0213 では、CP932 互換性についての多少の配慮もしています。
CP932 では、ベンダー拡張文字（システム外字）として下記が搭載されていました。

- 8740-879E　NEC特殊文字
- ED40-EEFC　NEC認定IBM拡張文字
- FA40-FCFC　IBM拡張文字

JIS X 0213 で追加する非漢字で、「NEC特殊文字」に存在する文字は、CP932 と同一コードにマッピングを行いました。
しかし、JIS X 0213 - 第三水準漢字と「NEC認定IBM拡張文字」、JIS X 0213 - 第四水準漢字と「IBM拡張文字」はコード空間が重複するので、Shift_JIS-2004 では「NEC認定IBM拡張文字」「IBM拡張文字」が利用できなくなります。

当時、大きな話題となった JIS X 0213 ですが、下記理由で、Shift_JIS-2004 としては、広く利用されることはありませんでした。

- Unicode という存在があり、今更、Shift_JIS で利用文字を拡張する必要性が乏しい
- CP932 との完全互換がとれていない
- 過去の JIS X 0208 で自由領域とされていたコード空間を利用した、ユーザ定義文字（ユーザ外字）が利用不可となる

::: note
符号化文字集合 JIS X 0213 で定義されている一部の文字について、JIS X 0208-1990 定義文字から、字形変更がされています。
フォントファイルとしては、この字形変更に対応した JIS X 0213 準拠が進みました。

標準フォントファイルを JIS X 0213 準拠としたベンダーは、互換性維持のため JIS X 0208-1990 定義字形としたフォントファイルを、JIS90 対応フォントファイルとして提供していました。
:::


## CP932

Microsoft 標準キャラクタセット CP932（MS932, Windows-31J）について記載します。

### 歴史

[Shift_JIS と Windows-31J (MS932) の違いを整理してみよう](https://weblabo.oscasierra.net/shift_jis-windows31j/) で、わかりやすい図を用いて、歴史が説明されています。

> ![CP932-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3963267/dead4ba5-971e-3025-adb5-f29ba517164e.png)
> 1993年にマイクロソフトは Windows 3.1 をリリースするにあたり、独自にコンピュータメーカが CP932 を拡張する事を禁じ、当時コンピュータの大きなシェアを持っていた NEC と IBM が独自拡張した CP932 を統合することにし、この統合した CP932 を Windows の標準日本語コードとすることにしたのです。


### 拡張文字

CP932では、Shift_JIS コード空間の下記エリアに、ベンダー拡張文字が標準搭載されています。
- 8740-879E　NEC特殊文字
- ED40-EEFC　NEC認定IBM拡張文字
- FA40-FCFC　IBM拡張文字

ベンダー拡張文字として、NEC、IBM 双方に対する互換性を配慮したため、「NEC特殊文字 / NEC認定IBM拡張文字」と「IBM拡張文字」では、同一文字が異なるコードに重複配置されています。
例えば「髙 - U+9AD9」は、「IBM拡張文字 - FBFC」「NEC認定IBM拡張文字 - EEE0」に存在します。

Windows で、CP932 ベンダー拡張文字に該当する Unicode を CP932 に変換した場合、基本的には、「NEC特殊文字」に含まれる文字は該当文字コード、それ以外は「IBM拡張文字」の該当文字コードに変換されます。

::: note
上記文面で「基本的には」という文言が存在する理由は、「≒」「≡」などの文字が「NEC特殊文字」に存在しますが、これらの文字は JIS X 0208-1983 で追加されているので、Unicode から CP932 に変換すると、JIS X 0208 側の該当文字コードに変換されるためです。
:::

先ほどの「髙 - U+9AD9」を、CP932 → Unicode → CP932 変換した結果を記載します。
- 「NEC認定IBM拡張文字 - EEE0」→「U+9AD9」→「IBM拡張文字 - FBFC」
- 「IBM拡張文字 - FBFC」→「U+9AD9」→「IBM拡張文字 - FBFC」


### 元号合字

「NEC特殊文字」に、本来なら漢字２文字で表現される元号を、漢字１文字だけで表せる合字（合成文字）として、「㍾」「㍽」「㍼」「㍻」が用意されていました。
[Windows 用の日本の新元号対応更新プログラムについて - KB4469068](https://support.microsoft.com/ja-jp/topic/windows-%E7%94%A8%E3%81%AE%E6%97%A5%E6%9C%AC%E3%81%AE%E6%96%B0%E5%85%83%E5%8F%B7%E5%AF%BE%E5%BF%9C%E6%9B%B4%E6%96%B0%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6-kb4469068-1e760b2f-619a-ab02-723a-5b7e97a916bf) で新元号の合字「㋿」を Windows 標準フォントに追加しましたが、追加された合字は Unicode のみ使用可で、CP932 にはマッピングされませんでした。  

![CP932-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3963267/7062d97d-9119-c3a1-610c-dce5b2979076.png)

### IME

現時点の IME 候補一覧では、Shift_JIS（CP932 拡張文字を除く）にマッピングできない文字について、［環境依存］ と明記されます。

![CP932-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3963267/2644a3ed-4a17-0fbf-bc7b-dc7cbcff48aa.png)

Windows で表示/印刷可能な CP932 拡張文字も［環境依存］と表記される理由は、個別デバイス利用フォントファイルなどで、CP932 拡張文字に対応していない可能性があるためと思われます。

### C# での振舞い

Windows では、基本的に Shift_JIS = CP932 として扱います。
Unicode と CP932 との相互変換は、下記手法などで実施できます。

```CSharp
// .NET では、エンコード Shift_JIS が未登録なので。
// 下記コメントを外して、プロバイダ登録を実施する必要があります
// Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

string hoge = "123ＡＢＣ"; 

// Unicode → CP932
var bytes = Encoding.GetEncoding("Shift_JIS").GetBytes(hoge);

// CP932 → Unicode
var fuga = Encoding.GetEncoding("Shift_JIS").GetString(bytes);
```

https://learn.microsoft.com/ja-jp/dotnet/api/system.text.encoding.getencoding

https://learn.microsoft.com/ja-jp/dotnet/api/system.text.encoding

Unicode には、CP932 にマッピングできない文字が含まれていて、マッピングできない文字を CP932 に変換すると「? - 3F」になってしまいます。
例として、下記表に記載した文字を用いた Unicode を CP932 変換をしてみます。

![CP932-07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3963267/7021ac0b-3ace-5188-cb34-d3a040cdaa48.png)

```CSharp
string hoge = "１Ⅰ⑴"; 
var bytes = Encoding.GetEncoding("Shift_JIS").GetBytes(hoge);
// → 0x82 0x50 0x87 0x54 0x3F
```

- 「U+FF11」は、CP932「8250」に変換（JIS X 0208 文字）
- 「U+2160」は、CP932「8754」に変換（CP932 拡張文字）
- 「U+2474」は、CP932に該当する文字がないので「3F」に変換

### JavaScript での振舞い

JavaScript ライブラリを用いた文字コード変換で、Shift_JIS、CP932 を明確に区別しているか否かは、各ライブラリ仕様に依存します。

Node.js の [iconv-lite](https://www.npmjs.com/package/iconv-lite) については、https://github.com/ashtuchkin/iconv-lite/blob/v0.4.19/encodings/dbcs-data.js#L41-L56 を見ると cp932 や windows31j は shiftjis のエイリアスという扱いになっています。
（shiftjis ＝ cp932 ＝ windows31j で CP932拡張文字を含む）

```javascript
var iconv = require('iconv-lite');
var hoge = "123ＡＢＣ";

// Unicode → CP932
var data = iconv.encode(hoge, 'cp932');

// CP932 → Unicode
var fuga = iconv.decode(data, 'cp932');
```