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'.
This commit is contained in:
John Wellbelove 2026-06-06 13:54:12 +01:00
parent 9d6e8dd92e
commit 5352182913
6 changed files with 394 additions and 39 deletions

View File

@ -218,20 +218,36 @@ namespace etl
/// Helper function for floating point nan and inf.
//***************************************************************************
template <typename TIString>
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<TIString>& 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 <typename T, typename TIString>
void add_floating_point_scientific(const T value, TIString& str, const etl::basic_format_spec<TIString>& 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<TIString> mantissa_integral_format = format;
mantissa_integral_format.decimal().width(0U).precision(0U);
etl::basic_format_spec<TIString> 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<uworkspace_t>(f_integral);
// Find the fractional part of the floating point.
uworkspace_t fractional = static_cast<uworkspace_t>(::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<uworkspace_t>(etl::absolute(exponent));
etl::basic_format_spec<TIString> 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 <typename T, typename TIString>
void add_floating_point_non_scientific(const T value, TIString& str, const etl::basic_format_spec<TIString>& format, const uint32_t max_precision)
{
typedef typename TIString::value_type type;
etl::basic_format_spec<TIString> integral_format = format;
integral_format.decimal().width(0).precision(format.get_precision() > max_precision ? max_precision : format.get_precision());
etl::basic_format_spec<TIString> 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<uworkspace_t>(f_integral);
// Find the fractional part of the floating point.
uworkspace_t fractional = static_cast<uworkspace_t>(::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<TIString> 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<T>(etl::numeric_limits<uworkspace_t>::max()));
etl::basic_format_spec<TIString> 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<uworkspace_t>(f_integral);
// Find the fractional part of the floating point.
uworkspace_t fractional = static_cast<uworkspace_t>(::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);
}

View File

@ -30,7 +30,7 @@ SOFTWARE.
#include <iomanip>
#include <ostream>
#include <sstream>
#include <format>
#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<double>::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<double>::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<double>::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<workspace_t>::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<workspace_t>::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<uworkspace_t>::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<uworkspace_t>::min(), s6, Format().precision(5).width(21).right());
CHECK(etl::string<64>(STR(" 0")) == s6);
#endif
}
}
} // namespace

View File

@ -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<double>::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<double>::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<double>::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<workspace_t>::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<workspace_t>::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<uworkspace_t>::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<uworkspace_t>::min(), s6, Format().precision(5).width(21).right());
CHECK(etl::u16string<64>(STR(" 0")) == s6);
#endif
}
}
} // namespace

View File

@ -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<double>::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<double>::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<double>::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<workspace_t>::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<workspace_t>::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<uworkspace_t>::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<uworkspace_t>::min(), s6, Format().precision(5).width(21).right());
CHECK(etl::u32string<64>(STR(" 0")) == s6);
#endif
}
}
} // namespace

View File

@ -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<double>::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<double>::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<double>::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<workspace_t>::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<workspace_t>::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<uworkspace_t>::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<uworkspace_t>::min(), s6, Format().precision(5).width(21).right());
CHECK(etl::u8string<64>(STR(" 0")) == s6);
#endif
}
}
} // namespace

View File

@ -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<double>::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<double>::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<double>::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<workspace_t>::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<workspace_t>::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<uworkspace_t>::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<uworkspace_t>::min(), s6, Format().precision(5).width(21).right());
CHECK(etl::wstring<64>(STR(" 0")) == s6);
#endif
}
}
} // namespace