diff --git a/include/sec21/literals/memory.h b/include/sec21/literals/memory.h index 2682b7e3..5fbea40c 100644 --- a/include/sec21/literals/memory.h +++ b/include/sec21/literals/memory.h @@ -1,26 +1,35 @@ #pragma once +#include + namespace sec21::literals { + //! \brief Byte + constexpr auto operator""_B(unsigned long long n) noexcept -> memory { return {n}; } + + // SI + // //! \brief Kilobyte - constexpr auto operator""_kB(unsigned long long n) noexcept { return n * 1000; } + constexpr auto operator""_kB(unsigned long long n) noexcept -> memory { return {n * 1000}; } //! \brief Megabyte - constexpr auto operator""_MB(unsigned long long n) noexcept { return n * 1000_kB; } + constexpr auto operator""_MB(unsigned long long n) noexcept -> memory { return memory{n} * 1000_kB; } //! \brief Gigabyte - constexpr auto operator""_GB(unsigned long long n) noexcept { return n * 1000_MB; } + constexpr auto operator""_GB(unsigned long long n) noexcept -> memory { return memory{n} * 1000_MB; } //! \brief Terabyte - constexpr auto operator""_TB(unsigned long long n) noexcept { return n * 1000_GB; } + constexpr auto operator""_TB(unsigned long long n) noexcept -> memory { return memory{n} * 1000_GB; } //! \brief Petabyte - constexpr auto operator""_PB(unsigned long long n) noexcept { return n * 1000_TB; } + constexpr auto operator""_PB(unsigned long long n) noexcept -> memory { return memory{n} * 1000_TB; } + // IEC + // //! \brief Kibibyte - constexpr auto operator""_kiB(unsigned long long n) noexcept { return n * 1024; } + constexpr auto operator""_kiB(unsigned long long n) noexcept -> memory { return {n * 1024}; } //! \brief Mebibyte - constexpr auto operator""_MiB(unsigned long long n) noexcept { return n * 1024_kiB; } + constexpr auto operator""_MiB(unsigned long long n) noexcept -> memory { return memory{n} * 1024_kiB; } //! \brief Gibibyte - constexpr auto operator""_GiB(unsigned long long n) noexcept { return n * 1024_MiB; } + constexpr auto operator""_GiB(unsigned long long n) noexcept -> memory { return memory{n} * 1024_MiB; } //! \brief Tebibyte - constexpr auto operator""_TiB(unsigned long long n) noexcept { return n * 1024_GiB; } + constexpr auto operator""_TiB(unsigned long long n) noexcept -> memory { return memory{n} * 1024_GiB; } //! \brief Pebibyte - constexpr auto operator""_PiB(unsigned long long n) noexcept { return n * 1024_TiB; } + constexpr auto operator""_PiB(unsigned long long n) noexcept -> memory { return memory{n} * 1024_TiB; } } // namespace sec21::literals diff --git a/include/sec21/memory.h b/include/sec21/memory.h new file mode 100644 index 00000000..f4bb433c --- /dev/null +++ b/include/sec21/memory.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include + +namespace sec21 +{ + struct memory + { + using value_t = unsigned long long; + value_t bytes{}; + + friend auto operator<=>(memory const&, memory const&) = default; + + friend auto operator<<(std::ostream& out, memory const& obj) -> auto& { return out << obj.bytes; } + }; + + [[nodiscard]] constexpr auto operator+(memory const& lhs, std::byte rhs) noexcept -> memory + { + return {lhs.bytes + std::to_underlying(rhs)}; + } + + [[nodiscard]] constexpr auto operator+(memory const& lhs, memory const& rhs) noexcept -> memory + { + return {lhs.bytes + rhs.bytes}; + } + + [[nodiscard]] constexpr auto operator-(memory const& lhs, std::byte rhs) noexcept -> memory + { + if (std::to_underlying(rhs) > lhs.bytes) { + throw std::out_of_range{"right-hand-side is greater than the left-hand-side"}; + } + return {lhs.bytes - std::to_underlying(rhs)}; + } + + [[nodiscard]] constexpr auto operator-(memory const& lhs, memory const& rhs) -> memory + { + if (rhs.bytes > lhs.bytes) { + throw std::out_of_range{"right-hand-side is greater than the left-hand-side"}; + } + return {lhs.bytes - rhs.bytes}; + } + + [[nodiscard]] constexpr auto operator*(memory const& lhs, std::byte rhs) noexcept -> memory + { + return {lhs.bytes * std::to_underlying(rhs)}; + } + + [[nodiscard]] constexpr auto operator*(memory const& lhs, memory const& rhs) noexcept -> memory + { + return {lhs.bytes * rhs.bytes}; + } +} // namespace sec21 + +template <> +struct std::formatter +{ + constexpr auto parse(std::format_parse_context& ctx) + { + auto pos = ctx.begin(); + while (pos != ctx.end() and *pos != '}') { + if (*pos == 'h' or *pos == 'H') { + human_readable = true; + } + if (*pos == '.') { + pos = std::from_chars(++pos, ctx.end(), precision).ptr; + --pos; + } + ++pos; + } + return pos; + } + + auto format(sec21::memory const& obj, std::format_context& ctx) const + { + constexpr auto units = std::array{"B", "kB", "MB", "GB", "TB", "PB"}; + + if (human_readable) { + constexpr auto factor = 1000; + constexpr auto factorIEC = 1024; //! \todo + + auto value = static_cast(obj.bytes); + auto unit = units[0]; + + for (decltype(units.size()) i = 0; i < units.size(); ++i) { + if (obj.bytes >= std::pow(factor, i)) { + value = static_cast(obj.bytes) / std::pow(factor, i); + unit = units[i]; + } + } + return std::format_to(ctx.out(), "{:.{}f}{}", value, precision, unit); + } + return std::format_to(ctx.out(), "{}{}", obj.bytes, units[0]); + } + + bool human_readable{false}; + std::size_t precision{2}; +}; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 95dd9fcb..20440b6f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -49,6 +49,7 @@ test("for_each_adjacent") test("for_each_chunk") test("for_each_indexed") test("limited_quantity") +test("memory") test("literals.memory") test("reflection.get_column") test("scope_guard") diff --git a/tests/literals.memory.cpp b/tests/literals.memory.cpp index 91555110..71cc421a 100644 --- a/tests/literals.memory.cpp +++ b/tests/literals.memory.cpp @@ -2,19 +2,22 @@ #include -TEST_CASE("checks memory literals", "[sec21][type_traits]") +TEST_CASE("checks memory literals", "[sec21][literals]") { + using namespace sec21; using namespace sec21::literals; - STATIC_REQUIRE(1_kB == 1'000); - STATIC_REQUIRE(1_MB == 1'000'000); - STATIC_REQUIRE(1_GB == 1'000'000'000); - STATIC_REQUIRE(1_TB == 1'000'000'000'000); - STATIC_REQUIRE(1_PB == 1'000'000'000'000'000); + STATIC_REQUIRE(memory{1_B}.bytes == 1); - STATIC_REQUIRE(1_kiB == 1024); - STATIC_REQUIRE(1_MiB == 1048576); - STATIC_REQUIRE(1_GiB == 1073741824); - STATIC_REQUIRE(1_TiB == 1099511627776); - STATIC_REQUIRE(1_PiB == 1125899906842624); + STATIC_REQUIRE(memory{1_kB}.bytes == 1'000); + STATIC_REQUIRE(memory{1_MB}.bytes == 1'000'000); + STATIC_REQUIRE(memory{1_GB}.bytes == 1'000'000'000); + STATIC_REQUIRE(memory{1_TB}.bytes == 1'000'000'000'000); + STATIC_REQUIRE(memory{1_PB}.bytes == 1'000'000'000'000'000); + + STATIC_REQUIRE(memory{1_kiB}.bytes == 1024); + STATIC_REQUIRE(memory{1_MiB}.bytes == 1048576); + STATIC_REQUIRE(memory{1_GiB}.bytes == 1073741824); + STATIC_REQUIRE(memory{1_TiB}.bytes == 1099511627776); + STATIC_REQUIRE(memory{1_PiB}.bytes == 1125899906842624); } diff --git a/tests/memory.cpp b/tests/memory.cpp new file mode 100644 index 00000000..d0d25ac0 --- /dev/null +++ b/tests/memory.cpp @@ -0,0 +1,51 @@ +#include + +#include +#include + +TEST_CASE("memory class", "[sec21]") +{ + using namespace sec21; + using namespace sec21::literals; + + SECTION("addition") + { + REQUIRE(memory{1} + memory{2} == memory{3}); + REQUIRE(4_kiB + std::byte{4} == memory{4100}); + } + SECTION("subtraction") + { + REQUIRE(memory{2} - memory{1} == memory{1}); + REQUIRE(4_kiB - std::byte{4} == memory{4092}); + } + SECTION("test safety check of subtraction operator") + { + REQUIRE_THROWS([] { auto result = memory{2} - memory{10}; }()); + // REQUIRE_THROWS([] { auto result = memory{2} - std::byte{10}; }()); + } + SECTION("multiplication") + { + REQUIRE(memory{2} * memory{1} == 2_B); + REQUIRE(4_kiB * std::byte{2} == memory{8192}); + } + SECTION("test formatter") + { + SECTION("default") { REQUIRE(std::format("{}", memory{1024}) == "1024B"); } + + SECTION("format output to a human readable format") + { + REQUIRE(std::format("{:h}", memory{1_kiB}) == "1.02kB"); + REQUIRE(std::format("{:h}", memory{1_MiB}) == "1.05MB"); + REQUIRE(std::format("{:h}", memory{1_GiB}) == "1.07GB"); + REQUIRE(std::format("{:h}", memory{1_TiB}) == "1.10TB"); + REQUIRE(std::format("{:h}", memory{1_PiB}) == "1.13PB"); + } + SECTION("human readable format with precision") + { + REQUIRE(std::format("{:.0h}", memory{1024 * 1024}) == "1MB"); + REQUIRE(std::format("{:.1h}", memory{1024 * 1024}) == "1.0MB"); + REQUIRE(std::format("{:.4h}", memory{1024 * 1024}) == "1.0486MB"); + REQUIRE(std::format("{:.h}", memory{1024 * 1024}) == "1.05MB"); + } + } +}