Commit ded1e767 authored by Victor Zverovich's avatar Victor Zverovich

Refactor floating point formatting

parent c7edd8e5
...@@ -51,12 +51,8 @@ ...@@ -51,12 +51,8 @@
// Dummy implementations of strerror_r and strerror_s called if corresponding // Dummy implementations of strerror_r and strerror_s called if corresponding
// system functions are not available. // system functions are not available.
inline fmt::internal::null<> strerror_r(int, char*, ...) { inline fmt::internal::null<> strerror_r(int, char*, ...) { return {}; }
return {}; inline fmt::internal::null<> strerror_s(char*, std::size_t, ...) { return {}; }
}
inline fmt::internal::null<> strerror_s(char*, std::size_t, ...) {
return {};
}
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace internal { namespace internal {
...@@ -250,20 +246,6 @@ template <> FMT_FUNC int count_digits<4>(internal::fallback_uintptr n) { ...@@ -250,20 +246,6 @@ template <> FMT_FUNC int count_digits<4>(internal::fallback_uintptr n) {
return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1; return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1;
} }
template <typename T>
int format_float(char* buf, std::size_t size, const char* format, int precision,
T value) {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (precision > 100000)
throw std::runtime_error(
"fuzz mode - avoid large allocation inside snprintf");
#endif
// Suppress the warning about nonliteral format string.
auto snprintf_ptr = FMT_SNPRINTF;
return precision < 0 ? snprintf_ptr(buf, size, format, value)
: snprintf_ptr(buf, size, format, precision, value);
}
template <typename T> template <typename T>
const char basic_data<T>::digits[] = const char basic_data<T>::digits[] =
"0001020304050607080910111213141516171819" "0001020304050607080910111213141516171819"
...@@ -276,9 +258,9 @@ template <typename T> ...@@ -276,9 +258,9 @@ template <typename T>
const char basic_data<T>::hex_digits[] = "0123456789abcdef"; const char basic_data<T>::hex_digits[] = "0123456789abcdef";
#define FMT_POWERS_OF_10(factor) \ #define FMT_POWERS_OF_10(factor) \
factor * 10, (factor) * 100, (factor) * 1000, (factor) * 10000, (factor) * 100000, \ factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \
(factor) * 1000000, (factor) * 10000000, (factor) * 100000000, \ (factor)*1000000, (factor)*10000000, (factor)*100000000, \
(factor) * 1000000000 (factor)*1000000000
template <typename T> template <typename T>
const uint64_t basic_data<T>::powers_of_10_64[] = { const uint64_t basic_data<T>::powers_of_10_64[] = {
...@@ -1121,78 +1103,99 @@ bool grisu_format(Double value, buffer<char>& buf, int precision, ...@@ -1121,78 +1103,99 @@ bool grisu_format(Double value, buffer<char>& buf, int precision,
return true; return true;
} }
template <typename Double> template <typename Float>
char* sprintf_format(Double value, internal::buffer<char>& buf, int sprintf_format(Float value, int precision, float_spec spec,
sprintf_specs specs) { buffer<char>& buf) {
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
FMT_ASSERT(buf.capacity() != 0, "empty buffer"); FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer");
// Subtract 1 to account for the difference in precision since we use %e for
// both general and exponent format.
if (spec.format == float_format::general)
precision = (precision >= 0 ? precision : 6) - 1;
// Build format string. // Build the format string.
enum { max_format_size = 10 }; // longest format: %#-*.*Lg enum { max_format_size = 7 }; // Ths longest format is "%#.*Le".
char format[max_format_size]; char format[max_format_size];
char* format_ptr = format; char* format_ptr = format;
*format_ptr++ = '%'; *format_ptr++ = '%';
if (specs.alt || !specs.type) *format_ptr++ = '#'; if (spec.alt) *format_ptr++ = '#';
if (specs.precision >= 0) { if (precision > 0) {
*format_ptr++ = '.'; *format_ptr++ = '.';
*format_ptr++ = '*'; *format_ptr++ = '*';
} }
if (std::is_same<Double, long double>::value) *format_ptr++ = 'L'; if (std::is_same<Float, long double>()) *format_ptr++ = 'L';
*format_ptr++ = spec.format != float_format::hex
char type = specs.type; ? (spec.format == float_format::fixed ? 'f' : 'e')
: (spec.upper ? 'A' : 'a');
if (type == '%')
type = 'f';
else if (type == 0 || type == 'n')
type = 'g';
if (FMT_MSC_VER && type == 'F')
type = 'f'; // // MSVC's printf doesn't support 'F'.
*format_ptr++ = type;
*format_ptr = '\0'; *format_ptr = '\0';
// Format using snprintf. // Format using snprintf.
char* start = nullptr; auto offset = buf.size();
char* decimal_point_pos = nullptr;
for (;;) { for (;;) {
std::size_t buffer_size = buf.capacity(); auto begin = buf.data() + offset;
start = &buf[0]; auto capacity = buf.capacity() - offset;
int result = #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
format_float(start, buffer_size, format, specs.precision, value); if (precision > 100000)
if (result >= 0) { throw std::runtime_error(
unsigned n = internal::to_unsigned(result); "fuzz mode - avoid large allocation inside snprintf");
if (n < buf.capacity()) { #endif
// Find the decimal point. // Suppress the warning about a nonliteral format string.
auto p = buf.data(), end = p + n; auto snprintf_ptr = FMT_SNPRINTF;
if (*p == '+' || *p == '-') ++p; int result = precision > 0
if (specs.type != 'a' && specs.type != 'A') { ? snprintf_ptr(begin, capacity, format, precision, value)
while (p < end && *p >= '0' && *p <= '9') ++p; : snprintf_ptr(begin, capacity, format, value);
if (p < end && *p != 'e' && *p != 'E') { if (result < 0) {
decimal_point_pos = p; buf.reserve(capacity + 1); // The buffer will grow exponentially.
if (!specs.type) { continue;
// Keep only one trailing zero after the decimal point. }
++p; unsigned size = to_unsigned(result);
if (*p == '0') ++p; if (size > capacity) {
while (p != end && *p >= '1' && *p <= '9') ++p; buf.reserve(size + 1); // Add 1 for the terminating '\0'.
char* where = p; continue;
while (p != end && *p == '0') ++p; }
if (p == end || *p < '0' || *p > '9') { auto is_digit = [](char c) { return c >= '0' && c <= '9'; };
if (p != end) std::memmove(where, p, to_unsigned(end - p)); if (spec.format == float_format::fixed) {
n -= static_cast<unsigned>(p - where); // Find and remove the decimal point.
} auto end = begin + size, p = end;
} do {
} --p;
} } while (is_digit(*p));
buf.resize(n); int fraction_size = static_cast<int>(end - p - 1);
break; // The buffer is large enough - continue with formatting. std::memmove(p, p + 1, fraction_size);
} buf.resize(size - 1);
buf.reserve(n + 1); return -fraction_size;
} else { }
// If result is negative we ask to increase the capacity by at least 1, if (spec.format == float_format::hex) {
// but as std::vector, the buffer grows exponentially. buf.resize(size + offset);
buf.reserve(buf.capacity() + 1); 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];
assert(sign == '+' || sign == '-');
int exp = 0;
auto p = exp_pos + 2; // Skip 'e' and sign.
do {
assert(is_digit(*p));
exp = exp * 10 + (*p++ - '0');
} while (p != end);
if (sign == '-') exp = -exp;
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.
int fraction_size = static_cast<int>(fraction_end - begin - 1);
std::memmove(begin + 1, begin + 2, fraction_size);
buf.resize(fraction_size + offset + 1);
exp -= fraction_size;
} }
return exp;
} }
return decimal_point_pos;
} }
} // namespace internal } // namespace internal
......
This diff is collapsed.
...@@ -36,11 +36,11 @@ template FMT_API std::string internal::vformat<char>( ...@@ -36,11 +36,11 @@ template FMT_API std::string internal::vformat<char>(
template FMT_API format_context::iterator internal::vformat_to( template FMT_API format_context::iterator internal::vformat_to(
internal::buffer<char>&, string_view, basic_format_args<format_context>); internal::buffer<char>&, string_view, basic_format_args<format_context>);
template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&, template FMT_API int internal::sprintf_format(double, int, internal::float_spec,
sprintf_specs); internal::buffer<char>&);
template FMT_API char* internal::sprintf_format(long double, template FMT_API int internal::sprintf_format(long double, int,
internal::buffer<char>&, internal::float_spec,
sprintf_specs); internal::buffer<char>&);
// Explicit instantiations for wchar_t. // Explicit instantiations for wchar_t.
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# A vagrant config for testing against gcc-4.8. # A vagrant config for testing against gcc-4.8.
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/trusty64" config.vm.box = "ubuntu/xenial64"
config.vm.provider "virtualbox" do |vb| config.vm.provider "virtualbox" do |vb|
vb.memory = "4096" vb.memory = "4096"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment