Move snprintf_float to format.h

This commit is contained in:
Victor Zverovich
2022-03-18 08:07:19 -07:00
parent 0419d23882
commit c076a54a4d
3 changed files with 118 additions and 131 deletions

View File

@@ -1325,10 +1325,116 @@ FMT_HEADER_ONLY_CONSTEXPR20 auto format_float(T value, int precision,
float_specs specs,
buffer<char>& buf) -> int;
#ifndef _MSC_VER
# define FMT_SNPRINTF snprintf
#else
FMT_API auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...) -> int;
# define FMT_SNPRINTF fmt_snprintf
#endif // _MSC_VER
// Formats a floating-point number with snprintf.
template <typename T>
auto snprintf_float(T value, int precision, float_specs specs,
buffer<char>& buf) -> int;
buffer<char>& buf) -> int {
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer");
static_assert(!std::is_same<T, float>::value, "");
// Subtract 1 to account for the difference in precision since we use %e for
// both general and exponent format.
if (specs.format == float_format::general ||
specs.format == float_format::exp) {
precision = (precision >= 0 ? precision : 6) - 1;
}
// Build the format string.
char format[7]; // The longest format is "%#.*Le".
char* format_ptr = format;
*format_ptr++ = '%';
if (specs.showpoint && specs.format == float_format::hex) *format_ptr++ = '#';
if (precision >= 0) {
*format_ptr++ = '.';
*format_ptr++ = '*';
}
if (std::is_same<T, long double>()) *format_ptr++ = 'L';
*format_ptr++ = specs.format != float_format::hex
? (specs.format == float_format::fixed ? 'f' : 'e')
: (specs.upper ? 'A' : 'a');
*format_ptr = '\0';
// Format using snprintf.
auto offset = buf.size();
for (;;) {
auto begin = buf.data() + offset;
auto capacity = buf.capacity() - offset;
#ifdef FMT_FUZZ
if (precision > 100000)
throw std::runtime_error("fuzz mode: avoid large allocation in snprintf");
#endif
// Suppress the warning about a nonliteral format string.
// Cannot use auto because of a bug in MinGW (#1532).
int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF;
int result = precision >= 0
? snprintf_ptr(begin, capacity, format, precision, value)
: snprintf_ptr(begin, capacity, format, value);
if (result < 0) {
// The buffer will grow exponentially.
buf.try_reserve(buf.capacity() + 1);
continue;
}
auto size = to_unsigned(result);
// Size equal to capacity means that the last character was truncated.
if (size >= capacity) {
buf.try_reserve(size + offset + 1); // Add 1 for the terminating '\0'.
continue;
}
auto is_digit = [](char c) { return c >= '0' && c <= '9'; };
if (specs.format == float_format::fixed) {
if (precision == 0) {
buf.try_resize(size);
return 0;
}
// Find and remove the decimal point.
auto end = begin + size, p = end;
do {
--p;
} while (is_digit(*p));
int fraction_size = static_cast<int>(end - p - 1);
std::memmove(p, p + 1, to_unsigned(fraction_size));
buf.try_resize(size - 1);
return -fraction_size;
}
if (specs.format == float_format::hex) {
buf.try_resize(size + offset);
return 0;
}
// Find and parse the exponent.
auto end = begin + size, exp_pos = end;
do {
--exp_pos;
} while (*exp_pos != 'e');
char sign = exp_pos[1];
FMT_ASSERT(sign == '+' || sign == '-', "");
int exp = 0;
auto p = exp_pos + 2; // Skip 'e' and sign.
do {
FMT_ASSERT(is_digit(*p), "");
exp = exp * 10 + (*p++ - '0');
} while (p != end);
if (sign == '-') exp = -exp;
int fraction_size = 0;
if (exp_pos != begin + 1) {
// Remove trailing zeros.
auto fraction_end = exp_pos - 1;
while (*fraction_end == '0') --fraction_end;
// Move the fractional part left to get rid of the decimal point.
fraction_size = static_cast<int>(fraction_end - begin - 1);
std::memmove(begin + 1, begin + 2, to_unsigned(fraction_size));
}
buf.try_resize(to_unsigned(fraction_size) + offset + 1);
return exp - fraction_size;
}
}
template <typename T>
using convert_float_result =
@@ -3203,14 +3309,6 @@ extern template auto format_float<double>(double value, int precision,
extern template auto format_float<long double>(long double value, int precision,
float_specs specs,
buffer<char>& buf) -> int;
void snprintf_float(float, int, float_specs, buffer<char>&) = delete;
extern template auto snprintf_float<double>(double value, int precision,
float_specs specs,
buffer<char>& buf) -> int;
extern template auto snprintf_float<long double>(long double value,
int precision,
float_specs specs,
buffer<char>& buf) -> int;
#endif // FMT_HEADER_ONLY
FMT_END_DETAIL_NAMESPACE