基数、バイトのサイズ、そして議論の的になる単位

16 進数がビットとどう対応するのか、1 TB のドライブが 931 GB と表示される理由、0.1 と 0.2 の和が実際にはいくつになるのか、そして丸め、パーセント、ロケール書式が数値を静かに変えてしまう仕組みを解説します。

書かれた数値は 2 つのものを運びます。値と、それをどう読むべきかについての前提です。0xFF2550o37711111111 は 1 つの量を 4 通りの記法で書いたものです。テラバイトとテビバイトは 10 パーセント違います。1,234 はロンドンではおよそ 1000 ですが、ベルリンではおよそ 1 です。数値まわりのバグの大半は、計算そのものではなく前提から生まれます。

2 進数、8 進数、10 進数、16 進数

位取り記数法はどの基数でも同じ仕組みです。各桁の重みは 1 つ右の桁の重みに基数を掛けたもので、数字はゼロから基数マイナス 1 までを取ります。16 進数には数字が 6 つ足りないので、10 から 15 に A から F を借りています。

バイトや色に 16 進数が使われるのは、16 が 2 の 4 乗だからです。16 進の 1 桁がちょうど 4 ビット、1 バイトがちょうど 16 進 2 桁に対応し、その間に繰り上がりは発生しません。

0 0000   4 0100   8 1000   C 1100
1 0001   5 0101   9 1001   D 1101
2 0010   6 0110   A 1010   E 1110
3 0011   7 0111   B 1011   F 1111

変換はこの表がすべてです。16 進から 2 進に変えるには、各桁を対応する 4 ビットに置き換えます。0x2F0010 1111 になります。戻すには、右から 4 ビットずつ区切り、左側をゼロで埋め、各グループを表から読み取ります。

10 進に変えるには桁の重みを使います。0x2F2 x 16 + 15 なので 47 です。逆向きには 16 で割り続け、余りを下から上へ読みます。

200 / 16 = 12 remainder 8   -> 8
 12 / 16 =  0 remainder 12  -> C
200 decimal = 0xC8

8 進数は 1 桁に 3 ビットを詰め込みます。Unix のパーミッションが 755 と書かれるのはこのためで、各桁が rwx の 3 つ組 1 つに対応します。71115101 です。#FF8800 のような CSS の色は 3 バイトで、チャンネルごとに 1 バイト、それぞれ 0 から 255 の範囲を取ります。3 桁の省略形 #F80 は、ゼロで埋めるのではなく各桁を 2 つに複製して展開されます。Number Base Converter は 4 つの基数を一度に扱えます。

2 の補数と、範囲の由来

1 バイトは 256 通りのパターンを保持できます。符号なしとして読めば 0 から 255 です。符号付きとして読めば、最上位ビットが負の側を示し、負の値はそのパターンから 256 を引いたものになります。この方式が 2 の補数です。これが選ばれたのは、通常の 2 進の加算が符号付きと符号なしで同一に働くからです。

数の符号を反転させるには、全ビットを反転して 1 を足します。範囲が非対称なのは、ゼロが非負側の 128 個の枠のうち 1 つを占めているからで、符号付きの 1 バイトは -128 から 127 までを取ります。範囲の上端に 1 を足すと、下端に回り込みます。

  0111 1111    127
+ 0000 0001      1
= 1000 0000   -128

ほとんどの言語では何も警告されず、値が範囲の反対側に現れるだけです。32 ビットで同じ回り込みが起きるのが 2038 年問題で、符号付き 32 ビットの Unix タイムスタンプが 2,147,483,647 秒で尽きます。符号なし 16 ビットは 0 から 65,535 を取ります。TCP のポート番号がそこで止まるのはまさにこのためであり、Random Port Generator がこの範囲から、1024 未満の予約ポートを避けつつ選ぶ理由でもあります。

キロバイト、キビバイト、消えたギガバイト

ストレージのベンダーは 10 のべき乗で数えます。オペレーティングシステムは歴史的に 2 のべき乗で数え、その結果に 10 進の接頭辞を付けてきました。論争はここから始まります。2 進接頭辞 (KiB、MiB、GiB) は、この曖昧さを取り除くために存在します。

10 のべき乗10 進の値2 進接頭辞2 進の値
kB, 10^31,000KiB, 2^101,0242.4%
MB, 10^61,000,000MiB, 2^201,048,5764.9%
GB, 10^91,000,000,000GiB, 2^301,073,741,8247.4%
TB, 10^121,000,000,000,000TiB, 2^401,099,511,627,77610.0%
PB, 10^1510^15PiB, 2^501,125,899,906,842,62412.6%

1 TB のドライブは、販売上は 10^12 バイトを保持します。これを 2^30 で割ると 931.32 になるので、Windows は 2 進の量に 10 進のラベルを付けて約 931 GB と報告します。消えたものは何もありません。同じバイト数を別の数で割っているだけです。macOS と多くの Linux のツールは現在 10 進の GB で報告するので、箱に書かれた数字と一致します。メモリのサイズは本当に 2 進なので、16 GB の RAM は実際に 16 GiB です。

