2進数変換

Binary
10 1010
Octal
52
Decimal
42
Hexadecimal
2a

2の補数

8-bit
0010 1010
16-bit
0000 0000 0010 1010
32-bit
0000 0000 0000 0000 0000 0000 0010 1010
64-bit
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 1010

入力した内容が保存・記録・共有されることはありません。

数値を2進数・8進数・10進数・16進数の間で変換できます。テキストとバイナリの相互変換も可能です。64ビットを大きく超える値でも正確さを保ちます。

使い方

  1. 入力する基数を選び、値を入力します。
  2. 他のすべての基数が同時に表示され、下部に2の補数も表示されます。
  3. 「テキスト」モードでは文字列とバイナリを相互変換できます。

計算が正確な理由

ここでの変換はすべて、サイズの上限も丸めもないBigIntで行っています。実装上の補足のように聞こえますが、実はこれが、Web上の多くの変換ツールとこのツールを分ける最大の点です。

JavaScriptの数値はIEEE 754の倍精度浮動小数点数で、整数を正確に表せるのは9,007,199,254,740,991(2の53乗から1を引いた値)までです。素直に書いた変換ツールは、それ以下では正しく、それを超えると黙って間違えます。これはたちの悪い失敗の仕方です。エラーも警告もなく、長さも形も正しいのに、答えだけがたまたま正しくない結果が返ってきます。

この境界は、しょっちゅう超えてしまうほど低いものです。64ビットの値全体ははるかに上ですし、Snowflake方式で生成したデータベースID、ナノ秒のタイムスタンプ、ハードウェアレジスタのダンプ、ハッシュ値、64ビットのメモリアドレスから読み出した値もそうです。わざわざ変換したくなるほど大きな数値こそ、普通の変換ツールが信頼できなくなる領域なのです。

2の補数

4つの基数の下には、その値を符号付きの8、16、32、64ビット整数として格納した場合の表現を表示しています。これは2進数の前にマイナス記号を付けるのとは違い、その違いこそがメモリを読むときに人がつまずく点です。

2の補数では、負の数は絶対値のすべてのビットを反転して1を足すことで作ります。8ビットでは、-1は11111111、-128は10000000です。取り除くべき独立した符号ビットはなく、負のゼロもなく、符号を特別扱いしなくても加算がそのまま機能します。主要なプロセッサーがすべてこの方式で整数を格納しているのはそのためです。

範囲が非対称になる理由もこれで説明できます。8ビットで表せるのは-128から128ではなく-128から127で、ゼロが正の側の枠を1つ使っているからです。値が収まらないビット幅では、黙って切り詰めるのではなくそう伝えます。切り詰めた結果は完全に正しそうに見えて、実は正しくないからです。

出力の読み方

長い値はひと目で読めるよう区切って表示します。2進数は8桁(1バイト)ごと、16進数は2桁ごと、10進数は3桁ごとです。区切りは表示上のものだけで、コピーする内容からは取り除かれます。

入力も同じ方向に、あえて寛容にしています。0x、0b、0oの接頭辞は受け付けて無視し、区切りとして使われたアンダースコア、スペース、カンマも同様です。そのため、ソースコードや16進ダンプからそのまま貼り付けたリテラルも、事前に整えなくても使えます。

テキストから2進数へ

テキストモードでは、テキストをUTF-8でエンコードし、各バイトを8ビットで書き出します。逆方向では、ビットをバイトとして読み戻し、UTF-8としてデコードします。

もう1つのやり方、つまり各文字のコードユニットをそのまま使う方法は、多くのテキスト→2進数変換ツールが採用していますが、2か所で破綻します。U+00FFを超える文字は8ビットを超えるので、もはやバイト単位にそろいません。さらに悪いことに、ほとんどの絵文字を含む基本多言語面(BMP)外の文字は、JavaScriptでは2つのサロゲートコードユニットとして格納されます。それぞれを個別に扱うと意味のない半分ずつになり、受け取る側では何にもデコードされません。UTF-8を経由すればこの両方の問題を避けられます。ここでは絵文字が往復できて、ほかではできないことが多いのはそのためです。日本語もここでは問題なく往復します。

1つの数を4通りに書く

2,025という数を考えます。2進数では11111101001、8進数では3751、16進数では7E9です。量そのものは何も変わらず、書き表すときの基数が変わっただけです。どの基数でも各桁は位の値を表します。7E9は7×256+14×16+9であり、11111101001はビットが立っている2のべき乗の合計です。

16進数が重宝されるのは、16が2の4乗であるため、1桁がちょうど4ビットに対応し、対応関係も覚えやすいからです。Fは1111、8は1000、Aは1010です。メモリダンプ、色の値、ハッシュ、MACアドレスが10進数ではなく16進数で書かれるのはこのためで、計算をしなくても桁からビットがそのまま読み取れます。

8進数が生き残っているのはほぼ1か所、Unixのファイルパーミッションです。理由は同じで、1桁が3ビットに対応し、読み取り・書き込み・実行にぴったり当てはまるので、755と書けばrwxr-xr-xであり、何も調べる必要がありません。

ビット演算と、マスクの役割

値を2進数で捉えると、ビット演算子の意味は明快です。ANDは両方で立っているビットだけを残すので、フラグの判定に使えます。value AND 0b0100が0でないのは、3番目のビットが立っているときだけです。ORはビットを立て、XORは反転させ、NOTはすべてを反転し、左右のシフトは2のべき乗の掛け算と割り算になります。

