mirror of
https://github.com/fmtlib/fmt.git
synced 2025-02-27 12:39:56 +00:00
Move snprintf_float to format.h
This commit is contained in:
parent
0419d23882
commit
c076a54a4d
@ -44,19 +44,6 @@ FMT_FUNC void throw_format_error(const char* message) {
|
|||||||
FMT_THROW(format_error(message));
|
FMT_THROW(format_error(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef _MSC_VER
|
|
||||||
# define FMT_SNPRINTF snprintf
|
|
||||||
#else // _MSC_VER
|
|
||||||
inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, format);
|
|
||||||
int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args);
|
|
||||||
va_end(args);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
# define FMT_SNPRINTF fmt_snprintf
|
|
||||||
#endif // _MSC_VER
|
|
||||||
|
|
||||||
FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
|
FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
|
||||||
string_view message) noexcept {
|
string_view message) noexcept {
|
||||||
// Report error code making sure that the output fits into
|
// Report error code making sure that the output fits into
|
||||||
@ -2174,6 +2161,17 @@ FMT_CONSTEXPR20 inline void format_dragon(fp value, bool is_predecessor_closer,
|
|||||||
buf[num_digits - 1] = static_cast<char>('0' + digit);
|
buf[num_digits - 1] = static_cast<char>('0' + digit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
FMT_FUNC auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...)
|
||||||
|
-> int {
|
||||||
|
auto args = va_list();
|
||||||
|
va_start(args, fmt);
|
||||||
|
int result = vsnprintf_s(buf, size, _TRUNCATE, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
template <typename Float>
|
template <typename Float>
|
||||||
FMT_HEADER_ONLY_CONSTEXPR20 int format_float(Float value, int precision,
|
FMT_HEADER_ONLY_CONSTEXPR20 int format_float(Float value, int precision,
|
||||||
float_specs specs,
|
float_specs specs,
|
||||||
@ -2251,110 +2249,6 @@ FMT_HEADER_ONLY_CONSTEXPR20 int format_float(Float value, int precision,
|
|||||||
}
|
}
|
||||||
return exp;
|
return exp;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
int snprintf_float(T value, int precision, float_specs specs,
|
|
||||||
buffer<char>& buf) {
|
|
||||||
// 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.
|
|
||||||
enum { max_format_size = 7 }; // The longest format is "%#.*Le".
|
|
||||||
char format[max_format_size];
|
|
||||||
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 inside 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <> struct formatter<detail::bigint> {
|
template <> struct formatter<detail::bigint> {
|
||||||
|
@ -1325,10 +1325,116 @@ FMT_HEADER_ONLY_CONSTEXPR20 auto format_float(T value, int precision,
|
|||||||
float_specs specs,
|
float_specs specs,
|
||||||
buffer<char>& buf) -> int;
|
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.
|
// Formats a floating-point number with snprintf.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
auto snprintf_float(T value, int precision, float_specs specs,
|
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>
|
template <typename T>
|
||||||
using convert_float_result =
|
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,
|
extern template auto format_float<long double>(long double value, int precision,
|
||||||
float_specs specs,
|
float_specs specs,
|
||||||
buffer<char>& buf) -> int;
|
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
|
#endif // FMT_HEADER_ONLY
|
||||||
|
|
||||||
FMT_END_DETAIL_NAMESPACE
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
@ -100,11 +100,6 @@ template FMT_API void detail::vformat_to(
|
|||||||
detail::buffer<char>&, string_view,
|
detail::buffer<char>&, string_view,
|
||||||
basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
|
basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
|
||||||
|
|
||||||
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
|
|
||||||
detail::buffer<char>&);
|
|
||||||
template FMT_API int detail::snprintf_float(long double, int,
|
|
||||||
detail::float_specs,
|
|
||||||
detail::buffer<char>&);
|
|
||||||
template FMT_API int detail::format_float(double, int, detail::float_specs,
|
template FMT_API int detail::format_float(double, int, detail::float_specs,
|
||||||
detail::buffer<char>&);
|
detail::buffer<char>&);
|
||||||
template FMT_API int detail::format_float(long double, int, detail::float_specs,
|
template FMT_API int detail::format_float(long double, int, detail::float_specs,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user