Refactor format_float

This commit is contained in:
Victor Zverovich
2021-09-25 08:34:56 -07:00
parent 807ee5ec31
commit 2976e31ac9
2 changed files with 38 additions and 31 deletions

View File

@ -269,7 +269,7 @@ class fp {
// Assigns d to this and return true iff predecessor is closer than successor. // Assigns d to this and return true iff predecessor is closer than successor.
template <typename Float, FMT_ENABLE_IF(is_supported_float<Float>::value)> template <typename Float, FMT_ENABLE_IF(is_supported_float<Float>::value)>
FMT_CONSTEXPR bool assign(Float d) { FMT_CONSTEXPR bool assign(Float n) {
// Assume float is in the format [sign][exponent][significand]. // Assume float is in the format [sign][exponent][significand].
using limits = std::numeric_limits<Float>; using limits = std::numeric_limits<Float>;
const int float_significand_size = limits::digits - 1; const int float_significand_size = limits::digits - 1;
@ -280,7 +280,7 @@ class fp {
const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask; const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1;
constexpr bool is_double = sizeof(Float) == sizeof(uint64_t); constexpr bool is_double = sizeof(Float) == sizeof(uint64_t);
auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(d); auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(n);
f = u & significand_mask; f = u & significand_mask;
int biased_e = int biased_e =
static_cast<int>((u & exponent_mask) >> float_significand_size); static_cast<int>((u & exponent_mask) >> float_significand_size);
@ -2239,11 +2239,11 @@ small_divisor_case_label:
} }
} // namespace dragonbox } // namespace dragonbox
// Formats value using a variation of the Fixed-Precision Positive // Formats a floating-point number using a variation of the Fixed-Precision
// Floating-Point Printout ((FPP)^2) algorithm by Steele & White: // Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
// https://fmt.dev/papers/p372-steele.pdf. // https://fmt.dev/papers/p372-steele.pdf.
template <typename Double> template <typename Float>
FMT_CONSTEXPR20 void fallback_format(Double d, int num_digits, bool binary32, FMT_CONSTEXPR20 void fallback_format(Float n, int num_digits, bool binary32,
buffer<char>& buf, int& exp10) { buffer<char>& buf, int& exp10) {
bigint numerator; // 2 * R in (FPP)^2. bigint numerator; // 2 * R in (FPP)^2.
bigint denominator; // 2 * S in (FPP)^2. bigint denominator; // 2 * S in (FPP)^2.
@ -2256,7 +2256,7 @@ FMT_CONSTEXPR20 void fallback_format(Double d, int num_digits, bool binary32,
// is closer) to make lower and upper integers. This eliminates multiplication // is closer) to make lower and upper integers. This eliminates multiplication
// by 2 during later computations. // by 2 during later computations.
const bool is_predecessor_closer = const bool is_predecessor_closer =
binary32 ? value.assign(static_cast<float>(d)) : value.assign(d); binary32 ? value.assign(static_cast<float>(n)) : value.assign(n);
int shift = is_predecessor_closer ? 2 : 1; int shift = is_predecessor_closer ? 2 : 1;
uint64_t significand = value.f << shift; uint64_t significand = value.f << shift;
if (value.e >= 0) { if (value.e >= 0) {
@ -2391,28 +2391,33 @@ FMT_HEADER_ONLY_CONSTEXPR20 int format_float(T value, int precision,
return dec.exponent; return dec.exponent;
} }
// Use Grisu + Dragon4 for the given precision:
// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf.
int exp = 0; int exp = 0;
const int min_exp = -60; // alpha in Grisu. bool fallback = true;
int cached_exp10 = 0; // K in Grisu. if (is_fast_float<T>()) {
fp normalized = normalize(fp(value)); // Use Grisu + Dragon4 for the given precision:
const auto cached_pow = get_cached_power( // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf.
min_exp - (normalized.e + fp::significand_size), cached_exp10); const int min_exp = -60; // alpha in Grisu.
normalized = normalized * cached_pow; int cached_exp10 = 0; // K in Grisu.
// Limit precision to the maximum possible number of significant digits in an fp normalized = normalize(fp(value));
// IEEE754 double because we don't need to generate zeros. const auto cached_pow = get_cached_power(
const int max_double_digits = 767; min_exp - (normalized.e + fp::significand_size), cached_exp10);
if (precision > max_double_digits) precision = max_double_digits; normalized = normalized * cached_pow;
fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; // Limit precision to the maximum possible number of significant digits in
if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error || // an IEEE754 double because we don't need to generate zeros.
is_constant_evaluated()) { const int max_double_digits = 767;
exp += handler.size - cached_exp10 - 1; if (precision > max_double_digits) precision = max_double_digits;
fallback_format(value, handler.precision, specs.binary32, buf, exp); fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
} else { if (!is_constant_evaluated() &&
exp += handler.exp10; grisu_gen_digits(normalized, 1, exp, handler) != digits::error) {
buf.try_resize(to_unsigned(handler.size)); exp += handler.exp10;
buf.try_resize(to_unsigned(handler.size));
fallback = false;
} else {
exp += handler.size - cached_exp10 - 1;
precision = handler.precision;
}
} }
if (fallback) fallback_format(value, precision, specs.binary32, buf, exp);
if (!fixed && !specs.showpoint) { if (!fixed && !specs.showpoint) {
// Remove trailing zeros. // Remove trailing zeros.
auto num_digits = buf.size(); auto num_digits = buf.size();

View File

@ -8,10 +8,12 @@
#include "fmt/compile.h" #include "fmt/compile.h"
#include "gmock/gmock.h" #include "gmock/gmock.h"
#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806 && \ #if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806 && \
defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \ defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \
defined(__cpp_constexpr_dynamic_alloc) && \ defined(__cpp_constexpr_dynamic_alloc) && \
__cpp_constexpr_dynamic_alloc >= 201907 && __cplusplus >= 202002L __cpp_constexpr_dynamic_alloc >= 201907 && __cplusplus >= 202002L && \
!FMT_MSC_VER && \
!FMT_GCC_VERSION // MSVC & GCC constexpr limits are too low.
template <size_t max_string_length, typename Char = char> struct test_string { template <size_t max_string_length, typename Char = char> struct test_string {
template <typename T> constexpr bool operator==(const T& rhs) const noexcept { template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0; return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;