Index: include/llvm/Support/Format.h =================================================================== --- include/llvm/Support/Format.h +++ include/llvm/Support/Format.h @@ -252,6 +252,57 @@ ByteGroupSize, Upper, true); } +/// This is a helper class used for format_memory. +class FormattedMemorySize { +public: + enum Format { Binary, Decimal, Customary = -1 }; + FormattedMemorySize(double NB, unsigned W = 2, Format S = Binary) + : NumBytes(NB), Width(W), Suffix(S) {} + +private: + friend class raw_ostream; + const double NumBytes; + const unsigned Width; + const Format Suffix; +}; + +/// format_memory_size - Output \p NB as human readable amount of bytes with +/// IEC suffixes. When the amount is greater than half the amount for the next +/// suffix that next suffix is used. If no suffix exists for the amount of +/// bytes: the number is printed as bytes. +/// +/// OS << format_memory_size(1024, 2) => "1.00 KiB" +/// OS << format_memory_size(524288, 2) => "0.50 MiB" +/// OS << format_memory_size(1048576, 4) => "1.0000 MiB" +/// OS << format_memory_size(pow(1024, 5), 3) => "1.000 PiB" +inline FormattedMemorySize format_memory_size(double NB, unsigned W = 2) { + return FormattedMemorySize(NB, W); +} + +/// format_memory_size_si - Output \p NB as human readable amount of bytes with +/// SI suffixes. When the amount is greater than half the amount for the next +/// suffix that next suffix is used. If no suffix exists for the amount of +/// bytes: the number is printed as bytes. +/// +/// OS << format_memory_size(1000, 2) => "1.00 kB" +/// OS << format_memory_size(500000, 2) => "0.50 MB" +/// OS << format_memory_size(1000000, 4) => "1.0000 MB" +/// OS << format_memory_size(pow(1000, 5), 3) => "1.000 PB" +inline FormattedMemorySize format_memory_size_si(double NB, unsigned W = 2) { + return FormattedMemorySize(NB, W, FormattedMemorySize::Decimal); +} + +/// format_memory_size_customary - Output \p NB as human readable amount of +/// bytes with SI suffixes, but still as power of 2. Techincally incorrect but +/// still often used for RAM, older unix tools, and operating systems. +/// +/// OS << format_memory_size_customary(1024, 2) => "1.00 kB" +/// OS << format_memory_size_customary(524288, 2) => "0.50 MB" +inline FormattedMemorySize format_memory_size_customary(double NB, + unsigned W = 2) { + return FormattedMemorySize(NB, W, FormattedMemorySize::Customary); +} + } // end namespace llvm #endif Index: include/llvm/Support/raw_ostream.h =================================================================== --- include/llvm/Support/raw_ostream.h +++ include/llvm/Support/raw_ostream.h @@ -30,6 +30,7 @@ class FormattedString; class FormattedNumber; class FormattedBytes; +class FormattedMemorySize; namespace sys { namespace fs { @@ -235,6 +236,9 @@ // Formatted output, see the format_bytes() function in Support/Format.h. raw_ostream &operator<<(const FormattedBytes &); + // Formatted output, see the format_memory() function in Support/Format.h. + raw_ostream &operator<<(const FormattedMemorySize &); + /// indent - Insert 'NumSpaces' spaces. raw_ostream &indent(unsigned NumSpaces); Index: lib/Support/raw_ostream.cpp =================================================================== --- lib/Support/raw_ostream.cpp +++ lib/Support/raw_ostream.cpp @@ -448,6 +448,37 @@ return *this; } +raw_ostream &raw_ostream::operator<<(const FormattedMemorySize &FM) { + constexpr size_t NumSuffixes = 8; + struct ByteTable { + ByteTable(float Base) { + for (size_t I = 0; I <= NumSuffixes; ++I) + Table[I] = std::pow(Base, I + 1); + } + double operator[](int I) const { return Table[I]; } + double Table[NumSuffixes + 1]; + }; + static const ByteTable Base2(1024), Base10(1000); + const ByteTable &BT = + FM.Suffix != FormattedMemorySize::Decimal ? Base2 : Base10; + + if (FM.NumBytes >= BT[0]) { + const bool Binary = FM.Suffix == FormattedMemorySize::Binary; + const char Suffix[NumSuffixes] = { + Binary ? 'K' : 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'}; + for (size_t I = 1; I <= NumSuffixes; ++I) { + if (FM.NumBytes >= BT[I] * 0.5) + continue; + this->operator<<(llvm::format("%.*f ", FM.Width, FM.NumBytes / BT[I-1])); + this->operator<<(Suffix[I-1]); + this->operator<<(Binary ? "iB" : "B"); + return *this; + } + } + this->operator<<(llvm::format("%.0f B", FM.NumBytes)); + return *this; +} + /// indent - Insert 'NumSpaces' spaces. raw_ostream &raw_ostream::indent(unsigned NumSpaces) { static const char Spaces[] = " " Index: unittests/Support/raw_ostream_test.cpp =================================================================== --- unittests/Support/raw_ostream_test.cpp +++ unittests/Support/raw_ostream_test.cpp @@ -13,6 +13,8 @@ #include "llvm/Support/raw_ostream.h" #include "gtest/gtest.h" +#include + using namespace llvm; namespace { @@ -158,6 +160,53 @@ EXPECT_EQ("none", printToString(center_justify("none", 1), 1)); } +static std::string format_memory_pow2(double Pow, double Mult = 1) { + const double Baseline = std::pow(1024, Pow); + return printToString(llvm::format_memory_size(Baseline * 0.4)) + " " + + printToString(llvm::format_memory_size(Baseline * 0.5)) + " " + + printToString(llvm::format_memory_size(Baseline)); +} + +static std::string format_memory_pow10(double Pow, double Mult = 1) { + const double Baseline = std::pow(1000, Pow); + return printToString(llvm::format_memory_size_si(Baseline * 0.4)) + " " + + printToString(llvm::format_memory_size_si(Baseline * 0.5)) + " " + + printToString(llvm::format_memory_size_si(Baseline)); +} + +TEST(raw_ostreamTest, FormatMemory) { + EXPECT_EQ("410 B 512 B 1.00 KiB", format_memory_pow2(1)); + EXPECT_EQ("409.60 KiB 0.50 MiB 1.00 MiB", format_memory_pow2(2)); + EXPECT_EQ("409.60 MiB 0.50 GiB 1.00 GiB", format_memory_pow2(3)); + EXPECT_EQ("409.60 GiB 0.50 TiB 1.00 TiB", format_memory_pow2(4)); + EXPECT_EQ("409.60 TiB 0.50 PiB 1.00 PiB", format_memory_pow2(5)); + EXPECT_EQ("409.60 PiB 0.50 EiB 1.00 EiB", format_memory_pow2(6)); + EXPECT_EQ("409.60 EiB 0.50 ZiB 1.00 ZiB", format_memory_pow2(7)); + EXPECT_EQ("409.60 ZiB 0.50 YiB 1.00 YiB", format_memory_pow2(8)); + + EXPECT_EQ("400 B 500 B 1.00 kB", format_memory_pow10(1)); + EXPECT_EQ("400.00 kB 0.50 MB 1.00 MB", format_memory_pow10(2)); + EXPECT_EQ("400.00 MB 0.50 GB 1.00 GB", format_memory_pow10(3)); + EXPECT_EQ("400.00 GB 0.50 TB 1.00 TB", format_memory_pow10(4)); + EXPECT_EQ("400.00 TB 0.50 PB 1.00 PB", format_memory_pow10(5)); + EXPECT_EQ("400.00 PB 0.50 EB 1.00 EB", format_memory_pow10(6)); + EXPECT_EQ("400.00 EB 0.50 ZB 1.00 ZB", format_memory_pow10(7)); + EXPECT_EQ("400.00 ZB 0.50 YB 1.00 YB", format_memory_pow10(8)); + + EXPECT_EQ("256.00 kB", + printToString(llvm::format_memory_size_customary(262144))); + EXPECT_EQ("0.50 MB", + printToString(llvm::format_memory_size_customary(262144 * 2))); + + EXPECT_EQ("0.50 PB", printToString(llvm::format_memory_size_customary( + std::pow(1024, 5) * 0.5))); + EXPECT_EQ("409.60 TB", printToString(llvm::format_memory_size_customary( + std::pow(1024, 5) * 0.4))); + + EXPECT_EQ("0.5010 TB", printToString(llvm::format_memory_size_customary( + std::pow(1024, 3) + std::pow(1024, 4) * 0.5, 4))); +} + TEST(raw_ostreamTest, FormatHex) { EXPECT_EQ("0x1234", printToString(format_hex(0x1234, 6), 6)); EXPECT_EQ("0x001234", printToString(format_hex(0x1234, 8), 8));