diff --git a/include/etl/private/to_string_helper.h b/include/etl/private/to_string_helper.h index 1e1ff016..b09f9c5c 100644 --- a/include/etl/private/to_string_helper.h +++ b/include/etl/private/to_string_helper.h @@ -218,20 +218,36 @@ namespace etl /// Helper function for floating point nan and inf. //*************************************************************************** template - void add_nan_inf(const bool not_a_number, const bool infinity, TIString& str) + void add_nan_inf(const bool not_a_number, const bool infinity, TIString& str, const etl::basic_format_spec& format) { typedef typename TIString::value_type type; - static const type n[] = {'n', 'a', 'n'}; - static const type i[] = {'i', 'n', 'f'}; + static const type nan_lower[] = {'n', 'a', 'n'}; + static const type nan_upper[] = {'N', 'A', 'N'}; + static const type inf_lower[] = {'i', 'n', 'f'}; + static const type inf_upper[] = {'I', 'N', 'F'}; if (not_a_number) { - str.insert(str.end(), ETL_OR_STD11::begin(n), ETL_OR_STD11::end(n)); + if (format.is_upper_case()) + { + str.insert(str.end(), ETL_OR_STD11::begin(nan_upper), ETL_OR_STD11::end(nan_upper)); + } + else + { + str.insert(str.end(), ETL_OR_STD11::begin(nan_lower), ETL_OR_STD11::end(nan_lower)); + } } else if (infinity) { - str.insert(str.end(), ETL_OR_STD11::begin(i), ETL_OR_STD11::end(i)); + if (format.is_upper_case()) + { + str.insert(str.end(), ETL_OR_STD11::begin(inf_upper), ETL_OR_STD11::end(inf_upper)); + } + else + { + str.insert(str.end(), ETL_OR_STD11::begin(inf_lower), ETL_OR_STD11::end(inf_lower)); + } } } @@ -275,6 +291,131 @@ namespace etl } #endif + //*************************************************************************** + /// Helper function for floating point in scientific format. + //*************************************************************************** + template + void add_floating_point_scientific(const T value, TIString& str, const etl::basic_format_spec& format, const uint32_t max_precision) + { + typedef typename TIString::value_type type; + + const uint32_t requested_precision = format.get_precision(); + const uint32_t precision = (requested_precision == 0U) + ? max_precision + : (requested_precision > max_precision ? max_precision : requested_precision); + + etl::basic_format_spec mantissa_integral_format = format; + mantissa_integral_format.decimal().width(0U).precision(0U); + + etl::basic_format_spec mantissa_fractional_format = mantissa_integral_format; + mantissa_fractional_format.precision(precision).width(precision).fill(type('0')).right(); + + T abs_value = etl::absolute(value); + + // Find exponent by iterative scaling + int32_t exponent = 0; + T scaled = abs_value; + + if (scaled >= T(1)) + { + // Scale down for values >= 1 + while (scaled >= T(10)) + { + scaled /= T(10); + ++exponent; + } + } + else if (scaled > T(0)) + { + // Scale up for values < 1 + while (scaled < T(1)) + { + scaled *= T(10); + --exponent; + } + } + + // Calculate the multiplier for the fractional part. + uworkspace_t multiplier = 1U; + for (uint32_t i = 0U; i < precision; ++i) + { + multiplier *= 10U; + } + + // Find the integral part of the floating point + T f_integral = ::floor(scaled); + uworkspace_t integral = static_cast(f_integral); + + // Find the fractional part of the floating point. + uworkspace_t fractional = static_cast(::round((scaled - f_integral) * multiplier)); + + // Check for a rounding carry to the integral. + if (fractional == multiplier) + { + ++integral; + fractional = 0U; + + if (integral == 10U) + { + integral = 1U; + ++exponent; + } + } + + etl::private_to_string::add_integral_and_fractional(integral, fractional, str, mantissa_integral_format, mantissa_fractional_format, etl::is_negative(value)); + + // Append the exponent. + str.push_back(format.is_upper_case() ? type('E') : type('e')); + str.push_back((exponent < 0) ? type('-') : type('+')); + + uworkspace_t abs_exponent = static_cast(etl::absolute(exponent)); + + etl::basic_format_spec exponent_format = format; + exponent_format.decimal().width(1U).precision(0U).right(); + + etl::private_to_string::add_integral(abs_exponent, str, exponent_format, true, false); + } + + //*************************************************************************** + /// Helper function for floating point in non-scientific format. + //*************************************************************************** + template + void add_floating_point_non_scientific(const T value, TIString& str, const etl::basic_format_spec& format, const uint32_t max_precision) + { + typedef typename TIString::value_type type; + + etl::basic_format_spec integral_format = format; + integral_format.decimal().width(0).precision(format.get_precision() > max_precision ? max_precision : format.get_precision()); + + etl::basic_format_spec fractional_format = integral_format; + fractional_format.width(integral_format.get_precision()).fill(type('0')).right(); + + // Calculate the multiplier for the fractional part. + uworkspace_t multiplier = 1U; + + for (uint32_t i = 0U; i < fractional_format.get_precision(); ++i) + { + multiplier *= 10U; + } + + // Find the integral part of the floating point + T f_integral = ::floor(etl::absolute(value)); + uworkspace_t integral = static_cast(f_integral); + + // Find the fractional part of the floating point. + uworkspace_t fractional = static_cast(::round((etl::absolute(value) - f_integral) * multiplier)); + + // Check for a rounding carry to the integral. + if (fractional == multiplier) + { + ++integral; + fractional = 0U; + } + + // Create the string. + etl::private_to_string::add_integral_and_fractional(integral, fractional, str, integral_format, fractional_format, etl::is_negative(value)); + } + //*************************************************************************** /// Helper function for floating point. //*************************************************************************** @@ -293,7 +434,7 @@ namespace etl if (isnan(value) || isinf(value)) { - etl::private_to_string::add_nan_inf(isnan(value), isinf(value), str); + etl::private_to_string::add_nan_inf(isnan(value), isinf(value), str, format); } else { @@ -307,36 +448,19 @@ namespace etl } #endif - etl::basic_format_spec integral_format = format; - integral_format.decimal().width(0).precision(format.get_precision() > max_precision ? max_precision : format.get_precision()); + bool requires_scientific_form = format.is_scientific() || (etl::absolute(value) > static_cast(etl::numeric_limits::max())); - etl::basic_format_spec fractional_format = integral_format; - fractional_format.width(integral_format.get_precision()).fill(type('0')).right(); - - uworkspace_t multiplier = 1U; - - for (uint32_t i = 0U; i < fractional_format.get_precision(); ++i) + if (requires_scientific_form) { - multiplier *= 10U; + etl::private_to_string::add_floating_point_scientific(value, str, format, max_precision); } - - // Find the integral part of the floating point - T f_integral = ::floor(etl::absolute(value)); - uworkspace_t integral = static_cast(f_integral); - - // Find the fractional part of the floating point. - uworkspace_t fractional = static_cast(::round((etl::absolute(value) - f_integral) * multiplier)); - - // Check for a rounding carry to the integral. - if (fractional == multiplier) + else { - ++integral; - fractional = 0U; + etl::private_to_string::add_floating_point_non_scientific(value, str, format, max_precision); } - - etl::private_to_string::add_integral_and_fractional(integral, fractional, str, integral_format, fractional_format, etl::is_negative(value)); } + // Add alignment if necessary. etl::private_to_string::add_alignment(str, start, format); } diff --git a/test/test_to_string.cpp b/test/test_to_string.cpp index d9ef2919..767201c4 100644 --- a/test/test_to_string.cpp +++ b/test/test_to_string.cpp @@ -30,7 +30,7 @@ SOFTWARE. #include #include -#include +#include #include "etl/format_spec.h" #include "etl/string.h" @@ -45,15 +45,6 @@ namespace SUITE(test_to_string) { - TEST(test_issue_314) - { - etl::string<20> str; - - etl::format_spec ft; - ft.precision(6); - etl::to_string(1.23, str, ft, true); - } - //************************************************************************* TEST(test_default_format_no_append) { @@ -675,5 +666,53 @@ namespace CHECK_EQUAL(etl::string<20>(STR("-124.0000")).c_str(), result_i.c_str()); CHECK_EQUAL(result_d.c_str(), result_i.c_str()); } + + //************************************************************************* + TEST(test_issue_1438_does_not_handle_floating_point_number_with_integer_part_exceeding_max_uint64_t) + { + if (std::numeric_limits::is_iec559) + { + // Check forced scientific formatting for smaller numbers. + etl::string<64> s0; + etl::to_string(1000.0, s0, Format().precision(5).width(15).right().scientific(true)); + CHECK(etl::string<64>(STR(" 1.00000e+3")) == s0); + + // Maximum double value is 1.7976931348623157e+308, which rounds to 1.79769e+308 with 5 digits of precision. + etl::string<64> s1; + etl::to_string(std::numeric_limits::max(), s1, Format().precision(5).width(15).right()); + CHECK(etl::string<64>(STR(" 1.79769e+308")) == s1); + + // Negative maximum double value is -1.7976931348623157e+308, which rounds to -1.79769e+308 with 5 digits of precision. + etl::string<64> s2; + etl::to_string(-std::numeric_limits::max(), s2, Format().precision(5).width(15).right()); + CHECK(etl::string<64>(STR(" -1.79769e+308")) == s2); + } + +#if ETL_USING_64BIT_TYPES + using workspace_t = etl::private_to_string::workspace_t; + + // Maximum workspace_t value is 9223372036854775807. + etl::string<64> s3; + etl::to_string(std::numeric_limits::max(), s3, Format().precision(5).width(21).right()); + CHECK(etl::string<64>(STR(" 9223372036854775807")) == s3); + + // Minimum workspace_t value is -9223372036854775808. + etl::string<64> s4; + etl::to_string(std::numeric_limits::min(), s4, Format().precision(5).width(21).right()); + CHECK(etl::string<64>(STR(" -9223372036854775808")) == s4); + + using uworkspace_t = etl::private_to_string::uworkspace_t; + + // Maximum uworkspace_t value is 18446744073709551615. + etl::string<64> s5; + etl::to_string(std::numeric_limits::max(), s5, Format().precision(5).width(21).right()); + CHECK(etl::string<64>(STR(" 18446744073709551615")) == s5); + + // Minimum uworkspace_t value is 0. + etl::string<64> s6; + etl::to_string(std::numeric_limits::min(), s6, Format().precision(5).width(21).right()); + CHECK(etl::string<64>(STR(" 0")) == s6); +#endif + } } } // namespace diff --git a/test/test_to_u16string.cpp b/test/test_to_u16string.cpp index 4050b940..c9b45ab3 100644 --- a/test/test_to_u16string.cpp +++ b/test/test_to_u16string.cpp @@ -579,5 +579,53 @@ namespace CHECK(etl::u16string<20>(STR("-124.0000")) == result_i); CHECK(result_d == result_i); } + + //************************************************************************* + TEST(test_issue_1438_does_not_handle_floating_point_number_with_integer_part_exceeding_max_uint64_t) + { + if (std::numeric_limits::is_iec559) + { + // Check forced scientific formatting for smaller numbers. + etl::u16string<64> s0; + etl::to_string(1000.0, s0, Format().precision(5).width(15).right().scientific(true)); + CHECK(etl::u16string<64>(STR(" 1.00000e+3")) == s0); + + // Maximum double value is 1.7976931348623157e+308, which rounds to 1.79769e+308 with 5 digits of precision. + etl::u16string<64> s1; + etl::to_string(std::numeric_limits::max(), s1, Format().precision(5).width(15).right()); + CHECK(etl::u16string<64>(STR(" 1.79769e+308")) == s1); + + // Negative maximum double value is -1.7976931348623157e+308, which rounds to -1.79769e+308 with 5 digits of precision. + etl::u16string<64> s2; + etl::to_string(-std::numeric_limits::max(), s2, Format().precision(5).width(15).right()); + CHECK(etl::u16string<64>(STR(" -1.79769e+308")) == s2); + } + +#if ETL_USING_64BIT_TYPES + using workspace_t = etl::private_to_string::workspace_t; + + // Maximum workspace_t value is 9223372036854775807. + etl::u16string<64> s3; + etl::to_string(std::numeric_limits::max(), s3, Format().precision(5).width(21).right()); + CHECK(etl::u16string<64>(STR(" 9223372036854775807")) == s3); + + // Minimum workspace_t value is -9223372036854775808. + etl::u16string<64> s4; + etl::to_string(std::numeric_limits::min(), s4, Format().precision(5).width(21).right()); + CHECK(etl::u16string<64>(STR(" -9223372036854775808")) == s4); + + using uworkspace_t = etl::private_to_string::uworkspace_t; + + // Maximum uworkspace_t value is 18446744073709551615. + etl::u16string<64> s5; + etl::to_string(std::numeric_limits::max(), s5, Format().precision(5).width(21).right()); + CHECK(etl::u16string<64>(STR(" 18446744073709551615")) == s5); + + // Minimum uworkspace_t value is 0. + etl::u16string<64> s6; + etl::to_string(std::numeric_limits::min(), s6, Format().precision(5).width(21).right()); + CHECK(etl::u16string<64>(STR(" 0")) == s6); +#endif + } } } // namespace diff --git a/test/test_to_u32string.cpp b/test/test_to_u32string.cpp index 9468ce45..22d19256 100644 --- a/test/test_to_u32string.cpp +++ b/test/test_to_u32string.cpp @@ -582,5 +582,53 @@ namespace CHECK(etl::u32string<20>(STR("-124.0000")) == result_i); CHECK(result_d == result_i); } + + //************************************************************************* + TEST(test_issue_1438_does_not_handle_floating_point_number_with_integer_part_exceeding_max_uint64_t) + { + if (std::numeric_limits::is_iec559) + { + // Check forced scientific formatting for smaller numbers. + etl::u32string<64> s0; + etl::to_string(1000.0, s0, Format().precision(5).width(15).right().scientific(true)); + CHECK(etl::u32string<64>(STR(" 1.00000e+3")) == s0); + + // Maximum double value is 1.7976931348623157e+308, which rounds to 1.79769e+308 with 5 digits of precision. + etl::u32string<64> s1; + etl::to_string(std::numeric_limits::max(), s1, Format().precision(5).width(15).right()); + CHECK(etl::u32string<64>(STR(" 1.79769e+308")) == s1); + + // Negative maximum double value is -1.7976931348623157e+308, which rounds to -1.79769e+308 with 5 digits of precision. + etl::u32string<64> s2; + etl::to_string(-std::numeric_limits::max(), s2, Format().precision(5).width(15).right()); + CHECK(etl::u32string<64>(STR(" -1.79769e+308")) == s2); + } + +#if ETL_USING_64BIT_TYPES + using workspace_t = etl::private_to_string::workspace_t; + + // Maximum workspace_t value is 9223372036854775807. + etl::u32string<64> s3; + etl::to_string(std::numeric_limits::max(), s3, Format().precision(5).width(21).right()); + CHECK(etl::u32string<64>(STR(" 9223372036854775807")) == s3); + + // Minimum workspace_t value is -9223372036854775808. + etl::u32string<64> s4; + etl::to_string(std::numeric_limits::min(), s4, Format().precision(5).width(21).right()); + CHECK(etl::u32string<64>(STR(" -9223372036854775808")) == s4); + + using uworkspace_t = etl::private_to_string::uworkspace_t; + + // Maximum uworkspace_t value is 18446744073709551615. + etl::u32string<64> s5; + etl::to_string(std::numeric_limits::max(), s5, Format().precision(5).width(21).right()); + CHECK(etl::u32string<64>(STR(" 18446744073709551615")) == s5); + + // Minimum uworkspace_t value is 0. + etl::u32string<64> s6; + etl::to_string(std::numeric_limits::min(), s6, Format().precision(5).width(21).right()); + CHECK(etl::u32string<64>(STR(" 0")) == s6); +#endif + } } } // namespace diff --git a/test/test_to_u8string.cpp b/test/test_to_u8string.cpp index 8f2c62e3..ea79a1f5 100644 --- a/test/test_to_u8string.cpp +++ b/test/test_to_u8string.cpp @@ -581,6 +581,54 @@ namespace CHECK(etl::u8string<20>(STR("-124.0000")) == result_i); CHECK(result_d == result_i); } + + //************************************************************************* + TEST(test_issue_1438_does_not_handle_floating_point_number_with_integer_part_exceeding_max_uint64_t) + { + if (std::numeric_limits::is_iec559) + { + // Check forced scientific formatting for smaller numbers. + etl::u8string<64> s0; + etl::to_string(1000.0, s0, Format().precision(5).width(15).right().scientific(true)); + CHECK(etl::u8string<64>(STR(" 1.00000e+3")) == s0); + + // Maximum double value is 1.7976931348623157e+308, which rounds to 1.79769e+308 with 5 digits of precision. + etl::u8string<64> s1; + etl::to_string(std::numeric_limits::max(), s1, Format().precision(5).width(15).right()); + CHECK(etl::u8string<64>(STR(" 1.79769e+308")) == s1); + + // Negative maximum double value is -1.7976931348623157e+308, which rounds to -1.79769e+308 with 5 digits of precision. + etl::u8string<64> s2; + etl::to_string(-std::numeric_limits::max(), s2, Format().precision(5).width(15).right()); + CHECK(etl::u8string<64>(STR(" -1.79769e+308")) == s2); + } + +#if ETL_USING_64BIT_TYPES + using workspace_t = etl::private_to_string::workspace_t; + + // Maximum workspace_t value is 9223372036854775807. + etl::u8string<64> s3; + etl::to_string(std::numeric_limits::max(), s3, Format().precision(5).width(21).right()); + CHECK(etl::u8string<64>(STR(" 9223372036854775807")) == s3); + + // Minimum workspace_t value is -9223372036854775808. + etl::u8string<64> s4; + etl::to_string(std::numeric_limits::min(), s4, Format().precision(5).width(21).right()); + CHECK(etl::u8string<64>(STR(" -9223372036854775808")) == s4); + + using uworkspace_t = etl::private_to_string::uworkspace_t; + + // Maximum uworkspace_t value is 18446744073709551615. + etl::u8string<64> s5; + etl::to_string(std::numeric_limits::max(), s5, Format().precision(5).width(21).right()); + CHECK(etl::u8string<64>(STR(" 18446744073709551615")) == s5); + + // Minimum uworkspace_t value is 0. + etl::u8string<64> s6; + etl::to_string(std::numeric_limits::min(), s6, Format().precision(5).width(21).right()); + CHECK(etl::u8string<64>(STR(" 0")) == s6); +#endif + } } } // namespace diff --git a/test/test_to_wstring.cpp b/test/test_to_wstring.cpp index 0c9d6fc3..0ea9166f 100644 --- a/test/test_to_wstring.cpp +++ b/test/test_to_wstring.cpp @@ -645,5 +645,53 @@ namespace CHECK(etl::wstring<20>(STR("-124.0000")) == result_i); CHECK(result_d == result_i); } + + //************************************************************************* + TEST(test_issue_1438_does_not_handle_floating_point_number_with_integer_part_exceeding_max_uint64_t) + { + if (std::numeric_limits::is_iec559) + { + // Check forced scientific formatting for smaller numbers. + etl::wstring<64> s0; + etl::to_string(1000.0, s0, Format().precision(5).width(15).right().scientific(true)); + CHECK(etl::wstring<64>(STR(" 1.00000e+3")) == s0); + + // Maximum double value is 1.7976931348623157e+308, which rounds to 1.79769e+308 with 5 digits of precision. + etl::wstring<64> s1; + etl::to_string(std::numeric_limits::max(), s1, Format().precision(5).width(15).right()); + CHECK(etl::wstring<64>(STR(" 1.79769e+308")) == s1); + + // Negative maximum double value is -1.7976931348623157e+308, which rounds to -1.79769e+308 with 5 digits of precision. + etl::wstring<64> s2; + etl::to_string(-std::numeric_limits::max(), s2, Format().precision(5).width(15).right()); + CHECK(etl::wstring<64>(STR(" -1.79769e+308")) == s2); + } + +#if ETL_USING_64BIT_TYPES + using workspace_t = etl::private_to_string::workspace_t; + + // Maximum workspace_t value is 9223372036854775807. + etl::wstring<64> s3; + etl::to_string(std::numeric_limits::max(), s3, Format().precision(5).width(21).right()); + CHECK(etl::wstring<64>(STR(" 9223372036854775807")) == s3); + + // Minimum workspace_t value is -9223372036854775808. + etl::wstring<64> s4; + etl::to_string(std::numeric_limits::min(), s4, Format().precision(5).width(21).right()); + CHECK(etl::wstring<64>(STR(" -9223372036854775808")) == s4); + + using uworkspace_t = etl::private_to_string::uworkspace_t; + + // Maximum uworkspace_t value is 18446744073709551615. + etl::wstring<64> s5; + etl::to_string(std::numeric_limits::max(), s5, Format().precision(5).width(21).right()); + CHECK(etl::wstring<64>(STR(" 18446744073709551615")) == s5); + + // Minimum uworkspace_t value is 0. + etl::wstring<64> s6; + etl::to_string(std::numeric_limits::min(), s6, Format().precision(5).width(21).right()); + CHECK(etl::wstring<64>(STR(" 0")) == s6); +#endif + } } } // namespace