javascript Base64 と UTF

MDNで『あの「Unicode の問題」』とも言われている問題。Base64はバイトコードのエンコードを目的としているため、8bitコード以外では相手にしない問題。

つまり現代の文字列のように1文字が16bitから32bitもあると、オーバーフローする話。

Base64が単純に受け付けるバイトにこだわらず、ビット数を無視して受け取った端から変換してもらっても良いような気がするけど。そうは仕様が変わらないようで、しょうがない。

有難いことにマルチバイト文字の問題はUTFの時代になって世界中で考えてもらっている。CJKだけではなかなか。

結局javascriptの大本山的MDNを見てみると、結論が無いものの「汚く」「高速」に動くコードを見つけた。

"use strict";

/*\
|*|
|*|  Base64 / binary data / UTF-8 strings utilities (#3)
|*|
|*|  https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|*|
|*|  Author: madmurphy
|*|
\*/

function btoaUTF16 (sString) {

	var aUTF16CodeUnits = new Uint16Array(sString.length);
	Array.prototype.forEach.call(aUTF16CodeUnits, function (el, idx, arr) { arr[idx] = sString.charCodeAt(idx); });
	return btoa(String.fromCharCode.apply(null, new Uint8Array(aUTF16CodeUnits.buffer)));

}

function atobUTF16 (sBase64) {

	var sBinaryString = atob(sBase64), aBinaryView = new Uint8Array(sBinaryString.length);
	Array.prototype.forEach.call(aBinaryView, function (el, idx, arr) { arr[idx] = sBinaryString.charCodeAt(idx); });
	return String.fromCharCode.apply(null, new Uint16Array(aBinaryView.buffer));

}

いいじゃんこれ。自己使用だけだし。またこの前段に正しいコードも書いてある。本当はjavascriptで正式にサポートしてほしいところだけどね。

var myString = "🂡🃅🚄🚦🚨😁〠〒〄〶";

/* Part 1: `myString` をネイティブの UTF-16 を使って Base64 にエンコードする */

var sUTF16Base64 = btoaUTF16(myString);

/* 出力を表示する */

alert(sUTF16Base64); // "PNih3DzYxdw92ITePdim3j3YqN492AHeIDASMAQwNjA="

/* Part 2: `sUTF16Base64` を UTF-16 にデコードする */

var sDecodedString = atobUTF16(sUTF16Base64);

/* 出力を表示する */

alert(sDecodedString); // "🂡🃅🚄🚦🚨😁〠〒〄〶"

6ビットに変換し余りを0で埋める事について

少し気になったのはbase64の仕様で8bitを6bitに変換し余ったら0で埋めてしまうという事について。

考えるとなんだかバイト数が増える気がするので考えてみた。

結論から言えば切り捨てる事で問題ないという事。車輪の大発明だけど。

8bitバイト数総ビット数6bitバイト数余り
1812
21624
32440
43252
54064
64880

入力8bitでそれより少ない6bitに変換し、余った0を切り捨てることに問題ないという事。

何故なら余りは絶対に4を超えない。

デコードして8bitで割り切れないところにはどうせ足された0しかないという。

base64でエンコードしてデコードしたら余計な0がくっついてくる心配は無いのだ。