diff --git a/include/boost/static_string/static_string.hpp b/include/boost/static_string/static_string.hpp index cd67705..68a556a 100644 --- a/include/boost/static_string/static_string.hpp +++ b/include/boost/static_string/static_string.hpp @@ -246,7 +246,7 @@ public: Traits::assign(data_[size_], value_type()); } - size_type size_{0}; + size_type size_ = 0; #ifdef BOOST_STATIC_STRING_CPP20 value_type data_[N + 1]; #else @@ -336,9 +336,20 @@ integer_to_string( } if (value < 0) { - value = -value; + const bool is_min = value == std::numeric_limits::min(); + // negation of a min value cannot be represented + if (is_min) + value = std::numeric_limits::max(); + else + value = -value; + const auto last_char = str_end - 1; for (; value > 0; value /= 10) Traits::assign(*--str_end, "0123456789"[value % 10]); + // minimum values are powers of 2, so it will + // never terminate with a 9. + if (is_min) + Traits::assign(*last_char, Traits::to_char_type( + Traits::to_int_type(*last_char) + 1)); Traits::assign(*--str_end, '-'); return str_end; } @@ -380,9 +391,20 @@ integer_to_wstring( } if (value < 0) { - value = -value; + const bool is_min = value == std::numeric_limits::min(); + // negation of a min value cannot be represented + if (is_min) + value = std::numeric_limits::max(); + else + value = -value; + const auto last_char = str_end - 1; for (; value > 0; value /= 10) Traits::assign(*--str_end, L"0123456789"[value % 10]); + // minimum values are powers of 2, so it will + // never terminate with a 9. + if (is_min) + Traits::assign(*last_char, Traits::to_char_type( + Traits::to_int_type(*last_char) + 1)); Traits::assign(*--str_end, L'-'); return str_end; } @@ -433,6 +455,13 @@ to_static_wstring_int_impl(Integer value) noexcept return static_wstring(digits_begin, std::distance(digits_begin, digits_end)); } +BOOST_STATIC_STRING_CPP11_CONSTEXPR +inline +std::size_t count_digits(std::size_t value) +{ + return value < 10 ? 1 : count_digits(value / 10) + 1; +} + template inline static_string @@ -440,7 +469,27 @@ to_static_string_float_impl(double value) noexcept { // extra one needed for null terminator char buffer[N + 1]; - std::sprintf(buffer, "%f", value); + const auto write_count = + std::snprintf(buffer, N + 1, "%f", value); + // since our buffer has a fixed size, if the number of characters + // that would be written exceeds the size of the buffer, + // we need touse scientific notation instead. + // the number is converted into the format + // [-]d.ddde(+/-)dd; the exponent will contain at least + // 2 digits, the integral part will be 1 digit, the fractional + // part will be the length of the specified precision, and 3 + // characters will be needed for e, the sign of the exponent + // and the potential sign of the integral part, total of 6 chars. + // we will reserve 7 characters just in case the size of the exponent + // is 3 characters. + if (write_count > N) + { + const auto period = + std::char_traits::find(buffer, N, '.'); + // we want at least 2 decimal places + if (!period || std::size_t(period - buffer) > N - 3) + std::snprintf(buffer, N + 1, "%.*e", int(N > 7 ? N - 7 : 0), value); + } // this will not throw return static_string(buffer); } @@ -452,7 +501,17 @@ to_static_string_float_impl(long double value) noexcept { // extra one needed for null terminator char buffer[N + 1]; - std::sprintf(buffer, "%Lf", value); + // number of characters written + const auto write_count = + std::snprintf(buffer, N + 1, "%Lf", value); + if (write_count > N) + { + const auto period = + std::char_traits::find(buffer, N, '.'); + // we want at least 2 decimal places + if (!period || std::size_t(period - buffer) > N - 3) + std::snprintf(buffer, N + 1, "%.*e", int(N > 7 ? N - 7 : 0), value); + } // this will not throw return static_string(buffer); } @@ -464,7 +523,16 @@ to_static_wstring_float_impl(double value) noexcept { // extra one needed for null terminator wchar_t buffer[N + 1]; - std::swprintf(buffer, N + 1, L"%f", value); + const auto write_count = + std::swprintf(buffer, N + 1, L"%f", value); + if (write_count > N) + { + const auto period = + std::char_traits::find(buffer, N, '.'); + // we want at least 2 decimal places + if (!period || std::size_t(period - buffer) > N - 3) + std::swprintf(buffer, N + 1, L"%.*e", int(N > 7 ? N - 7 : 0), value); + } // this will not throw return static_wstring(buffer); } @@ -476,7 +544,16 @@ to_static_wstring_float_impl(long double value) noexcept { // extra one needed for null terminator wchar_t buffer[N + 1]; - std::swprintf(buffer, N + 1, L"%Lf", value); + const auto write_count = + std::swprintf(buffer, N + 1, L"%Lf", value); + if (write_count > N) + { + const auto period = + std::char_traits::find(buffer, N, '.'); + // we want at least 2 decimal places + if (!period || std::size_t(period - buffer) > N - 3) + std::swprintf(buffer, N + 1, L"%.*e", int(N > 7 ? N - 7 : 0), value); + } // this will not throw return static_wstring(buffer); } @@ -4510,30 +4587,30 @@ operator<<( //------------------------------------------------------------------------------ /// Converts `value` to a `static_string` -static_string::digits10 + 1> +static_string::digits10 + 2> inline to_static_string(int value) noexcept { return detail::to_static_string_int_impl< - std::numeric_limits::digits10 + 1>(value); + std::numeric_limits::digits10 + 2>(value); } /// Converts `value` to a `static_string` -static_string::digits10 + 1> +static_string::digits10 + 2> inline to_static_string(long value) noexcept { return detail::to_static_string_int_impl< - std::numeric_limits::digits10 + 1>(value); + std::numeric_limits::digits10 + 2>(value); } /// Converts `value` to a `static_string` -static_string::digits10 + 1> +static_string::digits10 + 2> inline to_static_string(long long value) noexcept { return detail::to_static_string_int_impl< - std::numeric_limits::digits10 + 1>(value); + std::numeric_limits::digits10 + 2>(value); } /// Converts `value` to a `static_string` @@ -4591,30 +4668,30 @@ to_static_string(long double value) noexcept } /// Converts `value` to a `static_wstring` -static_wstring::digits10 + 1> +static_wstring::digits10 + 2> inline to_static_wstring(int value) noexcept { return detail::to_static_wstring_int_impl< - std::numeric_limits::digits10 + 1>(value); + std::numeric_limits::digits10 + 2>(value); } /// Converts `value` to a `static_wstring` -static_wstring::digits10 + 1> +static_wstring::digits10 + 2> inline to_static_wstring(long value) noexcept { return detail::to_static_wstring_int_impl< - std::numeric_limits::digits10 + 1>(value); + std::numeric_limits::digits10 + 2>(value); } /// Converts `value` to a `static_wstring` -static_wstring::digits10 + 1> +static_wstring::digits10 + 2> inline to_static_wstring(long long value) noexcept { return detail::to_static_wstring_int_impl< - std::numeric_limits::digits10 + 1>(value); + std::numeric_limits::digits10 + 2>(value); } /// Converts `value` to a `static_wstring` diff --git a/test/static_string.cpp b/test/static_string.cpp index e824ad8..c3b21fe 100644 --- a/test/static_string.cpp +++ b/test/static_string.cpp @@ -15,7 +15,10 @@ #include "constexpr_tests.hpp" +#include +#include #include +#include namespace boost { namespace static_string { @@ -224,8 +227,36 @@ testR(S s, typename S::size_type pos, typename S::size_type n1, const typename S } } -// done +template +bool +testTS(Arithmetic value, const char* str_expected = "", const wchar_t* wstr_expected = L"", bool test_expected = false) +{ + const auto str = to_static_string(value); + const auto wstr = to_static_wstring(value); + if (std::is_floating_point::value) + { + const auto std_res = std::to_string(value); + const auto wstd_res = std::to_wstring(value); + return str == std_res.data() && wstr == wstd_res.data(); + } + else + { + if (std::is_signed::value) + { + return std::strtoll(str.begin(), nullptr, 10) == value && + std::wcstoll(wstr.begin(), nullptr, 10) == value && + (test_expected ? str == str_expected && wstr == wstr_expected : true); + } + else + { + return std::strtoull(str.begin(), nullptr, 10) == value && + std::wcstoull(wstr.begin(), nullptr, 10) == value && + (test_expected ? str == str_expected && wstr == wstr_expected : true); + } + } +} +// done static void testConstruct() @@ -3647,24 +3678,30 @@ testGeneral() void testToStaticString() { - BOOST_TEST(to_static_string(0) == "0"); - BOOST_TEST(to_static_string(0u) == "0"); - BOOST_TEST(to_static_string(1) == "1"); - BOOST_TEST(to_static_string(0xffff) == "65535"); - BOOST_TEST(to_static_string(0x10000) == "65536"); - BOOST_TEST(to_static_string(0xffffffff) == "4294967295"); + BOOST_TEST(testTS(0, "0", L"0", true)); + BOOST_TEST(testTS(0xffff, "65535", L"65535", true)); + BOOST_TEST(testTS(0x10000, "65536", L"65536", true)); + BOOST_TEST(testTS(0xffffffff, "4294967295", L"4294967295", true)); + BOOST_TEST(testTS(-65535, "-65535", L"-65535", true)); + BOOST_TEST(testTS(-65536, "-65536", L"-65536", true)); + BOOST_TEST(testTS(-4294967295, "-4294967295", L"-4294967295", true)); + BOOST_TEST(testTS(1, "1", L"1", true)); + BOOST_TEST(testTS(-1, "-1", L"-1", true)); + BOOST_TEST(testTS(0.1)); + BOOST_TEST(testTS(0.0000001)); + BOOST_TEST(testTS(-0.0000001)); + BOOST_TEST(testTS(-0.1)); + BOOST_TEST(testTS(1234567890.0001)); + BOOST_TEST(testTS(1.123456789012345)); + BOOST_TEST(testTS(-1234567890.1234)); + BOOST_TEST(testTS(-1.123456789012345)); - BOOST_TEST(to_static_string(-1) == "-1"); - BOOST_TEST(to_static_string(-65535) == "-65535"); - BOOST_TEST(to_static_string(-65536) == "-65536"); - BOOST_TEST(to_static_string(-4294967295ll) == "-4294967295"); - - BOOST_TEST(to_static_string(0) == "0"); - - BOOST_TEST(to_static_string(1) == "1"); - BOOST_TEST(to_static_string(0xffff) == "65535"); - BOOST_TEST(to_static_string(0x10000) == "65536"); - BOOST_TEST(to_static_string(0xffffffff) == "4294967295"); + BOOST_TEST(testTS(std::numeric_limits::max())); + BOOST_TEST(testTS(std::numeric_limits::min())); + BOOST_TEST(testTS(std::numeric_limits::max())); + BOOST_TEST(testTS(std::numeric_limits::max())); + BOOST_TEST(testTS(std::numeric_limits::min())); + BOOST_TEST(testTS(std::numeric_limits::min())); } // done