ネットワークの速度はバイトではなくビットで数え、常に 10 進です。100 Mbps の回線は毎秒 100,000,000 ビットを運びます。これはオーバーヘッドを差し引く前で毎秒 12.5 MB です。プロトコルのオーバーヘッドが数パーセントを取るので、現実的な上限は毎秒 11 ~ 12 MB です。公称のメガビット値を 8 で割るのが手早い確認方法です。ストレージの単位は Unit Converter が扱います。

浮動小数点数と、金額を浮動小数点数にしない理由

2 進の小数が表せるのは、2 分の 1、4 分の 1、8 分の 1 などの和だけです。10 分の 1 に正確な 2 進表現がないのは、3 分の 1 に正確な 10 進表現がないのと同じです。そのため、IEEE 754 の倍精度として格納された 0.1 は 10 分の 1 よりわずかに大きい値です。こうした近似値を 2 つ足すと、誤差が表面化します。

0.1 + 0.2 = 0.30000000000000004

倍精度は 53 ビットの仮数部を持ちます。そのため 2^53 (9,007,199,254,740,992) までのすべての整数と、分母が 2 のべき乗であるすべての分数は正確に表現できます。2^53 を超えると表現できる値どうしの間隔が 2 になるので、2^53 + 12^53 に等しくなります。JSON でやり取りする 64 ビットのデータベース識別子を文字列として運ぶべきなのは、これが理由です。JavaScript の数値は倍精度なので、19 桁の ID は末尾の桁を黙って失います。

金額については、補助単位 (ペンス、セント) の整数で保存するか、10 進で動作する decimal 型を使ってください。浮動小数点数を等値で比較してはいけません。差を小さな許容値と比較します。Expression Calculator も倍精度で動くので、同じ制約を引き継ぎます。

丸めと、二度の丸め

half up は、ちょうど半分のときにゼロから遠ざかる方向へ丸めます。banker's rounding とも呼ばれる half even は、ちょうど半分のときに最も近い偶数の桁へ丸めます。これにより、half up が長い数値の列全体にもたらす上方向の偏りが取り除かれます。これは IEEE 754 の既定であり、いくつかの会計基準でも規定されています。

half uphalf even
0.510
1.522
2.532
3.544
-0.5-10

丸めは元の値から 1 回だけ行ってください。2.4449 を小数点以下 3 桁に丸めると 2.445 になり、それをさらに 2 桁に丸めると 2.45 になりますが、元の値を直接 2 桁に丸めると 2.44 です。中間の列を経由して丸めを重ねることは、合計が 1 ペニーずれる典型的な原因です。関連して驚かされる例があります。ほとんどの言語で 2.675 は 2.67 に丸められます。格納されている倍精度の値が 2.67499999999999982 だからです。

パーセントとパーセントポイント

4% から 5% に動いた率は、1 パーセントポイント上昇したとも、相対的に 25% 上昇したとも言えます。どちらも正しいので、どちらの単位なのかを明示しなければなりません。

パーセントの変化は打ち消し合いません。50% 下落した後に 50% 上昇しても、残るのは 100 ではなく 75 です。2 回目のパーセントは、より小さい基準に対して取られるからです。p の下落から回復するには p / (1 - p) の上昇が必要なので、50% の下落には 100%、80% の下落には 400% が必要です。連続する変化は掛け算になり、和ではなく等比数列を作ります。Number Sequence ツールがモデル化しているのは、この種の伸びです。

古い値から新しい値への変化率は (new - old) / old x 100 であり、Percentage Calculator が差として報告するのはこの値です。20% の VAT を取り除くというのは、20% を引くことではなく 1.2 で割ることです。規模の異なるグループ間でパーセントを単純に平均すると全体の値を誤ります。加重平均にしなければなりません。Sum and Average が平均値の隣に中央値を示すことで、その偏りが目に見えるようになります。

ロケールに合わせて整形された数値

書式は表示上の判断であり、ロケールを知らなければ元に戻せません。1.234 は英国では 1 とわずかですが、ドイツでは 1000 です。1,234 はその逆になります。区切りに空白やアポストロフィを使うロケールもあります。

ロケール1234567.89
en-GB, en-US1,234,567.89
de-DE1.234.567,89
fr-FR1 234 567,89
de-CH1'234'567.89
en-IN12,34,567.89

インドの桁区切りは、最初の区切りを 3 桁の後に置き、それ以降は 2 桁ごとに置きます。千や百万ではなく、ラークやクロールという単位になるためです。整形済みの文字列を数値に戻す処理が損失を伴うのは、まさにこうした事情によります。ですから値は書式を付けずに、小数点にドットを使って保存および送信し、表示の直前でだけ整形してください。読み手にとって曖昧であってはならない場面では、Number to Words で数を言葉に綴るのが、小切手や契約書での一般的なやり方です。ローマ数字はこの話の外にあります。位取りもゼロもないので、Roman Numeral Converter が扱っているのは基数ではなく数え上げの表記です。