From c3eb1e2909845724678a3ae5dad5dd40d3e44d37 Mon Sep 17 00:00:00 2001 From: Andreas Tsouchlos Date: Tue, 15 Feb 2022 09:35:36 +0100 Subject: [PATCH 1/3] Wrote tests for binary int formatting --- test/CMakeLists.txt | 3 +- test/src/format.cpp | 17 +++--- test/src/format_binary.cpp | 59 +++++++++++++++++++++ test/src/format_decimal.cpp | 103 ++++++++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 test/src/format_binary.cpp create mode 100644 test/src/format_decimal.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e17b605..e53938b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,5 +25,6 @@ endmacro() package_add_test(utility_test src/utility.cpp) package_add_test(parse_test src/parse.cpp) -package_add_test(format_test src/format.cpp) +package_add_test(format_decimal_test src/format_decimal.cpp) +package_add_test(format_binary_test src/format_binary.cpp) diff --git a/test/src/format.cpp b/test/src/format.cpp index 33e1c42..d60712a 100644 --- a/test/src/format.cpp +++ b/test/src/format.cpp @@ -1,4 +1,4 @@ -#include +#include #include @@ -6,7 +6,7 @@ using namespace const_fmt; using namespace const_fmt::const_fmt_detail; -TEST(Format, positive_int) { +TEST(FormatDecimal, positive_int) { constexpr std::array control1 = {'0', '0', '0', '0', '0', '0', '0', '2'}; constexpr std::array formatted1 = const_format<"{:08}">(2); @@ -32,7 +32,7 @@ TEST(Format, positive_int) { EXPECT_EQ(control5, formatted5); } -TEST(Format, negative_int) { +TEST(FormatDecimal, negative_int) { constexpr std::array control1 = {'-', '0', '0', '0', '0', '0', '0', '2'}; constexpr std::array formatted1 = const_format<"{:08}">(-2); @@ -58,7 +58,7 @@ TEST(Format, negative_int) { EXPECT_EQ(control5, formatted5); } -TEST(Format, positive_float) { +TEST(FormatDecimal, positive_float) { constexpr std::array control1 = {'0', '0', '2', '.', '3', '4', '5', '6'}; constexpr std::array formatted1 = const_format<"{:08.4}">(2.3456); @@ -82,7 +82,7 @@ TEST(Format, positive_float) { EXPECT_EQ(control4, formatted4); } -TEST(Format, negative_float) { +TEST(FormatDecimal, negative_float) { constexpr std::array control1 = {'-', '0', '2', '.', '3', '4', '5', '6'}; constexpr std::array formatted1 = const_format<"{:08.4}">(-2.3456); @@ -100,9 +100,4 @@ TEST(Format, negative_float) { EXPECT_EQ(control1, formatted1); EXPECT_EQ(control2, formatted2); EXPECT_EQ(control3, formatted3); -} -// -// TEST(Format, string) { -// // TODO -// EXPECT_EQ(true, false); -//} +} \ No newline at end of file diff --git a/test/src/format_binary.cpp b/test/src/format_binary.cpp new file mode 100644 index 0000000..c5873b2 --- /dev/null +++ b/test/src/format_binary.cpp @@ -0,0 +1,59 @@ +#include +#include + + +using namespace const_fmt; +using namespace const_fmt::const_fmt_detail; + + +TEST(FormatBinary, positive_int) { + constexpr std::array control1 = {'0', '0', '0', '0', + '0', '0', '1', '0'}; + constexpr std::array formatted1 = const_format<"{:08b}">(0b10); + + constexpr std::array control2 = {' ', ' ', '1', '0', + '1', '0', '1', '0'}; + constexpr std::array formatted2 = const_format<"{:8b}">(0b101010); + + constexpr std::array control3 = {'0', '0', '0', '1', + '1', '0', '0', '1'}; + constexpr std::array formatted3 = const_format<"{:08.4b}">(0b11001); + + constexpr std::array control4 = {'1', '0', '1', '1'}; + constexpr std::array formatted4 = const_format<"{:4b}">(0b1011); + + constexpr std::array control5 = {'f', 'f', 'f', 'f'}; + constexpr std::array formatted5 = const_format<"{:4b}">(0b10011); + + EXPECT_EQ(control1, formatted1); + EXPECT_EQ(control2, formatted2); + EXPECT_EQ(control3, formatted3); + EXPECT_EQ(control4, formatted4); + EXPECT_EQ(control5, formatted5); +} + +TEST(FormatBinary, negative_int) { + constexpr std::array control1 = {'-', '0', '0', '0', + '0', '0', '1', '0'}; + constexpr std::array formatted1 = const_format<"{:08b}">(-0b10); + + constexpr std::array control2 = {' ', '-', '1', '0', + '1', '0', '1', '0'}; + constexpr std::array formatted2 = const_format<"{:8b}">(-0b101010); + + constexpr std::array control3 = {'-', '0', '0', '1', + '0', '0', '1', '1'}; + constexpr std::array formatted3 = const_format<"{:08.4b}">(-0b10011); + + constexpr std::array control4 = {'-', '1', '1', '0', '1'}; + constexpr std::array formatted4 = const_format<"{:5b}">(-0b1101); + + constexpr std::array control5 = {'-', 'f', 'f', 'f', 'f'}; + constexpr std::array formatted5 = const_format<"{:05b}">(-0b10101); + + EXPECT_EQ(control1, formatted1); + EXPECT_EQ(control2, formatted2); + EXPECT_EQ(control3, formatted3); + EXPECT_EQ(control4, formatted4); + EXPECT_EQ(control5, formatted5); +} diff --git a/test/src/format_decimal.cpp b/test/src/format_decimal.cpp new file mode 100644 index 0000000..16bc490 --- /dev/null +++ b/test/src/format_decimal.cpp @@ -0,0 +1,103 @@ +#include +#include + + +using namespace const_fmt; +using namespace const_fmt::const_fmt_detail; + + +TEST(FormatDecimal, positive_int) { + constexpr std::array control1 = {'0', '0', '0', '0', + '0', '0', '0', '2'}; + constexpr std::array formatted1 = const_format<"{:08}">(2); + + constexpr std::array control2 = {' ', ' ', ' ', '2', + '2', '2', '2', '2'}; + constexpr std::array formatted2 = const_format<"{:8}">(22222); + + constexpr std::array control3 = {'0', '0', '0', '1', + '2', '3', '4', '5'}; + constexpr std::array formatted3 = const_format<"{:08.4}">(12345); + + constexpr std::array control4 = {'6', '7', '8', '9'}; + constexpr std::array formatted4 = const_format<"{:4}">(6789); + + constexpr std::array control5 = {'f', 'f', 'f', 'f'}; + constexpr std::array formatted5 = const_format<"{:4}">(67895); + + EXPECT_EQ(control1, formatted1); + EXPECT_EQ(control2, formatted2); + EXPECT_EQ(control3, formatted3); + EXPECT_EQ(control4, formatted4); + EXPECT_EQ(control5, formatted5); +} + +TEST(FormatDecimal, negative_int) { + constexpr std::array control1 = {'-', '0', '0', '0', + '0', '0', '0', '2'}; + constexpr std::array formatted1 = const_format<"{:08}">(-2); + + constexpr std::array control2 = {' ', ' ', '-', '2', + '2', '2', '2', '2'}; + constexpr std::array formatted2 = const_format<"{:8}">(-22222); + + constexpr std::array control3 = {'-', '0', '0', '1', + '2', '3', '4', '5'}; + constexpr std::array formatted3 = const_format<"{:08.4}">(-12345); + + constexpr std::array control4 = {'-', '6', '7', '8', '9'}; + constexpr std::array formatted4 = const_format<"{:5}">(-6789); + + constexpr std::array control5 = {'-', 'f', 'f', 'f', 'f'}; + constexpr std::array formatted5 = const_format<"{:05}">(-66789); + + EXPECT_EQ(control1, formatted1); + EXPECT_EQ(control2, formatted2); + EXPECT_EQ(control3, formatted3); + EXPECT_EQ(control4, formatted4); + EXPECT_EQ(control5, formatted5); +} + +TEST(FormatDecimal, positive_float) { + constexpr std::array control1 = {'0', '0', '2', '.', + '3', '4', '5', '6'}; + constexpr std::array formatted1 = const_format<"{:08.4}">(2.3456); + + // Float error: 2323.2 -> 2323.1 + constexpr std::array control2 = {' ', ' ', '2', '3', + '2', '3', '.', '1'}; + constexpr std::array formatted2 = const_format<"{:8.1}">(2323.2); + + constexpr std::array control3 = {'1', '2', '3', '4', + '.', '5', '0', '0'}; + constexpr std::array formatted3 = const_format<"{:08.3}">(1234.5); + + // Float error: .9 -> .8 + constexpr std::array control4 = {'f', 'f', '.', '8'}; + constexpr std::array formatted4 = const_format<"{:4.1}">(678.9); + + EXPECT_EQ(control1, formatted1); + EXPECT_EQ(control2, formatted2); + EXPECT_EQ(control3, formatted3); + EXPECT_EQ(control4, formatted4); +} + +TEST(FormatDecimal, negative_float) { + constexpr std::array control1 = {'-', '0', '2', '.', + '3', '4', '5', '6'}; + constexpr std::array formatted1 = const_format<"{:08.4}">(-2.3456); + + // Float error: 2323.2 -> 2323.1 + constexpr std::array control2 = {' ', '-', '2', '3', + '2', '3', '.', '1'}; + constexpr std::array formatted2 = const_format<"{:8.1}">(-2323.2); + + constexpr std::array control3 = {'-', 'f', 'f', 'f', + '.', '5', '0', '0'}; + constexpr std::array formatted3 = const_format<"{:08.3}">(-1234.5); + + + EXPECT_EQ(control1, formatted1); + EXPECT_EQ(control2, formatted2); + EXPECT_EQ(control3, formatted3); +} \ No newline at end of file From fa0fef37db4312f4d0314f41ee974a8ef30a31f8 Mon Sep 17 00:00:00 2001 From: Andreas Tsouchlos Date: Tue, 15 Feb 2022 09:37:02 +0100 Subject: [PATCH 2/3] Rewrote format_impl.h functions to work with both decimal and binary --- const_fmt/format.h | 4 +- const_fmt/format_impl.h | 86 ++++++++++++++++++++----------- examples/src/examples.cpp | 2 +- test/src/format.cpp | 103 ------------------------------------- test/src/format_binary.cpp | 2 +- 5 files changed, 60 insertions(+), 137 deletions(-) delete mode 100644 test/src/format.cpp diff --git a/const_fmt/format.h b/const_fmt/format.h index e300e78..091864c 100644 --- a/const_fmt/format.h +++ b/const_fmt/format.h @@ -42,12 +42,12 @@ constexpr inline void check_fmt_params() { template constexpr inline void format_arg(char* dest, arg_t arg) { - const_fmt_detail::format_int(dest, arg); + const_fmt_detail::format_int(dest, arg); }; template constexpr inline void format_arg(char* dest, arg_t arg){ - const_fmt_detail::format_float(dest, arg); + const_fmt_detail::format_float(dest, arg); }; // TODO: Error handling diff --git a/const_fmt/format_impl.h b/const_fmt/format_impl.h index c91aa0f..fdbb6bd 100644 --- a/const_fmt/format_impl.h +++ b/const_fmt/format_impl.h @@ -58,22 +58,40 @@ inline int do_count_digits(uint64_t n) { return t - (n < zero_or_powers_of_10[t]); } -constexpr inline auto count_digits(uint64_t n) -> int { - if (!std::is_constant_evaluated()) { - return do_count_digits(n); +template +constexpr inline auto count_digits_base(uint64_t n) -> int { + if constexpr (t_format_type == FormatType::b) { + int result = 0; + + while (n) { + n = n >> 1; + result += 1; + } + + return result; + } else { + if (!std::is_constant_evaluated()) { + return do_count_digits(n); + } + + return count_digits_fallback(n); } - return count_digits_fallback(n); } -// Converts value in the range [0, 100) to a string. -constexpr inline const char* digits2(size_t value) { +// Converts value in the range [0, base^2) to a string. +template +constexpr inline const char* digits2_base(size_t value) { // GCC generates slightly better code when value is pointer-size. - return &"0001020304050607080910111213141516171819" - "2021222324252627282930313233343536373839" - "4041424344454647484950515253545556575859" - "6061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"[value * 2]; + if constexpr (t_format_type == FormatType::b) { + return &"00011011"[value * 2]; + } else { + return &"0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"[value * 2]; + } } @@ -86,9 +104,13 @@ constexpr inline void copy2(char* dst, const char* src) { *dst = static_cast(*src); } -template -constexpr inline void format_decimal(char* out, uint_t value, int n_digits, - int size) { + +template +constexpr inline void format_base(char* out, uint_t value, int n_digits, + int size) { + constexpr unsigned divisor = (t_format_type == FormatType::b) ? 2 : 10; + constexpr unsigned square_divisor = const_pow(divisor, 2); + if (n_digits > size) { for (int i = 0; i < size; ++i) { *(out++) = 'f'; @@ -97,19 +119,20 @@ constexpr inline void format_decimal(char* out, uint_t value, int n_digits, } out += size; - while (value >= 100) { + while (value >= square_divisor) { out -= 2; - copy2(out, digits2(static_cast(value % 100))); - value /= 100; + copy2(out, digits2_base( + static_cast(value % square_divisor))); + value /= square_divisor; } - if (value < 10) { + if (value < divisor) { *--out = static_cast('0' + value); return; } out -= 2; - copy2(out, digits2(static_cast(value))); + copy2(out, digits2_base(static_cast(value))); } // returns {abs_value, was_negative} @@ -139,19 +162,22 @@ constexpr std::pair get_abs_value(int_t value) { */ -template +template constexpr inline void format_int(char* out, uint_t value) { - format_decimal(out, value, count_digits(value), t_fmt_node.length); + // format_decimal(out, value, count_digits(value), t_fmt_node.length); + format_base(out, value, count_digits_base(value), + t_fmt_node.length); } -template + +template constexpr inline void format_int(char* out, int_t value) { const auto [abs_value, negative] = get_abs_value(value); - const std::size_t n_digits = count_digits(abs_value); + const std::size_t n_digits = count_digits_base(abs_value); - format_decimal(out + 1 * (negative), abs_value, n_digits, - t_fmt_node.length - 1 * (negative)); + format_base(out + 1 * (negative), abs_value, n_digits, + t_fmt_node.length - 1 * (negative)); if constexpr (t_fmt_node.has_zero_padding) { if (negative) *(out) = '-'; @@ -169,21 +195,21 @@ constexpr inline void format_int(char* out, int_t value) { */ -template +template constexpr inline void format_float(char* out, float_t value) { // clang-format off constexpr fmt_data_t fmt_node_integral = { t_fmt_node.has_zero_padding, // has_zero_padding t_fmt_node.length - t_fmt_node.precision - 1, // length t_fmt_node.precision, // ignored - t_fmt_node.type, // ignored + FormatType::d, // type t_fmt_node.position // ignored }; constexpr fmt_data_t fmt_node_fractional = { true, // has_zero_padding t_fmt_node.precision, // length t_fmt_node.precision, // ignored - t_fmt_node.type, // ignored + FormatType::d, // type t_fmt_node.position // ignored }; // clang-format on @@ -198,8 +224,8 @@ constexpr inline void format_float(char* out, float_t value) { const auto [fractional_abs, fractional_negative] = get_abs_value(fractional); - format_int(out, integral); - format_int( + format_int(out, integral); + format_int( out + t_fmt_node.length - t_fmt_node.precision, fractional_abs); } diff --git a/examples/src/examples.cpp b/examples/src/examples.cpp index 5edb7ff..5259663 100644 --- a/examples/src/examples.cpp +++ b/examples/src/examples.cpp @@ -5,7 +5,7 @@ using namespace const_fmt; int main() { - constexpr auto s = "This is an integer: {:08}, and this is a float: {:09.4}"_const_fmt(12345, -86.2); + constexpr auto s = "This is an integer: {:08b}, and this is a float: {:09.4b}"_const_fmt(125u, -86.2); // Convert s (with a type of 'std::array') into something // writable to std::cout diff --git a/test/src/format.cpp b/test/src/format.cpp deleted file mode 100644 index d60712a..0000000 --- a/test/src/format.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include -#include - - -using namespace const_fmt; -using namespace const_fmt::const_fmt_detail; - - -TEST(FormatDecimal, positive_int) { - constexpr std::array control1 = {'0', '0', '0', '0', - '0', '0', '0', '2'}; - constexpr std::array formatted1 = const_format<"{:08}">(2); - - constexpr std::array control2 = {' ', ' ', ' ', '2', - '2', '2', '2', '2'}; - constexpr std::array formatted2 = const_format<"{:8}">(22222); - - constexpr std::array control3 = {'0', '0', '0', '1', - '2', '3', '4', '5'}; - constexpr std::array formatted3 = const_format<"{:08.4}">(12345); - - constexpr std::array control4 = {'6', '7', '8', '9'}; - constexpr std::array formatted4 = const_format<"{:4}">(6789); - - constexpr std::array control5 = {'f', 'f', 'f', 'f'}; - constexpr std::array formatted5 = const_format<"{:4}">(67895); - - EXPECT_EQ(control1, formatted1); - EXPECT_EQ(control2, formatted2); - EXPECT_EQ(control3, formatted3); - EXPECT_EQ(control4, formatted4); - EXPECT_EQ(control5, formatted5); -} - -TEST(FormatDecimal, negative_int) { - constexpr std::array control1 = {'-', '0', '0', '0', - '0', '0', '0', '2'}; - constexpr std::array formatted1 = const_format<"{:08}">(-2); - - constexpr std::array control2 = {' ', ' ', '-', '2', - '2', '2', '2', '2'}; - constexpr std::array formatted2 = const_format<"{:8}">(-22222); - - constexpr std::array control3 = {'-', '0', '0', '1', - '2', '3', '4', '5'}; - constexpr std::array formatted3 = const_format<"{:08.4}">(-12345); - - constexpr std::array control4 = {'-', '6', '7', '8', '9'}; - constexpr std::array formatted4 = const_format<"{:5}">(-6789); - - constexpr std::array control5 = {'-', 'f', 'f', 'f', 'f'}; - constexpr std::array formatted5 = const_format<"{:05}">(-66789); - - EXPECT_EQ(control1, formatted1); - EXPECT_EQ(control2, formatted2); - EXPECT_EQ(control3, formatted3); - EXPECT_EQ(control4, formatted4); - EXPECT_EQ(control5, formatted5); -} - -TEST(FormatDecimal, positive_float) { - constexpr std::array control1 = {'0', '0', '2', '.', - '3', '4', '5', '6'}; - constexpr std::array formatted1 = const_format<"{:08.4}">(2.3456); - - // Float error: 2323.2 -> 2323.1 - constexpr std::array control2 = {' ', ' ', '2', '3', - '2', '3', '.', '1'}; - constexpr std::array formatted2 = const_format<"{:8.1}">(2323.2); - - constexpr std::array control3 = {'1', '2', '3', '4', - '.', '5', '0', '0'}; - constexpr std::array formatted3 = const_format<"{:08.3}">(1234.5); - - // Float error: .9 -> .8 - constexpr std::array control4 = {'f', 'f', '.', '8'}; - constexpr std::array formatted4 = const_format<"{:4.1}">(678.9); - - EXPECT_EQ(control1, formatted1); - EXPECT_EQ(control2, formatted2); - EXPECT_EQ(control3, formatted3); - EXPECT_EQ(control4, formatted4); -} - -TEST(FormatDecimal, negative_float) { - constexpr std::array control1 = {'-', '0', '2', '.', - '3', '4', '5', '6'}; - constexpr std::array formatted1 = const_format<"{:08.4}">(-2.3456); - - // Float error: 2323.2 -> 2323.1 - constexpr std::array control2 = {' ', '-', '2', '3', - '2', '3', '.', '1'}; - constexpr std::array formatted2 = const_format<"{:8.1}">(-2323.2); - - constexpr std::array control3 = {'-', 'f', 'f', 'f', - '.', '5', '0', '0'}; - constexpr std::array formatted3 = const_format<"{:08.3}">(-1234.5); - - - EXPECT_EQ(control1, formatted1); - EXPECT_EQ(control2, formatted2); - EXPECT_EQ(control3, formatted3); -} \ No newline at end of file diff --git a/test/src/format_binary.cpp b/test/src/format_binary.cpp index c5873b2..c8456fc 100644 --- a/test/src/format_binary.cpp +++ b/test/src/format_binary.cpp @@ -1,4 +1,4 @@ -#include +#include #include From 5e4f24279603b2fc98f6464302221842053c5183 Mon Sep 17 00:00:00 2001 From: Andreas Tsouchlos Date: Tue, 15 Feb 2022 09:47:28 +0100 Subject: [PATCH 3/3] Renamed functions dedicated to decimal to better reflect their function --- const_fmt/format_impl.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/const_fmt/format_impl.h b/const_fmt/format_impl.h index fdbb6bd..26a6f1a 100644 --- a/const_fmt/format_impl.h +++ b/const_fmt/format_impl.h @@ -32,7 +32,7 @@ namespace const_fmt { namespace const_fmt_detail { (factor)*1000000000 template -constexpr int count_digits_fallback(T n) { +constexpr int count_digits_decimal_fallback(T n) { int count = 1; for (;;) { if (n < 10) return count; @@ -44,7 +44,7 @@ constexpr int count_digits_fallback(T n) { } } -inline int do_count_digits(uint64_t n) { +inline int do_count_digits_decimal(uint64_t n) { // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). static constexpr uint8_t bsr2log10[] = { 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, @@ -71,10 +71,10 @@ constexpr inline auto count_digits_base(uint64_t n) -> int { return result; } else { if (!std::is_constant_evaluated()) { - return do_count_digits(n); + return do_count_digits_decimal(n); } - return count_digits_fallback(n); + return count_digits_decimal_fallback(n); } } @@ -135,6 +135,7 @@ constexpr inline void format_base(char* out, uint_t value, int n_digits, copy2(out, digits2_base(static_cast(value))); } + // returns {abs_value, was_negative} template constexpr std::pair::type, bool> @@ -165,7 +166,8 @@ constexpr std::pair get_abs_value(int_t value) { template constexpr inline void format_int(char* out, uint_t value) { // format_decimal(out, value, count_digits(value), t_fmt_node.length); - format_base(out, value, count_digits_base(value), + format_base(out, value, + count_digits_base(value), t_fmt_node.length); }