1TBが931GiBになる理由とnumfmtの切り上げ

「1TBのSSDを買ったのに、OSでは931GBしかない」という話は、容量の数え方が2通りあることで説明がつく。メーカーは 1TB = 10^12 バイト(SI 接頭辞)で表記し、OS やコマンドは 1GiB = 2^30 バイト(2進接頭辞)で割った値を出すことが多い。ここまでは広く知られている。

この記事で確かめるのは、その先の細かい点である。GNU coreutils の numfmt・df -h・ls -h は、換算結果を四捨五入ではなく切り上げで丸める。そのため同じ 1TB が 931.33G や 932G と表示され、手計算の 931.32 と1ずれる。--si や --round=nearest を付けたときに結果がどう変わるかを、手元の環境で実測した。

2進接頭辞の定義は NIST の「Prefixes for binary multiples」に載っている。kibi(Ki)=2^10、mebi(Mi)=2^20、gibi(Gi)=2^30 で、1 GiB = 1 073 741 824 B と書かれている。IEC が名前と記号を承認したのは1998年12月で、SI の kilo は1000倍のままである。NIST は2進接頭辞を SI の一部ではないとも明記している。

換算ツールとしては データサイズ単位変換(ハシトシステム) が B/KB/MB/GB/TB(1000基準)と KiB/MiB/GiB/TiB(1024基準)を並べて表示する。計算はブラウザ内で完結する。以下はそのロジックを手元で再現し、コマンド側の挙動と突き合わせる。

接頭辞が大きいほど差が開く

1000基準の量を1024基準で見ると、目減りの割合は接頭辞ごとに次のようになる。

接頭辞 1000基準 1024基準 目減り
K 10^3 2^10 2.34%
M 10^6 2^20 4.63%
G 10^9 2^30 6.87%
T 10^12 2^40 9.05%

TB まで来ると約9%が消えたように見える。1TB は 931.32 GiB、2TB は 1.82 TiB になる。容量が減っているのではなく、同じバイト数を別の物差しで割っているだけである。

実際に試す

前提は Node.js と GNU coreutils(Linux、macOS の Homebrew 版、Windows なら Git Bash)。ここでは Windows 11 の Git Bash(GNU coreutils 8.32)と Node.js v24.11.1、Windows PowerShell 5.1 で実行した。次のファイルを bytes-units.mjs として保存する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// bytes-units.mjs : SI(1000)とIEC(1024)の換算、切り上げと四捨五入の差を確かめる
const SI = ['B', 'KB', 'MB', 'GB', 'TB'];
const IEC = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];

function scale(bytes, base, units) {
let v = bytes, i = 0;
while (v >= base && i < units.length - 1) { v /= base; i++; }
return { v, unit: units[i] };
}
const fmt = ({ v, unit }) => `${v.toFixed(2)} ${unit}`;

for (const [label, bytes] of [['256GB', 256e9], ['500GB', 500e9], ['1TB', 1e12], ['2TB', 2e12]]) {
console.log(label.padEnd(6), fmt(scale(bytes, 1000, SI)).padStart(10), '|', fmt(scale(bytes, 1024, IEC)).padStart(10));
}

console.log('--- 接頭辞ごとの目減り(1000基準の量を1024基準で見たとき)');
SI.slice(1).forEach((u, i) => {
const p = i + 1;
console.log(u.padEnd(3), ((1 - 1000 ** p / 1024 ** p) * 100).toFixed(2) + '%');
});

console.log('--- 切り上げと四捨五入');
const b = 1e12 / 2 ** 30; // 931.3225746...
console.log('実際の値 ', b);
console.log('四捨五入 ', b.toFixed(2));
console.log('切り上げ ', (Math.ceil(b * 100) / 100).toFixed(2));
console.log('Node', process.version);
1
node bytes-units.mjs

出力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
256GB   256.00 GB | 238.42 GiB
500GB 500.00 GB | 465.66 GiB
1TB 1.00 TB | 931.32 GiB
2TB 2.00 TB | 1.82 TiB
--- 接頭辞ごとの目減り(1000基準の量を1024基準で見たとき)
KB 2.34%
MB 4.63%
GB 6.87%
TB 9.05%
--- 切り上げと四捨五入
実際の値 931.3225746154785
四捨五入 931.32
切り上げ 931.33
Node v24.11.1

真の値は 931.3225… なので、小数第2位までの四捨五入は 931.32、切り上げは 931.33 になる。次に、numfmt がどちらで丸めるかを見る。

numfmt は既定で切り上げる

numfmt は数値を人が読みやすい単位に変換するコマンドで、--to=si は1000基準、--to=iec は1024基準で割る。

