From 707f9a1ce575c94d96c3cc27ea249cb9b5f8059d Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Sat, 12 Nov 2022 22:18:30 +0500 Subject: [PATCH] Replace snprintf-based hex float formatter with internal implementation Signed-off-by: Vladislav Shchapov --- include/fmt/format-inl.h | 14 +--- include/fmt/format.h | 158 +++++++++++++++++++++++++-------------- test/format-test.cc | 70 ++++++++++++++++- test/printf-test.cc | 15 +++- 4 files changed, 183 insertions(+), 74 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index eadf0da3eaa47..f1fc7840c56be 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -1040,8 +1040,7 @@ template <> struct cache_accessor { {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, {0xc5a05277621be293, 0xc7098b7305241886}, - { 0xf70867153aa2db38, - 0xb8cbee4fc66d1ea8 } + {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8} #else {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, @@ -1425,17 +1424,6 @@ template decimal_fp to_decimal(T x) noexcept { return ret_value; } } // namespace dragonbox - -#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 } // namespace detail template <> struct formatter { diff --git a/include/fmt/format.h b/include/fmt/format.h index 0c3231b5f87e8..744b59b5e1758 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1618,62 +1618,6 @@ FMT_CONSTEXPR inline fp get_cached_power(int min_exponent, *(data::pow10_exponents + index)}; } -#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 using the hexfloat format. -template -auto snprintf_float(T value, int precision, float_specs specs, - buffer& buf) -> int { - // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. - FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer"); - FMT_ASSERT(specs.format == float_format::hex, ""); - static_assert(!std::is_same::value, ""); - - // Build the format string. - char format[7]; // The longest format is "%#.*Le". - char* format_ptr = format; - *format_ptr++ = '%'; - if (specs.showpoint) *format_ptr++ = '#'; - if (precision >= 0) { - *format_ptr++ = '.'; - *format_ptr++ = '*'; - } - if (std::is_same()) *format_ptr++ = 'L'; - *format_ptr++ = 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; - abort_fuzzing_if(precision > 100000); - // 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_resize(size + offset); - return 0; - } - buf.try_reserve(size + offset + 1); // Add 1 for the terminating '\0'. - } -} - template using convert_float_result = conditional_t::value || @@ -3171,6 +3115,106 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, buf[num_digits - 1] = static_cast('0' + digit); } +// Formats a floating-point number with snprintf using the hexfloat format. +template ::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, + float_specs specs, buffer& buf) { + // float is passed as double to reduce the number of instantiations and to + // simplify implementation. + static_assert(!std::is_same::value, ""); + static_assert(std::numeric_limits::digits <= 113, "unsupported FP"); + + using info = dragonbox::float_info; + + // Assume Float is in the format [sign][exponent][significand]. + using carrier_uint = typename info::carrier_uint; + + carrier_uint f; + int e; + + const auto num_significand_bits = detail::num_significand_bits(); + const auto implicit_bit = carrier_uint(1) << num_significand_bits; + const auto significand_mask = implicit_bit - 1; + auto u = bit_cast(value); + f = static_cast(u & significand_mask); + auto biased_e = + static_cast((u & exponent_mask()) >> num_significand_bits); + + if (biased_e == 0) + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + else if (has_implicit_bit()) + f += static_cast(implicit_bit); + + e = biased_e - (1 << (info::exponent_bits - 1)) + 1; + + const auto num_fraction_bits = + num_significand_bits + (has_implicit_bit() ? 1 : 0); + const auto num_xdigits = (num_fraction_bits + 3) / 4; + + const auto leading_shift = ((num_xdigits - 1) * 4); + const auto leading_mask = carrier_uint(0xF) << leading_shift; + const auto leading_v = + static_cast((f & leading_mask) >> leading_shift); + if (leading_v > 1) e -= (32 - FMT_BUILTIN_CLZ(leading_v) - 1); + + int print_xdigits = num_xdigits - 1; + if (precision >= 0 && print_xdigits > precision) { + const int shift = ((print_xdigits - precision - 1) * 4); + const auto mask = carrier_uint(0xF) << shift; + const auto v = static_cast((f & mask) >> shift); + + if (v >= 8) { + const auto inc = carrier_uint(1) << (shift + 4); + f += inc; + f &= ~(inc - 1); + } + + // long double overflow + if (!has_implicit_bit() && ((f & implicit_bit) == implicit_bit)) { + f >>= 4; + e += 4; + } + + print_xdigits = precision; + } + + char xdigits[num_bits() / 4]; + detail::fill_n(xdigits, sizeof(xdigits), '0'); + format_uint<4>(xdigits, f, num_xdigits, specs.upper); + + // Remove zero tail + while (print_xdigits > 0 && xdigits[print_xdigits] == '0') --print_xdigits; + + buf.push_back('0'); + buf.push_back(specs.upper ? 'X' : 'x'); + buf.push_back(xdigits[0]); + if (specs.showpoint || print_xdigits > 0 || print_xdigits < precision) + buf.push_back('.'); + buf.append(xdigits + 1, xdigits + 1 + print_xdigits); + + if (print_xdigits < precision) + detail::fill_n(appender(buf), precision - print_xdigits, '0'); + + buf.push_back(specs.upper ? 'P' : 'p'); + + uint32_t abs_e; + if (e < 0) { + buf.push_back('-'); + abs_e = static_cast(-e); + } else { + buf.push_back('+'); + abs_e = static_cast(e); + } + format_decimal(appender(buf), abs_e, detail::count_digits(abs_e)); +} + +template ::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, + float_specs specs, buffer& buf) { + static_assert(std::numeric_limits::is_iec559, "unsupported FP"); + format_hexfloat(static_cast(value), precision, specs, buf); +} + template FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, buffer& buf) -> int { @@ -3285,7 +3329,7 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, memory_buffer buffer; if (fspecs.format == float_format::hex) { if (fspecs.sign) buffer.push_back(detail::sign(fspecs.sign)); - snprintf_float(convert_float(value), specs.precision, fspecs, buffer); + format_hexfloat(convert_float(value), specs.precision, fspecs, buffer); return write_bytes(out, {buffer.data(), buffer.size()}, specs); } diff --git a/test/format-test.cc b/test/format-test.cc index 4db3c9c493566..646c4c902d3e8 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1344,16 +1344,82 @@ TEST(format_test, format_double) { EXPECT_EQ(fmt::format("{:f}", 392.65), "392.650000"); EXPECT_EQ(fmt::format("{:F}", 392.65), "392.650000"); EXPECT_EQ(fmt::format("{:L}", 42.0), "42"); + EXPECT_EQ(fmt::format("{:24a}", 4.2f), " 0x1.0cccccp+2"); EXPECT_EQ(fmt::format("{:24a}", 4.2), " 0x1.0cccccccccccdp+2"); EXPECT_EQ(fmt::format("{:<24a}", 4.2), "0x1.0cccccccccccdp+2 "); EXPECT_EQ(fmt::format("{0:e}", 392.65), "3.926500e+02"); EXPECT_EQ(fmt::format("{0:E}", 392.65), "3.926500E+02"); EXPECT_EQ(fmt::format("{0:+010.4g}", 392.65), "+0000392.6"); char buffer[buffer_size]; + double d = 0; + +#if FMT_CPLUSPLUS >= 201703L + d = 0x1.ffffffffffp+2; + safe_sprintf(buffer, "%.*a", 10, d); + EXPECT_EQ(fmt::format("{:.10a}", d), buffer); + safe_sprintf(buffer, "%.*a", 9, d); + EXPECT_EQ(fmt::format("{:.9a}", d), buffer); + + if (std::numeric_limits::digits == 64) { + auto ld = 0xf.ffffffffffp-3l; + safe_sprintf(buffer, "%La", ld); + EXPECT_EQ(fmt::format("{:a}", ld), buffer); + safe_sprintf(buffer, "%.*La", 10, ld); + EXPECT_EQ(fmt::format("{:.10a}", ld), buffer); + safe_sprintf(buffer, "%.*La", 9, ld); + EXPECT_EQ(fmt::format("{:.9a}", ld), buffer); + } +#endif + + d = (std::numeric_limits::min)(); + safe_sprintf(buffer, "%a", d); + std::string d_min_outputs[] = { + "0x1p-1022", // UNIX + "0x1.0000000000000p-1022" // Windows + }; + EXPECT_THAT(d_min_outputs, testing::Contains(buffer)); + EXPECT_EQ(fmt::format("{:a}", d), "0x1p-1022"); + EXPECT_EQ(fmt::format("{:#a}", d), "0x1.p-1022"); + + d = (std::numeric_limits::max)(); + safe_sprintf(buffer, "%a", d); + EXPECT_EQ(fmt::format("{:a}", d), buffer); + + d = std::numeric_limits::denorm_min(); + EXPECT_EQ(fmt::format("{:a}", d), "0x0.0000000000001p-1022"); + + if (std::numeric_limits::digits == 64) { + auto ld = (std::numeric_limits::min)(); + safe_sprintf(buffer, "%La", ld); + EXPECT_EQ(fmt::format("{:a}", ld), buffer); + + ld = (std::numeric_limits::max)(); + safe_sprintf(buffer, "%La", ld); + EXPECT_EQ(fmt::format("{:a}", ld), buffer); + + ld = std::numeric_limits::denorm_min(); + EXPECT_EQ(fmt::format("{:a}", ld), "0x0.000000000000001p-16382"); + } + + safe_sprintf(buffer, "%.*a", 10, 4.2); + EXPECT_EQ(fmt::format("{:.10a}", 4.2), buffer); + safe_sprintf(buffer, "%a", -42.0); - EXPECT_EQ(fmt::format("{:a}", -42.0), buffer); + std::string a_outputs[] = { + "-0x1.5p+5", // UNIX + "-0x1.5000000000000p+5" // Windows + }; + EXPECT_THAT(a_outputs, testing::Contains(buffer)); + EXPECT_EQ(fmt::format("{:a}", -42.0), "-0x1.5p+5"); + safe_sprintf(buffer, "%A", -42.0); - EXPECT_EQ(fmt::format("{:A}", -42.0), buffer); + std::string A_outputs[] = { + "-0X1.5P+5", // UNIX + "-0X1.5000000000000P+5" // Windows + }; + EXPECT_THAT(A_outputs, testing::Contains(buffer)); + EXPECT_EQ(fmt::format("{:A}", -42.0), "-0X1.5P+5"); + EXPECT_EQ(fmt::format("{:f}", 9223372036854775807.0), "9223372036854775808.000000"); } diff --git a/test/printf-test.cc b/test/printf-test.cc index 383ffb83b5783..aa874b1ff50d0 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -226,9 +226,20 @@ TEST(printf_test, hash_flag) { EXPECT_PRINTF("-42.0000", "%#G", -42.0); safe_sprintf(buffer, "%#a", 16.0); - EXPECT_PRINTF(buffer, "%#a", 16.0); + std::string a_outputs[] = { + "0x1.p+4", // UNIX + "0x1.0000000000000p+4" // Windows + }; + EXPECT_THAT(a_outputs, testing::Contains(buffer)); + EXPECT_PRINTF("0x1.p+4", "%#a", 16.0); + safe_sprintf(buffer, "%#A", 16.0); - EXPECT_PRINTF(buffer, "%#A", 16.0); + std::string A_outputs[] = { + "0X1.P+4", // UNIX + "0X1.0000000000000P+4" // Windows + }; + EXPECT_THAT(A_outputs, testing::Contains(buffer)); + EXPECT_PRINTF("0X1.P+4", "%#A", 16.0); // '#' flag is ignored for non-numeric types. EXPECT_PRINTF("x", "%#c", 'x');