numfmt --to=iec 123456789 prints 118M. It’s part of GNU coreutils, so most Linux systems have it. --to=iec divides by 1,024 and adds K, M, G; --to=si divides by 1,000:
numfmt --to=iec 123456789 # 118M
numfmt --to=iec-i --suffix=B 123456789 # 118MiB
numfmt --to=si 123456789 # 124M
numfmt --to=si --format=%.1f 123456789 # 123.5M
Below 10 it keeps one decimal (4700000000 is 4.4G), and from 10 up it prints none. The tool below starts on the same 123456789 B: exactly 117.73756885528564453125 MiB and 123.456789 MB.
numfmt rounds up
124M for 123.46 million isn’t a typo. numfmt’s default is --round=from-zero, so any remainder rounds up, decimals included:
numfmt --to=iec --format=%.1f 123456789 # 117.8M
numfmt --to=iec --round=nearest --format=%.1f 123456789 # 117.7M
numfmt --to=iec 1025 # 1.1K
Add --round=nearest when the number should match what JavaScript, Python or a spreadsheet print. Going back is lossy too: numfmt --from=iec 118M gives 123731968.
Pipes and columns
stat -c %s video.mp4 | numfmt --to=iec-i --suffix=B # 118MiB
ls -l video.mp4 small.txt | numfmt --field=5 --to=iec
# -rw-r--r-- 1 root root 500 Oct 15 02:04 small.txt
# -rw-r--r-- 1 root root 118M Oct 15 02:04 video.mp4
Bash arithmetic can’t do this alone: it’s integer-only, so $(( 123456789 / 1048576 )) is 117.
ls, du and df
GNU ls -lh and du -h use 1,024 and the same letters, and they round up too. --si switches them to 1,000:
ls -lh video.mp4 # -rw-r--r-- 1 root root 118M Oct 15 02:04 video.mp4
ls -l --si video.mp4 # -rw-r--r-- 1 root root 124M Oct 15 02:04 video.mp4
du -h small.txt # 4.0K small.txt
du -h --apparent-size small.txt # 500 small.txt
du counts the disk blocks a file takes, so a 500-byte file shows 4.0K; --apparent-size reports its length. For filesystems, df -h is binary and df -H decimal. A 1 GiB tmpfs:
df -h /data
# Filesystem Size Used Avail Use% Mounted on
# tmpfs 1.0G 118M 907M 12% /data
df -H /data
# Filesystem Size Used Avail Use% Mounted on
# tmpfs 1.1G 124M 951M 12% /data
macOS
macOS has no numfmt; brew install coreutils adds it as gnumfmt. The BSD tools differ from GNU’s: stat -f %z file replaces stat -c %s, ls -lh prints bytes with a B (500B), df -h writes 926Gi, ls has no --si, and du --si rounds to the nearest, so the same file is 123M there and 124M with GNU du. An awk line works on both systems:
echo 123456789 | awk '{ split("B KiB MiB GiB TiB PiB", u); i = 1; while ($1 >= 1023.95 && i < 6) { $1 /= 1024; i++ } printf "%.1f %s\n", $1, u[i] }'
# 117.7 MiB
The 1023.95 sends 1,048,575 bytes to 1.0 MiB rather than 1024.0 KiB.