UNICODEとエンディアンと仕様を整理する。


ひげを剃る。そしてインド人を右に。

エンディアンはインディアンではない。。。

概要

こちらの記事見ていて、少し脱線はするが、。UNICODEの自動判定に疑問を持った。仕様で定義されており、自動判別する必要は無いのではないか、と。

UNICODE 系エンコードの中には UTF-8UTF-16UTF-32、 といった エンディアン 情報を表記に含んでいないものがある。
それらははたして ビッグ エンディアンリトル エンディアンそれとも マスター アジア

UTF-8 にはエンディアン問題が無いため、以下にはほぼ登場しません。

結論

色々と実装依存なものの。
UNICODEの仕様では、デフォルトは ビッグ エンディアン と明記されている。

私は定義されていると思っていたものの、リトル エンディアン のMSはてっきり仕様に則っているものだとばかり。。。orz=3。おのれ、まいくろそふとー・・・・。

補足

ただし、時代的背景を加味すると。いまでこそPCサーバーが当たり前だのクラッカーだけど。少し前までは ホスト だの メインフレーム だのでサーバーで用いられるアーキテクチャが異なっており

  • サーバーでは ビッグ エンディアン
  • PCでは リトル エンディアン

と言う形で二分化していたところもある様子には、まぁ解らないでもない。 MacintoshもCPUが Motorola系PowerPC などで ビッグ エンディアン がメインのみたいだった様だけど、普及率的にもソフト開発が盛んではなかったろうし、あまり意識することは多くなかったろうしね。

また、元々計算機は文字とCPUが密接に関係して進化してきた。

なので、PCは Wintel によって リトルエンディアン で長らく進行していたため、内部UNICODEもリトル エンディアン なのはあまり不自然な話でも無いだろう。デスクトップPC筆頭Wintel系とサーバー UNIX系とで、方針は分かれるべくして分かれたと考えることも出来る可能。

本来バイトとは、8ビット固定ではなく、扱う文字種や、あるいはワードサイズをいくつかに分割することによって決められる、1文字を表現する単位で、古くは直接的に「字」とか「キャラクタ」とも呼ばれていた単位に由来するものである(→「キャラクタマシン」および「ワードマシン」を参照)。歴史的には5ビットから12ビットまで存在したと言われる。DEC PDP-10、NEC ACOS-6など、初期のコンピュータの多くでは6ビット、また7ビットや9ビットとして扱うコンピュータもあった。

比較

リトル エンディアン (非準拠陣営)

恐らくIntel CPUが リトル エンディアン だったこともあり、Wintel系では リトル エンディアン で進んだ様な印象がある。

Windows

16LE (リトルエンディアン用) を使用してエンコードされたワイド文字は、Windows のネイティブ文字形式です。

.NET (CLR)

※ 内部文字コードは UTF-16 ではなく UCS2 です。で、 リトル エンディアン です。

貴様味噌ですが。

(出力結果抜粋)
Encoding.GetEncoding("utf-16") => UnicodeEncoding, 1200, 1200, FF FE (2), Unicode, utf-16, utf-16, utf-16
Encoding.GetEncoding("utf-16be") => UnicodeEncoding, 1201, 1200, FE FF (2), Unicode (Big-Endian), utf-16BE, utf-16BE, utf-16BE
Encoding.GetEncoding("utf-16le") => UnicodeEncoding, 1200, 1200, FF FE (2), Unicode, utf-16, utf-16, utf-16
Encoding.GetEncoding("utf-32") => UTF32Encoding, 12000, 1200, FF FE 00 00 (4), Unicode (UTF-32), utf-32, utf-32, utf-32
Encoding.GetEncoding("utf-32be") => UTF32Encoding, 12001, 1200, 00 00 FE FF (4), Unicode (UTF-32 ビッグ エンディアン), utf-32BE, utf-32BE, utf-32BE
Encoding.GetEncoding("utf-32le") => UTF32Encoding, 12000, 1200, FF FE 00 00 (4), Unicode (UTF-32), utf-32, utf-32, utf-32

