如何在没有循环的情况下用 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 GiB
和1.2 GiB
。
我所知道的是我可以使用对数来计算选择哪个单位。所以如果 log_1024(x) >= 1
那么它应该在 KiB
中。这样我就可以避免不必要的循环。
此外,:
std::string stringifyFraction(unsigned numerator,
unsigned denominator,
unsigned precision);
以1000
或1024
为底的对数确实可以用来确定正确的单位。我们实际上只需要对数的整数部分,也就是小数点前的部分。在现代硬件上,可以在 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;
}
我想用 C++ 打印文件大小。我的输入以字节为单位,如果它超过 1024
,我想在 KiB
中打印它,如果它超过 1024*1024
,我想在 MiB
中打印它,等等。或者它应该打印在KB
表示 1000
及以上,依此类推。
它也应该有一个小数部分,这样我就可以区分1.5 GiB
和1.2 GiB
。
我所知道的是我可以使用对数来计算选择哪个单位。所以如果 log_1024(x) >= 1
那么它应该在 KiB
中。这样我就可以避免不必要的循环。
此外,
std::string stringifyFraction(unsigned numerator,
unsigned denominator,
unsigned precision);
以1000
或1024
为底的对数确实可以用来确定正确的单位。我们实际上只需要对数的整数部分,也就是小数点前的部分。在现代硬件上,可以在 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;
}