フラグを個別の真偽値ではなく1ビットずつ格納するのはこのためです。パーミッションのフィールド、機能のオン・オフ、ネットワークパケットのオプションを1つの整数に収められ、1つを確認するのは命令1つで済みます。ANDに使う定数をマスクと呼び、これを2進数や16進数で書くと、10進数では決して見えない意図が見えるようになります。

シフトには覚えておきたい落とし穴が1つあります。多くの言語では符号付きの値の右シフトは符号ビットを維持するので、-8を右シフトすると大きな正の数ではなく-4になります。両方を用意している言語では別の記法を使い、JavaScriptの>>>演算子がその例です。

数値はメモリにどう置かれるか

7E9と書かれた値からは、それがどう格納されているかはわかりません。幅が重要です。同じ数でも型によって1、2、4、8バイトのいずれかを占め、幅に収まらない値は大きくなるのではなく切り詰められるか、循環します。

バイト順も重要です。ほぼすべての身近な機械であるリトルエンディアンでは、最下位バイトを先に格納するので、32ビット値0x000007E9は16進ダンプではE9 07 00 00と現れます。ビッグエンディアンはその逆で、ネットワークプロトコルが規定しているのはこちらです。ネットワークバイトオーダーと呼ばれ、ソケットの境界ごとに変換関数が存在するのはそのためです。どちらを見ているかを知らずにダンプを読むと、自信たっぷりの誤読が生まれます。

同じ幅の符号なし型と符号付き型は、同じビットを別の意味で解釈します。0xFFは符号なしバイトなら255、符号付きなら-1です。どちらが正しいかはバイト列のどこにも書かれておらず、決めているのは型だけです。

浮動小数点はまったく別の仕組み

ここまでの話はすべて整数のものです。倍精度浮動小数点数は、符号ビット1つ、11ビットの指数、52ビットの仮数として格納され、数を「分数×2のべき乗」として表します。その形で正確に表せない値は、表せる中で最も近いものに丸められます。

0.1と0.2を足しても0.3にならないという有名な結果の説明は、これがすべてです。10分の1は2進数では循環小数になり、3分の1が10進数で循環するのと同じことで、わずかにずれた値として格納されます。わずかにずれた値どうしを足せば、わずかにずれた結果になります。計算は正しく、間違っているのは10進の小数が2進の格納を生き延びるという思い込みのほうです。

そこから実務上の原則が出てきます。金額を浮動小数点数で保持しないこと。整数のセントか10進型を使うこと。浮動小数点数は等価ではなく許容誤差で比較すること。そして倍精度では2の53乗を超える整数が精度を失うこと。この変換ツールが任意精度の演算を使っているのは、まさにそのためです。

これらの基数と実際に出会う場所

16進数は、バイト列を人に見せるところならどこにでも現れます。CSSの色は3バイトの16進数、MACアドレスは6バイト、SHA-256のダイジェストは32バイトで64文字として書かれます。メモリアドレス、OSやハードウェアのエラーコードも16進数です。ビットの境界が桁の境界と一致するからです。

2進数は、1ビットずつに意味があるところに現れます。ネットマスク、ハードウェアレジスタ、パーミッションビット、機能フラグ、そしてQRコードやUTF-8のような形式の符号化表です。UTF-8を読むのは良い例です。0で始まるバイトはASCII、110で始まるバイトは2バイト列の先頭、10で始まるバイトは継続バイトです。この構造は10進数では見えず、2進数では一目瞭然です。

8進数はファイルパーミッションといくつかの古い形式にほぼ退きましたが、覚えておきたい罠が1つ残っています。C、ES5より前のJavaScript、いくつかの設定形式では、先頭にゼロのある数値は8進数と解釈されるため、0755は755ではありません。現在のJavaScriptは明示的な0o接頭辞を使い、YAML 1.2は先頭ゼロの規則そのものを廃止しました。これはこれで、ファイルモードが10進数として読まれるという別の驚きの原因になっています。

よくある質問

非常に大きな数でも正確ですか?
はい。計算にはBigIntを使用しており、桁数にかかわらず正確です。通常のJavaScriptの数値を使う変換ツールは2^53(約9000兆)を超えると精度を失い、もっともらしいが誤った答えを返します。64ビットの値はすでにこの上限を超えているため、この違いは想像以上によく問題になります。
2の補数とは何で、なぜ表示するのですか?
符号付き整数がメモリ上で実際に格納される形式です。8ビットの2の補数では、-1は符号ビット+1ではなく11111111になります。レジスタの内容を読むときやプロトコルをデバッグするときに必要なのは、この格納形式です。値が収まらない幅については、黙って切り捨てず「収まりません」と表示します。
0xや0bの接頭辞付きで貼り付けられますか?
はい。これらの接頭辞は受け付けて無視されます。桁区切りのアンダースコア・空白・カンマも同様なので、ソースコードからそのまま貼り付けられます。
テキストはどのようにバイナリへ変換されますか?
まずUTF-8としてエンコードし、各バイトを8ビットで表記します。そのため日本語・中国語・韓国語・絵文字も正しく往復変換できます。文字コードを直接読むツールではASCII以外が破綻し、絵文字1文字が壊れた2つの断片になります。

最終更新:

問題を報告

操作手順、期待した結果、実際の結果を記載してください。秘密情報や個人情報は削除してください。

関連ツール