TOC

StackOverflow 上被复制最多的代码

根据《The most copied StackOverflow snippet of all time is flawed!》,StackOverflow 上被复制最多的一段代码来自问题 How can I convert byte size into a human-readable format in Java?,也就是将字节数转换成可读格式,比如 1024 -> 1KB 这样。

问题代码

public static String humanReadableByteCount(long bytes, boolean si) {
    int unit = si ? 1000 : 1024;
    if (bytes < unit) return bytes + " B";
    int exp = (int) (Math.log(bytes) / Math.log(unit));
    String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
    return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}
  1. 国际单位制(SI)就按十进制,kB = 1000、MB = 1000 kB、GB = 1000 MB...
    二进制单位的话就是 KiB = 1024,MiB = 1024 KiB、GiB = 1024 MiB...

  2. 字符串格式化的时候会四舍五入,比如 550 kB,也就是 0.55M,使用 %.1f 格式化(保留一位小数),就会变成 0.6

其他时候都还好,只是在接近下一级的时候就有点让人懵,比如:
靠近 MB 时 >= 999950 = 1000.0 kB
接近 MiB 时 >= 1048525 = 1024.0 KiB

实际上是到了 .95 的时候,进一了,但是没有进一级,所以给人困扰。

解决方案

作者更新了相关代码,就解决了这个问题,比如超过 999949 之后,不再是 1000.0 kB 了,而是 1.0 MB。

public static strictfp String humanReadableByteCount(long bytes, boolean si) {
    int unit = si ? 1000 : 1024;
    long absBytes = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
    if (absBytes < unit) return bytes + " B";
    int exp = (int) (Math.log(absBytes) / Math.log(unit));
    long th = (long) Math.ceil(Math.pow(unit, exp) * (unit - 0.05));
    if (exp < 6 && absBytes >= th - ((th & 0xFFF) == 0xD00 ? 51 : 0)) exp++;
    String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
    if (exp > 4) {
        bytes /= unit;
        exp -= 1;
    }
    return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}

生产环境

作者推荐在生产环境版本使用以下代码:

  • SI (1 k = 1,000)
public static String humanReadableByteCountSI(long bytes) {
    if (-1000 < bytes && bytes < 1000) {
        return bytes + " B";
    }
    CharacterIterator ci = new StringCharacterIterator("kMGTPE");
    while (bytes <= -999_950 || bytes >= 999_950) {
        bytes /= 1000;
        ci.next();
    }
    return String.format("%.1f %cB", bytes / 1000.0, ci.current());
}
  • Binary (1 Ki = 1,024)
public static String humanReadableByteCountBin(long bytes) {
    long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
    if (absB < 1024) {
        return bytes + " B";
    }
    long value = absB;
    CharacterIterator ci = new StringCharacterIterator("KMGTPE");
    for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) {
        value >>= 10;
        ci.next();
    }
    value *= Long.signum(bytes);
    return String.format("%.1f %ciB", value / 1024.0, ci.current());
}

思考

这个问题和语言无关,在其他语言中肯定也有相同的问题,可以注意一下。
不过,这个场景下,最后保留小数的时候,不做四舍五入是否更加符合更多人的共识一些?比如 99.999999MB 保留两位小数就应该是 99.99MB。

用 Python 示例:

def size4human(size_bytes: int, si: bool = True) -> str:
    unit = 1000 if si else 1024
    if size_bytes < unit:
        return f"{size_bytes} B"
    exp = int(math.log(size_bytes) / math.log(unit))
    pre = ("" if si else "i") + "kMGTPE"[exp - 1]
    return f"{size_bytes * 100 // math.pow(unit, exp) / 100:.2f} {pre}B"