UTF-16LE の実態が UTF-16 っぽい・・・
Javaの UTF-16UTF-32 とは エンディアンが逆なので注意が必要。

Javaでは、getBytes("UTF-16")オプションのバイト順マーク付きのビッグエンディアン表現を返します。C#はSystem.Text.Encoding.Unicode.GetBytesリトルエンディアン表現を返します。ここからコードを確認することはできませんが、変換を正確に指定する必要があると思います。

notepad

名称 BOM エンディアン
Unicode あり リトルエンディアン
Unicode big endian あり ビッグエンディアン
UTF-8 あり n/a

UnicodeUTF-16リトル エンディアン だとか。
と言うかさ。 Unicode文字コード (文字集合) だけど エンコード じゃないんだから、曖昧な表現は、いい加減やめてほしい。。。
※ただし、BOMがついているので悪くなくもない

iconv

何故かiconvはこっち。
x86_64 (Hyper-v上のUbuntu 20.04)と、ARM (Raspberry Pi3 + CentOS 7.8-2003) で同じ結果。

console
$ echo hoge | iconv -t UTF-16 | od -tx1
0000000 ff fe 68 00 6f 00 67 00 65 00 0a 00
0000014

$ echo hoge | iconv -t UTF-16LE | od -tx1
0000000 68 00 6f 00 67 00 65 00 0a 00
0000012

$ echo hoge | iconv -t UTF-16BE | od -tx1
0000000 00 68 00 6f 00 67 00 65 00 0a
0000012

$ echo hoge | od -tx1
0000000 68 6f 67 65 0a
0000005

$ echo hoge | iconv -t UTF-8 | od -tx1
0000000 68 6f 67 65 0a
0000005

(おまけ) Intel系 CPU

(引用なし)

ビッグ エンディアン (準拠陣営)

ラインナップを見ていると、サーバー系のプラットフォームでは ビッグ エンディアン が台頭していた印象がある。

UNICODE 12.0.0 (仕様)

そもそも仕様で定義されているんだから、頑張ってそれに合わせる必要が、変革する必要があるよなぁ。。。。

UTF-16の場合はBOMでエンディアンを明示するか、上層のプロトコルで指定されておらずBOMも付与しない場合はビッグエンディアンにするよう決められている[1]。

The UTF-16 encoding scheme may or may not begin with a BOM. However,
when there is no BOM, and in the absence of a higher-level protocol, the byte order of the UTF-16 encoding scheme is big-endian.

UTF-16符号化フォームは、WindowsやJava(J2SE 5.0以上)で、内部表現に使われている。Windowsの内部表現では16ビット符号なし整数を符号単位とするUTF-16符号化フォームとして扱い、ファイルなどではBOMありのUTF-16符号化スキーム(リトルエンディアン)が主である。

RFC 2781 (仕様)

RFC 2781 ではBOMが付いていないUTF-16文書はビッグエンディアンとして解釈することになっている。Windowsのメモ帳で作成した「Unicodeテキスト」はBOMが付与されるようになっている。ビッグエンディアンの符号化方式をUTF-16BE、リトルエンディアンの符号化方式をUTF-16LEとして区別することもある

4.3 Interpreting text labelled as UTF-16

Text labelled with the "UTF-16" charset might be serialized in either big-endian or little-endian order. If the first two octets of the text is 0xFE followed by 0xFF, then the text can be interpreted as being big-endian. If the first two octets of the text is 0xFF followed by 0xFE, then the text can be interpreted as being little-endian. If the first two octets of the text is not 0xFE followed by 0xFF, and is not 0xFF followed by 0xFE, then the text SHOULD be interpreted as being big-endian.

4.3解釈テキストはUTF-16としてラベル
「UTF-16」の文字セットで標識したテキストは、ビッグエンディアンかリトルエンディアン順のいずれかでシリアル化される可能性があります。テキストの最初の2つのオクテットが0xFEのが0xFFが続いている場合は、テキストはビッグエンディアンであると解釈することができます。テキストの最初の2つのオクテットが0xFFが0xFEのが続いている場合は、テキストはリトルエンディアンであると解釈することができます。テキストの最初の2つのオクテットが0xFEのが0xFFが続かない、とは0xFFが0xFEのが続いていない場合、テキストはビッグエンディアンであるとして解釈されるべきです。

