如何在没有循环的情况下用 C++ 打印人类可读的文件大小

How can I print a human-readable file size in C++ without a loop

我想用 C++ 打印文件大小。我的输入以字节为单位,如果它超过 1024,我想在 KiB 中打印它,如果它超过 1024*1024,我想在 MiB 中打印它,等等。或者它应该打印在KB 表示 1000 及以上,依此类推。

它也应该有一个小数部分,这样我就可以区分1.5 GiB1.2 GiB

我所知道的是我可以使用对数来计算选择哪个单位。所以如果 log_1024(x) >= 1 那么它应该在 KiB 中。这样我就可以避免不必要的循环。

此外,

std::string stringifyFraction(unsigned numerator,
                              unsigned denominator,
                              unsigned precision);

10001024为底的对数确实可以用来确定正确的单位。我们实际上只需要对数的整数部分,也就是小数点前的部分。在现代硬件上,可以在 O(1) 中计算整数对数,因此这比使用 for 循环得到正确的单位要快一些。 .

如果整数部分是0,我们在B中打印,在KiB中打印1,等等。我们可以创建一个查找table其中关键是我们的对数:

constexpr const char FILE_SIZE_UNITS[8][3]{
    "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"
};

请注意,table 使用 3 作为内部大小,因为所有字符串都是 null-terminated。 您可能还想知道为什么查找 table 不包含 KiB 单位。这是因为中间的 i 是常量,不需要成为 table 的一部分。此外,文件大小有两种不同的单位系统,一种是以 1000 为基数,一种是以 1024 为基数。参见 Files size units: “KiB” vs “KB” vs “kB”。我们可以轻松地在一个功能中支持这两种功能。

然后我们可以按如下方式实现我们的 stringifyFileSize 方法:

// use SFINAE to only allow base 1000 or 1024
template <size_t BASE = 1024,
    std::enable_if_t<BASE == 1000 || BASE == 1024, int> = 0>
std::string stringifyFileSize(uint64_t size, unsigned precision = 0) noexcept
{
    constexpr const char FILE_SIZE_UNITS[8][3]{
        "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"
    };

    // The linked post about computing the integer logarithm
    // explains how to compute this.
    // This is equivalent to making a table: {1, 1000, 1000 * 1000, ...}
    // or {1, 1024, 1024 * 1024, ...}
    constexpr auto powers = makePowerTable<Uint, BASE>();

    unsigned unit = logFloor<BASE>(size);

    // Your numerator is size, your denominator is 1000^unit or 1024^unit.
    std::string result = stringifyFraction(size, powers[unit], precision);
    result.reserve(result.size() + 5);

    // Optional: Space separating number from unit. (usually looks better)
    result.push_back(' ');
    char first = FILE_SIZE_UNITS[unit][0];
    // Optional: Use lower case (kB, mB, etc.) for decimal units
    if constexpr (BASE == 1000) {
        first += 'a' - 'A';
    }
    result.push_back(first);

    // Don't insert anything more in case of single bytes.
    if (unit != 0) {
        if constexpr (BASE == 1024) {
            result.push_back('i');
        }
        result.push_back(FILE_SIZE_UNITS[unit][1]);
    }

    return result;
}