進数変換
基数変換ツールは、2進数、8進数、10進数、16進数と、2から36までの任意の基数の間を、ブラウザ内で変換します。整数は倍精度浮動小数点数を経由せず、最初から最後までBigIntで処理されます。parseIntは9007199254740993に対して黙って9007199254740992を返すからです。しかも人が変換する値はID、ビットマスク、ハッシュで、まさにその限界を超える素材です。
上に数値を入力するか例を試すと、2 進数、10 進数、16 進数などに変換します。
進数変換はどう動きますか?
基数変換は1行で書けるように見えて、2箇所でそうはいかなくなります。大きな整数と、小数です。
parseIntは黙って桁を失う
parseInt("9007199254740993", 10)は9007199254740992を返します。例外も警告も出ず、最後の桁がただ消えます——JavaScriptの数値は倍精度浮動小数点数であり、2 53を境に連続する整数が表現できなくなるからです。基数をまたいで変換される対象は、snowflake ID、権限のビットマスク、ハッシュ、メモリアドレスで、そのどれもが日常的にこの限界を超えます。だからこのツールの整数パスは、パースからフォーマットまでBigIntで貫通し、倍精度浮動小数点数を経由するラウンドトリップは決して行いません。
小数は乗算の繰り返しで変換する
BigIntに小数部はないため、小数は別々に処理します。小数部の桁に目標の基数を掛け、桁上がりを次の出力桁とし、これを繰り返します。演算はすべて元の基数の整数のまま行われ、floatを経由するものは何もありません。だから基数3の0.1はここでは0.3333333333333333ではなく、正確に0.1として出力されます。
目標の基数では有限桁で終わらない小数もあります。10進の0.1は2進では0.0001100110011…と無限に繰り返します。0.1 + 0.2 !== 0.3の背景にあるのは、同じ事実です。これを直す方法はないので、展開は40桁で打ち切られます。
基数と食い違う先頭の0xはエラー
基数10を選んだ状態で0x1fを貼り付けるのは、ほぼ確実に基数16のつもりの入力です。0とxを数字として扱うと、混乱を招く失敗か誤った答えのどちらかになるため、この食い違いを明示的に指摘します。桁区切り文字——アンダースコアとスペース——も受け付けます。1010_1010は、人がバイトを実際に書くときの形だからです。
-1がffffffffと表示されるのは2の補数のため
メモリ上の負の整数は、2の補数として格納されます。ビットを反転して1を足すのです。したがって32ビットでは-1はffffffff、-2147483648は80000000です。デバッガで、小さな負の数を期待していた場所に巨大な正の16進数が表示されるとき、これがその正体です。パネルは、その値が収まる最も狭いビット幅でエンコーディングを表示します。
マスクとして使う値の、立っているビット
権限を表す整数はフラグの集合であり、役に立つ問いはどのフラグが立っているかです。ビット位置は、最下位ビットを0として数えて列挙されます。これは、すべてのビットシフト演算が使っている規約です。
入力
255 (base 10)
変換結果
binary 1111 1111 octal 377 hexadecimal ff set bits 0, 1, 2, 3, 4, 5, 6, 7
進数変換が対応するオプションとエッジケースは?
| パラメータ | 型 | デフォルト | 動作とエッジケース |
|---|---|---|---|
| 基数の範囲 | 2〜36 | — | 数字は0-9、次にa-zで、大文字小文字は区別しません。これにより36種類の記号が使用可能です。基数36は、英数字のみを使用する最も高密度なエンコーディングです。 |
| 精度 | 任意精度 | BigInt | 任意の長さの整数は正確に変換されます。parseIntは2^53を超える桁を警告なく失うため、基数変換器における最も一般的なバグの一つです。 |
| 小数部 | 繰り返し乗算 | 最大40桁 | 小数が対象基数で有限に終わる場合は正確です。そうでない場合(例:10進数の0.1を2進数に変換)は、展開は無限ではなく境界値で止まります。 |
| 区切り文字 | _とスペース | 受け付け | 1010_1010と1010 1010の両方が解析されます。これらを拒否するのは形式的すぎます。人々はバイトをこう書くものです。 |
| 接頭辞 | 0b 0o 0x | 検証 | 選択した基数と一致する場合に受け入れられ、一致しない場合はエラーとして報告されます。基数10が選択されている状態で0x1fを貼り付けるのは、名前を付ける価値のある間違いです。 |
| 桁区切り | 真偽値 | on | 2進数では4桁ごと、8進数と10進数では3桁ごと、16進数では2桁ごとです。これらは各基数の読み方に一致する区切り方です。 |
| 2の補数 | 8/16/32/64-bit | 最小のビット幅 | 負の値が実際にどのように格納されるかであり、これが-1がデバッガでffffffffとして表示される理由です。 |
| ビット幅 | 符号あり/なし | 表示 | int8からuint64のどの型がその値を保持できるか。値がint32には収まるがint16には収まらないというのは、列のデータ型を選択する前に知っておくべきことです。 |
よくある質問
長い数値で他のコンバーターと結果が違うのはなぜですか?
相手はparseIntかNumberを使っており、どちらも倍精度浮動小数点数を経由するため、2^53——約9,007兆——を超えると精度を失います。parseInt("9007199254740993")はエラーを出さずに9007199254740992を返します。このツールは全体をBigIntで処理するので、すべての桁が生き残ります。snowflake ID、ハッシュ、大きなビットマスクを変換するなら、ここが効いてくる違いです。
0.1が2進数にきれいに変換できないのはなぜですか?
そもそもできないからです。10分の1は2進では0.0001100110011…と永遠に繰り返します。10進で3分の1が0.333…になるのと同じ理由です。分母に、基数が持っていない素因数があるのです。これが、2進浮動小数点を持つすべての言語での0.1 + 0.2 !== 0.3の完全な説明です。このツールの展開が40桁で止まるのは、どこかで止めるしかないからです。
-1がffffffffと表示されるのはなぜですか?
それは2の補数です。事実上すべてのコンピュータが負の整数をこう格納します。絶対値のビットを反転して1を足すのです。符号付きでも符号なしでも加算がまったく同じように動くという好都合な性質があり、それが他の方式に勝った理由です。デバッガで、小さな負の数を期待していた場所に不審なほど大きな正の16進数が表示されたら、目の前にあるのはこれです。
基数36は何のためにあるのですか?
コンパクトな識別子のためです。基数36は0-9とa-zを使います。大文字小文字の正規化に耐え、人が手で打てて、エンコードなしでURLに入れられる最大のアルファベットです。基数36のタイムスタンプは、10進表記の半分ほどの長さになります。基数62はさらに密度が高いものの、大文字小文字を区別するため、小文字化が起こる場面では壊れやすくなります。
浮動小数点数のビットパターンを変換できますか?
直接はできません。このツールが変換するのは数学的な値であって、IEEE 754のエンコーディングではありません。両者は別の問いです。値としての0.5は2進では0.1ですが、倍精度浮動小数点数としての0.5は16進では3FE0000000000000です。ビットパターンが欲しいなら、エンコードされた形式の16進表記を整数として変換してください。
入力した数値はアップロードされますか?
いいえ。ただの演算であり、ブラウザ内で動作します。255を16進数に変換するのにサーバーは必要ありません。しかも人が変換する数値は、他人のログには残したくない内部識別子であることがよくあります。