Java VM

※ 内部文字コードは UTF-16 ではなく UCS2 です。で、 ビッグ エンディアン です。

デコードの際、UTF-16 文字セットは入力ストリームの最初のバイト順マークを解釈してストリームのバイト順を決定するが、バイト順マークがない場合はビッグエンディアンバイト順を使用する。エンコードの際は、ビッグエンディアンバイト順を使用し、ビッグエンディアンバイト順マークを書き込む。

多分、Javaが ビッグ エンディアン なのは SPARC に関係してる気がする。。。

nkf

option (抜粋)
-w16 -w16B0
UTF16 コードを出力する。 (Big Endian / BOM 無し)

TCP/IP

・UTF-16 - Wikipedia #利用
https://ja.wikipedia.org/wiki/UTF-16

UTF-16符号化フォームは、WindowsやJava(J2SE 5.0以上)で、内部表現に使われている。Windowsの内部表現では16ビット符号なし整数を符号単位とするUTF-16符号化フォームとして扱い、ファイルなどではBOMありのUTF-16符号化スキーム(リトルエンディアン)が主である。

TCP/IPネットワークでは、プロトコルヘッダやMIME等の手段で文字符号化スキームを指定しない場合はビッグエンディアンに決められている。

・Windows ソケット : バイトの順序付け | Microsoft Docs
https://docs.microsoft.com/ja-jp/cpp/mfc/windows-sockets-byte-ordering?view=msvc-160

異なるコンピューターアーキテクチャでは、異なるバイトオーダーを使用してデータを格納する場合があります。 たとえば、Intel ベースのコンピューターでは、Macintosh (Motorola) コンピューターの逆の順序でデータが保存されます。 "リトルエンディアン" と呼ばれる Intel のバイトオーダーも、ネットワークの標準の "ビッグエンディアン" の順序と逆になります。

(おまけ) Motorola系 CPU

680×0、PowerPCやSuperHなど多くは、最上位バイトが先に配置されるビッグエンディアンである。

(おまけ) PowerPC

680×0、PowerPCやSuperHなど多くは、最上位バイトが先に配置されるビッグエンディアンである。

PowerPC お前もか。。。

(おまけ) SuperH

680×0、PowerPCやSuperHなど多くは、最上位バイトが先に配置されるビッグエンディアンである。

(おまけ) SPARC

SPARCは、完全ビッグエンディアンのRISCマイクロプロセッサ命令セットアーキテクチャで...

気になりごと

① 同じLinuxでもiconvとnkfで結果が異なる。

console
$ echo hoge | iconv -t UTF-16 | od -tx1
0000000 ff fe 68 00 6f 00 67 00 65 00 0a 00
0000014

# -w16 == UTF16
$ echo hoge | nkf -w16 | od -tx1
0000000 fe ff 00 68 00 6f 00 67 00 65 00 0a
0000014

なんでiconvは リトル エンディアン なんだろう。

結局つまりはどういうこと?

  • UTF-16UTF-32 と言うエンディアン表記のない場合には、 ビッグ エンディアン でも リトル エンディアン でも、どっちでも問題はない。
  • しかし、BOMや上流のプロトコルや仕様などで決まっていない場合、デフォルトでは ビッグ エンディアン と仕様で定めている。
  • ってことは。エンディアン でのトラブルっていうのは ソフトウェア のせいではなく、大抵が 人間同士の疎通ミス が原因。
    • 「なんでエンディアン情報を交換してないんだよ!」 「なんで仕様を確認してないんだよ!」 って言う話 。
    • iconv の場合は リトル エンディアン を名言する仕様が見当たらないので、iconv の不具合と言えなくもないかも。
  • イメージつかない人は HTTP (80) とか HTTPS (443) とかでイメージすると解りやすいかも。
    (明記して自由にしていいけど、暗黙のデフォルトの解釈も仕様で決められているよね。)

明日の未来のために

you get big-endianness!

参考 (謝辞)