mirror of
https://github.com/ETLCPP/etl.git
synced 2026-06-15 08:26:04 +08:00
Fix format: float zero-padding, nested replacement width, octal alternate form, and extreme doubles (#1442)
format.h:
- Fix float zero-padding ({:010f}): output sign first, then zero-fill,
then the unsigned formatted value, matching std::format behavior.
- Fix nested replacement fields ({:{}d}): consume the value's auto-index
in parse_format_spec before parsing nested width/precision fields, so
auto-indexing order matches the C++ standard.
- Fix {:#o} with value 0: produce "0" instead of "00" by skipping the
octal prefix when the value is zero.
- Fix format_floating_default overflow for extreme doubles (DBL_MIN,
DBL_MAX): fall back to scientific notation for values >= 1e18 or
tiny positives < 1e-6, delegating to format_floating_e.
- Fix format_floating_e precision loss: replace iterative multiply-by-10
normalization loop with O(1) log10/pow/floor computation.
- Add resolve_nested_replacements helper to extract width/precision
from format args at formatting time.
test_format.cpp:
- Add tests for float zero-padding, nested replacement width, octal
alternate form with zero, float sign/width/alignment, negative floats,
scientific notation for large/small values, default-to-scientific
switch, positive zero, brace escaping, and integer limits.
format.h + platform.h:
- log10l fix for different toolchain support:
Define ETL_FORMAT_NO_LONG_DOUBLE_MATH in the profile if libm doesn't
provide log10l. This is identified by linker error missing this symbol.
It was identified with the llvm/clang cross toolchain for ARM.
This commit is contained in:
parent
41174ed7f6
commit
9765cbf764
@ -1104,7 +1104,20 @@ namespace etl
|
||||
|
||||
format_spec = format_spec_t(); // reset format_spec to defaults
|
||||
|
||||
format_spec.index = parse_num(parse_ctx); // optional
|
||||
format_spec.index = parse_num(parse_ctx); // optional explicit index
|
||||
|
||||
// Consume the value's auto-index before parsing the format spec body,
|
||||
// so that nested replacement fields for width/precision get correct
|
||||
// auto-indices. Per C++ standard, in {:{}}, the value arg is consumed
|
||||
// first (arg 0), then the width arg (arg 1).
|
||||
if (!format_spec.index.has_value())
|
||||
{
|
||||
format_spec.index = parse_ctx.next_arg_id();
|
||||
}
|
||||
else
|
||||
{
|
||||
parse_ctx.check_arg_id(*format_spec.index);
|
||||
}
|
||||
|
||||
bool colon = parse_char(parse_ctx, ':');
|
||||
if (colon)
|
||||
@ -1239,7 +1252,7 @@ namespace etl
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename T>
|
||||
void format_alternate_form(OutputIt& it, const format_spec_t& spec)
|
||||
void format_alternate_form(OutputIt& it, T value, const format_spec_t& spec)
|
||||
{
|
||||
if (spec.hash && spec.type.has_value())
|
||||
{
|
||||
@ -1247,7 +1260,13 @@ namespace etl
|
||||
{
|
||||
case 'b': format_sequence(it, "0b"); break;
|
||||
case 'B': format_sequence(it, "0B"); break;
|
||||
case 'o': format_sequence(it, "0"); break;
|
||||
case 'o':
|
||||
// Per C++ standard, # for octal adds leading 0 only if not already present
|
||||
if (value != 0)
|
||||
{
|
||||
format_sequence(it, "0");
|
||||
}
|
||||
break;
|
||||
case 'x': format_sequence(it, "0x"); break;
|
||||
case 'X':
|
||||
format_sequence(it, "0X");
|
||||
@ -1401,13 +1420,72 @@ namespace etl
|
||||
{
|
||||
size_t width = 0;
|
||||
format_sign<OutputIt, T>(it, value, spec);
|
||||
format_alternate_form<OutputIt, T>(it, spec);
|
||||
format_alternate_form<OutputIt, T>(it, value, spec);
|
||||
adjust_width_from_spec(spec, width);
|
||||
check_precision(spec);
|
||||
format_plain_num(it, value, spec, width);
|
||||
}
|
||||
|
||||
#if ETL_USING_FORMAT_FLOATING_POINT
|
||||
#if ETL_NOT_USING_FORMAT_LONG_DOUBLE_MATH
|
||||
//***********************************
|
||||
// Math function wrappers to handle toolchains that don't provide
|
||||
// long double math functions (log10l, floorl, powl, modfl, roundl).
|
||||
// When ETL_FORMAT_NO_LONG_DOUBLE_MATH is defined, long double overloads
|
||||
// cast through double. For float and double, the standard functions are
|
||||
// called directly via the template versions.
|
||||
//***********************************
|
||||
inline long double format_log10(long double value)
|
||||
{
|
||||
return static_cast<long double>(::log10(static_cast<double>(value)));
|
||||
}
|
||||
inline long double format_floor(long double value)
|
||||
{
|
||||
return static_cast<long double>(::floor(static_cast<double>(value)));
|
||||
}
|
||||
inline long double format_pow(long double base, long double exp)
|
||||
{
|
||||
return static_cast<long double>(::pow(static_cast<double>(base), static_cast<double>(exp)));
|
||||
}
|
||||
inline long double format_round(long double value)
|
||||
{
|
||||
return static_cast<long double>(::round(static_cast<double>(value)));
|
||||
}
|
||||
inline long double format_modf(long double value, long double* iptr)
|
||||
{
|
||||
double d_iptr;
|
||||
double result = ::modf(static_cast<double>(value), &d_iptr);
|
||||
*iptr = static_cast<long double>(d_iptr);
|
||||
return static_cast<long double>(result);
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
T format_log10(T value)
|
||||
{
|
||||
return ::log10(value);
|
||||
}
|
||||
template <typename T>
|
||||
T format_floor(T value)
|
||||
{
|
||||
return ::floor(value);
|
||||
}
|
||||
template <typename T>
|
||||
T format_pow(T base, T exp)
|
||||
{
|
||||
return ::pow(base, exp);
|
||||
}
|
||||
template <typename T>
|
||||
T format_round(T value)
|
||||
{
|
||||
return ::round(value);
|
||||
}
|
||||
template <typename T>
|
||||
T format_modf(T value, T* iptr)
|
||||
{
|
||||
return ::modf(value, iptr);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename T>
|
||||
void format_floating_default(OutputIt& it, T value, const format_spec_t& spec)
|
||||
{
|
||||
@ -1415,9 +1493,20 @@ namespace etl
|
||||
|
||||
// Detect sign using signbit to correctly handle -0.0
|
||||
bool sign = signbit(value);
|
||||
T abs_value = sign ? -value : value;
|
||||
|
||||
// Use scientific notation for values that would overflow unsigned long long
|
||||
// or that are too small for meaningful fixed-point digits
|
||||
if (abs_value >= static_cast<T>(1e18) || (abs_value > static_cast<T>(0) && abs_value < static_cast<T>(1e-6)))
|
||||
{
|
||||
format_spec_t spec_e = spec;
|
||||
spec_e.type = 'e';
|
||||
format_floating_e(it, value, spec_e);
|
||||
return;
|
||||
}
|
||||
|
||||
T integral;
|
||||
T fractional = modf(value, &integral);
|
||||
T fractional = format_modf(value, &integral);
|
||||
|
||||
// Take absolute values to avoid casting negative values to unsigned
|
||||
if (sign)
|
||||
@ -1427,7 +1516,7 @@ namespace etl
|
||||
}
|
||||
|
||||
unsigned long long int scale = int_pow<unsigned long long int>(10, fractional_decimals);
|
||||
unsigned long long int fractional_int = static_cast<unsigned long long int>(round(fractional * scale));
|
||||
unsigned long long int fractional_int = static_cast<unsigned long long int>(format_round(fractional * scale));
|
||||
unsigned long long int integral_int = static_cast<unsigned long long int>(integral);
|
||||
|
||||
if (fractional_int == scale)
|
||||
@ -1454,20 +1543,20 @@ namespace etl
|
||||
bool sign = signbit(value);
|
||||
|
||||
T integral;
|
||||
T fractional = modf(value, &integral);
|
||||
T fractional = format_modf(value, &integral);
|
||||
|
||||
while (value >= 0x10 || value <= -0x10)
|
||||
{
|
||||
++exponent_int;
|
||||
value /= 0x10;
|
||||
fractional = modf(value, &integral);
|
||||
fractional = format_modf(value, &integral);
|
||||
}
|
||||
|
||||
while ((value > 0.0000000000001 && value < 1) || (value < -0.0000000000001 && value > -1))
|
||||
{
|
||||
--exponent_int;
|
||||
value *= 0x10;
|
||||
fractional = modf(value, &integral);
|
||||
fractional = format_modf(value, &integral);
|
||||
}
|
||||
|
||||
// Take absolute values to avoid casting negative values to unsigned
|
||||
@ -1478,7 +1567,7 @@ namespace etl
|
||||
}
|
||||
|
||||
unsigned long long int scale = int_pow<unsigned long long int>(0x10, fractional_decimals);
|
||||
unsigned long long int fractional_int = static_cast<unsigned long long int>(round(fractional * scale));
|
||||
unsigned long long int fractional_int = static_cast<unsigned long long int>(format_round(fractional * scale));
|
||||
unsigned long long int integral_int = static_cast<unsigned long long int>(integral);
|
||||
|
||||
if (fractional_int == scale)
|
||||
@ -1516,40 +1605,48 @@ namespace etl
|
||||
long long int exponent_int = 0;
|
||||
|
||||
// Detect sign using signbit to correctly handle -0.0
|
||||
bool sign = std::signbit(value);
|
||||
bool sign = signbit(value);
|
||||
|
||||
T abs_value = sign ? -value : value;
|
||||
|
||||
if (abs_value > static_cast<T>(0))
|
||||
{
|
||||
exponent_int = static_cast<long long int>(format_floor(format_log10(abs_value)));
|
||||
value = abs_value / format_pow(static_cast<T>(10), static_cast<T>(exponent_int));
|
||||
// Correct for floating-point rounding in log10/pow
|
||||
if (value >= static_cast<T>(10))
|
||||
{
|
||||
value /= static_cast<T>(10);
|
||||
++exponent_int;
|
||||
}
|
||||
else if (value < static_cast<T>(1))
|
||||
{
|
||||
value *= static_cast<T>(10);
|
||||
--exponent_int;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value = static_cast<T>(0);
|
||||
}
|
||||
|
||||
T integral;
|
||||
T fractional = modf(value, &integral);
|
||||
|
||||
while (value >= 10 || value <= -10)
|
||||
{
|
||||
++exponent_int;
|
||||
value /= 10;
|
||||
fractional = modf(value, &integral);
|
||||
}
|
||||
|
||||
while ((value > 0.0000000000001 && value < 1) || (value < -0.0000000000001 && value > -1))
|
||||
{
|
||||
--exponent_int;
|
||||
value *= 10;
|
||||
fractional = modf(value, &integral);
|
||||
}
|
||||
|
||||
// Take absolute values to avoid casting negative values to unsigned
|
||||
if (sign)
|
||||
{
|
||||
fractional = -fractional;
|
||||
integral = -integral;
|
||||
}
|
||||
T fractional = format_modf(value, &integral);
|
||||
|
||||
unsigned long long int scale = int_pow<unsigned long long int>(10, fractional_decimals);
|
||||
unsigned long long int fractional_int = static_cast<unsigned long long int>(round(fractional * scale));
|
||||
unsigned long long int fractional_int = static_cast<unsigned long long int>(format_round(fractional * scale));
|
||||
unsigned long long int integral_int = static_cast<unsigned long long int>(integral);
|
||||
|
||||
if (fractional_int == scale)
|
||||
{
|
||||
fractional_int = 0;
|
||||
++integral_int;
|
||||
|
||||
if (integral_int == 10)
|
||||
{
|
||||
integral_int = 1;
|
||||
++exponent_int;
|
||||
}
|
||||
}
|
||||
|
||||
private_format::format_sign<OutputIt, int>(it, sign ? -1 : 0, spec);
|
||||
@ -1572,10 +1669,10 @@ namespace etl
|
||||
const size_t fractional_decimals = 6; // default
|
||||
|
||||
// Detect sign using signbit to correctly handle -0.0
|
||||
bool sign = std::signbit(value);
|
||||
bool sign = signbit(value);
|
||||
|
||||
T integral;
|
||||
T fractional = modf(value, &integral);
|
||||
T fractional = format_modf(value, &integral);
|
||||
|
||||
// Take absolute values to avoid casting negative values to unsigned
|
||||
if (sign)
|
||||
@ -1585,7 +1682,7 @@ namespace etl
|
||||
}
|
||||
|
||||
unsigned long long int scale = int_pow<unsigned long long int>(10, fractional_decimals);
|
||||
unsigned long long int fractional_int = static_cast<unsigned long long int>(round(fractional * scale));
|
||||
unsigned long long int fractional_int = static_cast<unsigned long long int>(format_round(fractional * scale));
|
||||
unsigned long long int integral_int = static_cast<unsigned long long int>(integral);
|
||||
|
||||
if (fractional_int == scale)
|
||||
@ -1833,6 +1930,64 @@ namespace etl
|
||||
fmt_context.advance_to(tmp);
|
||||
}
|
||||
|
||||
// Visitor to extract an integer value as size_t from a format arg (for nested replacement fields)
|
||||
struct size_t_extractor
|
||||
{
|
||||
size_t value;
|
||||
|
||||
size_t_extractor()
|
||||
: value(0)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(int v)
|
||||
{
|
||||
value = static_cast<size_t>(v);
|
||||
}
|
||||
void operator()(unsigned int v)
|
||||
{
|
||||
value = static_cast<size_t>(v);
|
||||
}
|
||||
void operator()(long long int v)
|
||||
{
|
||||
value = static_cast<size_t>(v);
|
||||
}
|
||||
void operator()(unsigned long long int v)
|
||||
{
|
||||
value = static_cast<size_t>(v);
|
||||
}
|
||||
|
||||
// All other types are invalid for width/precision - ignore
|
||||
template <typename T>
|
||||
void operator()(T)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// Resolve nested replacement fields for width and precision in the format spec.
|
||||
// When width_nested_replacement or precision_nested_replacement is true, the
|
||||
// width/precision value holds the arg index, which must be resolved to the actual value.
|
||||
template <class OutputIt>
|
||||
void resolve_nested_replacements(format_spec_t& spec, format_args<OutputIt>& args)
|
||||
{
|
||||
if (spec.width_nested_replacement && spec.width.has_value())
|
||||
{
|
||||
format_arg<OutputIt> width_arg = args.get(spec.width.value());
|
||||
size_t_extractor ext;
|
||||
width_arg.template visit<void>(ext);
|
||||
spec.width = ext.value;
|
||||
spec.width_nested_replacement = false;
|
||||
}
|
||||
if (spec.precision_nested_replacement && spec.precision.has_value())
|
||||
{
|
||||
format_arg<OutputIt> prec_arg = args.get(spec.precision.value());
|
||||
size_t_extractor ext;
|
||||
prec_arg.template visit<void>(ext);
|
||||
spec.precision = ext.value;
|
||||
spec.precision_nested_replacement = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute prefix/suffix padding sizes for alignment.
|
||||
// default_align_start: if true, NONE defaults to left-align (START); otherwise right-align (END).
|
||||
inline void compute_padding(size_t pad, spec_align_t align, bool default_align_start, size_t& prefix_size, size_t& suffix_size)
|
||||
@ -1900,6 +2055,13 @@ namespace etl
|
||||
size_t prefix_size = 0;
|
||||
size_t suffix_size = 0;
|
||||
|
||||
// For zero-padding ({:0Nf}), use '0' as fill and right-align (padding after sign)
|
||||
char_type fill_char = fmt_ctx.format_spec.fill;
|
||||
if (fmt_ctx.format_spec.zero && fmt_ctx.format_spec.align == spec_align_t::NONE)
|
||||
{
|
||||
fill_char = '0';
|
||||
}
|
||||
|
||||
if (fmt_ctx.format_spec.width)
|
||||
{
|
||||
private_format::counter_iterator counter;
|
||||
@ -1908,15 +2070,61 @@ namespace etl
|
||||
if (counter.value() < fmt_ctx.format_spec.width.value())
|
||||
{
|
||||
size_t pad = fmt_ctx.format_spec.width.value() - counter.value();
|
||||
if (fmt_ctx.format_spec.zero && fmt_ctx.format_spec.align == spec_align_t::NONE)
|
||||
{
|
||||
// Zero-padding: all padding goes between sign and digits
|
||||
prefix_size = pad;
|
||||
}
|
||||
else
|
||||
{
|
||||
compute_padding(pad, fmt_ctx.format_spec.align, false, prefix_size, suffix_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// actual output
|
||||
OutputIt it = fmt_ctx.out();
|
||||
private_format::fill<OutputIt>(it, prefix_size, fmt_ctx.format_spec.fill);
|
||||
|
||||
if (fmt_ctx.format_spec.zero && fmt_ctx.format_spec.align == spec_align_t::NONE)
|
||||
{
|
||||
// Output sign first, then zero-fill, then the unsigned part
|
||||
bool sign = signbit(arg);
|
||||
if (sign || fmt_ctx.format_spec.sign != spec_sign_t::MINUS)
|
||||
{
|
||||
// Output the sign character
|
||||
char_type sc = '\0';
|
||||
if (sign)
|
||||
{
|
||||
sc = '-';
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (fmt_ctx.format_spec.sign)
|
||||
{
|
||||
case spec_sign_t::PLUS: sc = '+'; break;
|
||||
case spec_sign_t::SPACE: sc = ' '; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
if (sc != '\0')
|
||||
{
|
||||
*it = sc;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
private_format::fill<OutputIt>(it, prefix_size, '0');
|
||||
// Format without sign (sign already emitted)
|
||||
format_spec_t no_sign_spec = fmt_ctx.format_spec;
|
||||
no_sign_spec.sign = spec_sign_t::MINUS;
|
||||
Float abs_arg = sign ? -arg : arg;
|
||||
private_format::format_floating<OutputIt, Float>(it, abs_arg, no_sign_spec);
|
||||
}
|
||||
else
|
||||
{
|
||||
private_format::fill<OutputIt>(it, prefix_size, fill_char);
|
||||
private_format::format_floating<OutputIt, Float>(it, arg, fmt_ctx.format_spec);
|
||||
private_format::fill<OutputIt>(it, suffix_size, fmt_ctx.format_spec.fill);
|
||||
private_format::fill<OutputIt>(it, suffix_size, fill_char);
|
||||
}
|
||||
return it;
|
||||
}
|
||||
#endif
|
||||
@ -2414,16 +2622,13 @@ namespace etl
|
||||
else
|
||||
{
|
||||
private_format::parse_format_spec<OutputIt>(parse_context, fmt_context);
|
||||
etl::optional<size_t> index = fmt_context.format_spec.index;
|
||||
if (index.has_value())
|
||||
{
|
||||
parse_context.check_arg_id(*index);
|
||||
}
|
||||
else
|
||||
{
|
||||
index = parse_context.next_arg_id();
|
||||
}
|
||||
format_arg<OutputIt> arg = args.get(*index);
|
||||
|
||||
// Resolve nested replacement fields for width/precision
|
||||
private_format::resolve_nested_replacements<OutputIt>(fmt_context.format_spec, args);
|
||||
|
||||
// Value index is always resolved in parse_format_spec
|
||||
size_t index = fmt_context.format_spec.index.value();
|
||||
format_arg<OutputIt> arg = args.get(index);
|
||||
arg.template visit<void>(v);
|
||||
|
||||
ETL_ASSERT(*parse_context.begin() == '}', ETL_ERROR(bad_format_string_exception) /*"Closing brace missing"*/);
|
||||
|
||||
@ -174,6 +174,19 @@ SOFTWARE.
|
||||
#define ETL_NOT_USING_FORMAT_FLOATING_POINT 0
|
||||
#endif
|
||||
|
||||
//*************************************
|
||||
// Helper macro for ETL_FORMAT_NO_LONG_DOUBLE_MATH.
|
||||
// Define ETL_FORMAT_NO_LONG_DOUBLE_MATH if the toolchain does not provide
|
||||
// long double math functions (log10l, floorl, powl, modfl, roundl).
|
||||
// When defined, long double arguments are cast to double for math operations.
|
||||
#if defined(ETL_FORMAT_NO_LONG_DOUBLE_MATH)
|
||||
#define ETL_USING_FORMAT_LONG_DOUBLE_MATH 0
|
||||
#define ETL_NOT_USING_FORMAT_LONG_DOUBLE_MATH 1
|
||||
#else
|
||||
#define ETL_USING_FORMAT_LONG_DOUBLE_MATH 1
|
||||
#define ETL_NOT_USING_FORMAT_LONG_DOUBLE_MATH 0
|
||||
#endif
|
||||
|
||||
//*************************************
|
||||
// Figure out things about the compiler, if haven't already done so in
|
||||
// etl_profile.h
|
||||
|
||||
@ -217,6 +217,8 @@ namespace
|
||||
CHECK_EQUAL("1.234567", test_format(s, "{}", 1.234567499));
|
||||
CHECK_EQUAL("1.234568", test_format(s, "{}", 1.234567501));
|
||||
CHECK_EQUAL("1.5", test_format(s, "{}", 1.5));
|
||||
CHECK_EQUAL("2.225074e-308", test_format(s, "{}", etl::numeric_limits<double>::min()));
|
||||
CHECK_EQUAL("1.797693e+308", test_format(s, "{}", etl::numeric_limits<double>::max()));
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
@ -240,6 +242,7 @@ namespace
|
||||
CHECK_EQUAL("1.125000E+00", test_format(s, "{:E}", 1.125f));
|
||||
CHECK_EQUAL("-2.533324e-05", test_format(s, "{:e}", -0.00002533324f));
|
||||
CHECK_EQUAL("-2.500000e+11", test_format(s, "{:e}", -250000000000.0f));
|
||||
CHECK_EQUAL("1.000000e+01", test_format(s, "{:e}", 9.9999996));
|
||||
CHECK_EQUAL("1.000000", test_format(s, "{:f}", 1.0f));
|
||||
CHECK_EQUAL("1.125000", test_format(s, "{:F}", 1.125f));
|
||||
CHECK_EQUAL("1.000000", test_format(s, "{:g}", 1.0f));
|
||||
@ -326,9 +329,9 @@ namespace
|
||||
etl::string<100> s;
|
||||
|
||||
// 9.9999999: after normalization integral=9, fractional=0.9999999
|
||||
// round(0.9999999 * 1e6) == 1000000 => must carry: 10.000000e+00
|
||||
CHECK_EQUAL("10.000000e+00", test_format(s, "{:e}", 9.9999999));
|
||||
CHECK_EQUAL("-10.000000e+00", test_format(s, "{:e}", -9.9999999));
|
||||
// round(0.9999999 * 1e6) == 1000000 => must carry and renormalize: 1.000000e+01
|
||||
CHECK_EQUAL("1.000000e+01", test_format(s, "{:e}", 9.9999999));
|
||||
CHECK_EQUAL("-1.000000e+01", test_format(s, "{:e}", -9.9999999));
|
||||
|
||||
// 1.9999999: after normalization integral=1, fractional=0.9999999
|
||||
CHECK_EQUAL("2.000000e+00", test_format(s, "{:e}", 1.9999999));
|
||||
@ -787,6 +790,144 @@ namespace
|
||||
CHECK_THROW(test_format(s, "{:+#05.5X}", 0xEF1), etl::bad_format_string_exception);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ETL_USING_FORMAT_FLOATING_POINT
|
||||
//*************************************************************************
|
||||
TEST(test_format_float_sign)
|
||||
{
|
||||
etl::string<100> s;
|
||||
|
||||
CHECK_EQUAL("+1.500000", test_format(s, "{:+f}", 1.5));
|
||||
CHECK_EQUAL("1.500000", test_format(s, "{:-f}", 1.5));
|
||||
CHECK_EQUAL(" 1.500000", test_format(s, "{: f}", 1.5));
|
||||
CHECK_EQUAL("-1.500000", test_format(s, "{:+f}", -1.5));
|
||||
CHECK_EQUAL("-1.500000", test_format(s, "{: f}", -1.5));
|
||||
CHECK_EQUAL("+0.0", test_format(s, "{:+}", 0.0));
|
||||
CHECK_EQUAL(" 0.0", test_format(s, "{: }", 0.0));
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
TEST(test_format_float_width_align)
|
||||
{
|
||||
etl::string<100> s;
|
||||
|
||||
// {:f} with width
|
||||
CHECK_EQUAL(" 1.500000", test_format(s, "{:10f}", 1.5));
|
||||
CHECK_EQUAL("1.500000 ", test_format(s, "{:<10f}", 1.5));
|
||||
CHECK_EQUAL(" 1.500000", test_format(s, "{:>10f}", 1.5));
|
||||
CHECK_EQUAL(" 1.500000 ", test_format(s, "{:^10f}", 1.5));
|
||||
CHECK_EQUAL("*1.500000*", test_format(s, "{:*^10f}", 1.5));
|
||||
|
||||
// {:f} with width and sign
|
||||
CHECK_EQUAL(" +1.500000", test_format(s, "{:+10f}", 1.5));
|
||||
CHECK_EQUAL(" -1.500000", test_format(s, "{:+10f}", -1.5));
|
||||
|
||||
// {:e} with width
|
||||
CHECK_EQUAL(" 1.500000e+00", test_format(s, "{:15e}", 1.5));
|
||||
CHECK_EQUAL("1.500000e+00 ", test_format(s, "{:<15e}", 1.5));
|
||||
CHECK_EQUAL(" 1.500000e+00", test_format(s, "{:>15e}", 1.5));
|
||||
CHECK_EQUAL(" 1.500000e+00 ", test_format(s, "{:^15e}", 1.5));
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
TEST(test_format_negative_floats)
|
||||
{
|
||||
etl::string<100> s;
|
||||
|
||||
CHECK_EQUAL("-1.5", test_format(s, "{}", -1.5));
|
||||
CHECK_EQUAL("-123.456", test_format(s, "{}", -123.456));
|
||||
CHECK_EQUAL("-1.234560e+02", test_format(s, "{:e}", -123.456));
|
||||
CHECK_EQUAL("-123.456000", test_format(s, "{:f}", -123.456));
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
TEST(test_format_float_scientific_large_small)
|
||||
{
|
||||
etl::string<100> s;
|
||||
|
||||
// Large and small values with {:e}
|
||||
CHECK_EQUAL("1.000000e+100", test_format(s, "{:e}", 1e100));
|
||||
CHECK_EQUAL("1.000000e-100", test_format(s, "{:e}", 1e-100));
|
||||
CHECK_EQUAL("-1.000000e+100", test_format(s, "{:e}", -1e100));
|
||||
CHECK_EQUAL("-1.000000e-100", test_format(s, "{:e}", -1e-100));
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
TEST(test_format_float_default_scientific_switch)
|
||||
{
|
||||
etl::string<100> s;
|
||||
|
||||
// Values at the boundary of fixed vs scientific in default presentation
|
||||
CHECK_EQUAL("0.000001", test_format(s, "{}", 0.000001));
|
||||
CHECK_EQUAL("1.000000e-07", test_format(s, "{}", 0.0000001));
|
||||
CHECK_EQUAL("-1.000000e-07", test_format(s, "{}", -0.0000001));
|
||||
CHECK_EQUAL("123456789012345.0", test_format(s, "{}", 123456789012345.0));
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
TEST(test_format_positive_zero)
|
||||
{
|
||||
etl::string<100> s;
|
||||
|
||||
CHECK_EQUAL("0.0", test_format(s, "{}", 0.0));
|
||||
CHECK_EQUAL("0.000000e+00", test_format(s, "{:e}", 0.0));
|
||||
CHECK_EQUAL("0.000000", test_format(s, "{:f}", 0.0));
|
||||
}
|
||||
#endif
|
||||
|
||||
//*************************************************************************
|
||||
TEST(test_format_brace_escaping)
|
||||
{
|
||||
etl::string<100> s;
|
||||
|
||||
CHECK_EQUAL("{}", test_format(s, "{{}}"));
|
||||
CHECK_EQUAL("{42}", test_format(s, "{{{}}}", 42));
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
TEST(test_format_integer_limits)
|
||||
{
|
||||
etl::string<100> s;
|
||||
|
||||
CHECK_EQUAL("-2147483648", test_format(s, "{}", etl::numeric_limits<int>::min()));
|
||||
CHECK_EQUAL("2147483647", test_format(s, "{}", etl::numeric_limits<int>::max()));
|
||||
CHECK_EQUAL("ffffffffffffffff", test_format(s, "{:x}", static_cast<unsigned long long>(0xFFFFFFFFFFFFFFFFULL)));
|
||||
#if ETL_USING_CPP14
|
||||
CHECK_EQUAL("0b0", test_format(s, "{:#b}", 0));
|
||||
#endif
|
||||
CHECK_EQUAL("0x0", test_format(s, "{:#x}", 0));
|
||||
}
|
||||
|
||||
TEST(test_format_float_zero_padding)
|
||||
{
|
||||
etl::string<100> s;
|
||||
|
||||
CHECK_EQUAL("0000003.14", test_format(s, "{:010}", 3.14));
|
||||
CHECK_EQUAL("001.500000", test_format(s, "{:010f}", 1.5));
|
||||
CHECK_EQUAL("+01.500000", test_format(s, "{:+010f}", 1.5));
|
||||
CHECK_EQUAL("-01.500000", test_format(s, "{:010f}", -1.5));
|
||||
CHECK_EQUAL("+001.500000e+00", test_format(s, "{:+015e}", 1.5));
|
||||
CHECK_EQUAL("-001.500000e+00", test_format(s, "{:+015e}", -1.5));
|
||||
}
|
||||
|
||||
TEST(test_format_nested_replacement_width)
|
||||
{
|
||||
etl::string<100> s;
|
||||
|
||||
CHECK_EQUAL(" 42", test_format(s, "{:{}d}", 42, 10));
|
||||
CHECK_EQUAL(" 42", test_format(s, "{0:{1}d}", 42, 10));
|
||||
CHECK_EQUAL("hello ", test_format(s, "{:{}}", "hello", 10));
|
||||
CHECK_EQUAL("x 42", test_format(s, "{} {:{}d}", "x", 42, 10));
|
||||
}
|
||||
|
||||
TEST(test_format_octal_alternate_zero)
|
||||
{
|
||||
etl::string<100> s;
|
||||
|
||||
CHECK_EQUAL("0", test_format(s, "{:#o}", 0));
|
||||
CHECK_EQUAL("07", test_format(s, "{:#o}", 7));
|
||||
CHECK_EQUAL("010", test_format(s, "{:#o}", 8));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user