1
2
3
4
5
echo 1000000000000 | numfmt --to=si
echo 1000000000000 | numfmt --to=iec
echo 1000000000000 | numfmt --to=iec-i
echo 1000000000000 | numfmt --to=iec --format=%.2f
echo 1000000000000 | numfmt --to=iec --round=nearest --format=%.2f
1
2
3
4
5
1.0T
932G
932Gi
931.33G
931.32G
  • --to=iec は 932G を返す。真の値 931.32 を整数に切り上げている。
  • --format=%.2f を付けても 931.33G で、四捨五入の 931.32 にはならない。
  • --round=nearest を付けると 931.32G になる。--round には up・down・from-zero・towards-zero・nearest を指定でき、numfmt --help では from-zero が既定とされている(正の数では切り上げと同じ)。実測では --round=up が 931.33G、--round=down が 931.32G だった。
  • --to=iec の出力に付く単位は G で、GiB ではない。G と書かれていても1024基準である。iec-i にすると Gi と付く。

この切り上げは桁数が少ないほど目立つ。100万バイトは numfmt --to=iec で 977K、--round=nearest --format=%.1f で 976.6K、--to=si --format=%.1f で 1.0M になる。1,000,000 / 1024 = 976.5625 なので、977K は切り上げの結果である。

ls -h と df -h も同じ丸め

ls -l のサイズ欄でも同じことが起きる。1,000,000 バイトのファイルを作って確かめる。

1
2
3
4
5
6
7
d=$(mktemp -d); head -c 1000000 /dev/zero > $d/f
ls -lh $d/f | awk '{print $5}'
ls -l --si $d/f | awk '{print $5}'
ls -l -h --si $d/f | awk '{print $5}'
du -h $d/f
du -h --apparent-size $d/f
rm -rf $d
1
2
3
4
5
977K
1.0M
1.0M
980K
977K
  • ls -lh は1024基準で 977K、ls --si は1000基準で 1.0M。
  • -h と --si を両方付けたときは後に書いた方が効く(-h --si で 1.0M)。
  • du -h は 980K で、ls -lh の 977K と違う。du は既定ではディスク上で確保されたブロック量を数える。ここでは確保量が 980KiB になっている。ファイル本来のバイト数は --apparent-size を付けると出て、977K で ls と一致する。

df -h も1024基準の切り上げ表示である。この環境の C: ドライブは df -B1 で 999069577216 バイト(約 999.07 GB)だが、df -h は 931G と表示した。実際の値は 930.46 GiB で、これも切り上げられている。

単位の接尾辞を読み込む側の違い

数値を読み込む numfmt --from にも罠がある。1.5K が1500なのか1536なのかは、--from に何を指定したかで決まる。

1
2
3
4
echo 1.5K  | numfmt --from=si
echo 1.5K | numfmt --from=iec
echo 1.5K | numfmt --from=auto
echo 1.5Ki | numfmt --from=auto
1
2
3
4
1500
1536
1500
1536

--from=auto では K が1000倍、Ki が1024倍として扱われる。単位の綴りが K で終わる入力は、既定だと1000基準に読まれる点に注意が必要である。

PowerShell の GB は1024基準

Windows PowerShell には KB・MB・GB・TB という数値リテラルの接尾辞があるが、これは1024基準である。この名前は SI の記号と同じなので間違えやすい。

1
2
3
4
5
6
1GB
1TB
[math]::Round(1e12 / 1TB * 1024, 2)
(Get-Volume -DriveLetter C).Size
[math]::Round((Get-Volume -DriveLetter C).Size / 1GB, 2)
[math]::Round((Get-Volume -DriveLetter C).Size / 1e9, 2)
1
2
3
4
5
6
1073741824
1099511627776
931.32
999069577216
930.46
999.07

1GB は 1073741824(2^30)、1TB は 1099511627776(2^40)になる。同じ C: ドライブを /1GB で割ると 930.46、/1e9 で割ると 999.07 で、同じバイト数が別の数字になる。ここで言えるのは PowerShell のリテラルの挙動だけで、エクスプローラーなど他の画面の表示基準はここでは確かめていない。

まとめ

  • 1TB(10^12 バイト)は 931.32 GiB。接頭辞が大きいほど差が開き、TB で約9.05%になる。
  • GNU numfmt は既定で切り上げる。--to=iec は 932G、%.2f を付けても 931.33G。四捨五入にするには --round=nearest を付ける。
  • ls -lh と df -h は1024基準で、--si を付けると1000基準になる。G や K の表示だけでは基準が分からない。
  • du -h はブロック数、ls -l はバイト数を数える。比べるときは du --apparent-size を使う。
  • numfmt --from=auto は K を1000倍、Ki を1024倍と読む。PowerShell の GB は1024基準である。

同じ手元実測の記事として、chmod 755でディレクトリのsetgidが消えない理由 も書いている。

ツール: データサイズ単位変換(ハシトシステム)。B/KB/MB/GB/TB(1000基準)と KiB/MiB/GiB/TiB(1024基準)、ビットを相互換算し、ストレージ表記と OS 表示のずれの理由も確認できる。処理はブラウザ内で完結する。