From 535218291357956a1a9e29e3ccc043e02df80a68 Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Sat, 6 Jun 2026 13:54:12 +0100 Subject: [PATCH] Forces floating point numbers that are larger than than the internal integral workspace types to be formatted in scientific format. Also allows scientific format to be forced for all floating point output. NAN and INF outputs now follow case settings, as does the new scientific exponent letter. 'e' or 'E'. --- include/etl/private/to_string_helper.h | 182 +++++++++++++++++++++---- test/test_to_string.cpp | 59 ++++++-- test/test_to_u16string.cpp | 48 +++++++ test/test_to_u32string.cpp | 48 +++++++ test/test_to_u8string.cpp | 48 +++++++ test/test_to_wstring.cpp | 48 +++++++ 6 files changed, 394 insertions(+), 39 deletions(-) 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