Skip to content

Commit

Permalink
Support single precision floats in grisu formatting
Browse files Browse the repository at this point in the history
Fixes #1336
  • Loading branch information
orivej committed Oct 12, 2019
1 parent 3a15ea3 commit d69177c
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 9 deletions.
23 changes: 20 additions & 3 deletions include/fmt/format-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ class fp {
// normalized form.
static FMT_CONSTEXPR_DECL const int double_significand_size =
std::numeric_limits<double>::digits - 1;
static FMT_CONSTEXPR_DECL const int float_significand_size =
std::numeric_limits<float>::digits - 1;
static FMT_CONSTEXPR_DECL const uint64_t implicit_bit =
1ull << double_significand_size;

Expand Down Expand Up @@ -423,6 +425,17 @@ class fp {
lower.f <<= lower.e - upper.e;
lower.e = upper.e;
}

void compute_float_boundaries(fp& lower, fp& upper) const {
constexpr significand_type half_step =
1 << (double_significand_size - float_significand_size - 1);
constexpr significand_type quarter_step =
1 << (double_significand_size - float_significand_size - 2);
upper = normalize<0>(fp(f + half_step, e));
lower = f == implicit_bit ? fp(f - quarter_step, e) : fp(f - half_step, e);
lower.f <<= lower.e - upper.e;
lower.e = upper.e;
}
};

// Returns an fp number representing x - y. Result may not be normalized.
Expand Down Expand Up @@ -935,8 +948,8 @@ template <typename Double> FMT_FUNC void fallback_format(Double v, int exp10) {

template <typename Double,
enable_if_t<(sizeof(Double) == sizeof(uint64_t)), int>>
FMT_API bool grisu_format(Double value, buffer<char>& buf, int precision,
unsigned options, int& exp) {
FMT_API bool grisu_format(Double value, size_t sizeof_value, buffer<char>& buf,
int precision, unsigned options, int& exp) {
FMT_ASSERT(value >= 0, "value is negative");
const bool fixed = (options & grisu_options::fixed) != 0;
if (value <= 0) { // <= instead of == to silence a warning.
Expand Down Expand Up @@ -966,7 +979,11 @@ FMT_API bool grisu_format(Double value, buffer<char>& buf, int precision,
buf.resize(to_unsigned(handler.size));
} else {
fp lower, upper; // w^- and w^+ in the Grisu paper.
fp_value.compute_boundaries(lower, upper);
if (sizeof_value == sizeof(float))
fp_value.compute_float_boundaries(lower, upper);
else
fp_value.compute_boundaries(lower, upper);

// Find a cached power of 10 such that multiplying upper by it will bring
// the exponent in the range [min_exp, -32].
const auto cached_pow = get_cached_power( // \tilde{c}_{-k} in Grisu.
Expand Down
6 changes: 3 additions & 3 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -1117,9 +1117,9 @@ enum { fixed = 1, grisu3 = 2 };
// Formats value using the Grisu algorithm:
// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf
template <typename Double, FMT_ENABLE_IF(sizeof(Double) == sizeof(uint64_t))>
FMT_API bool grisu_format(Double, buffer<char>&, int, unsigned, int&);
FMT_API bool grisu_format(Double, size_t, buffer<char>&, int, unsigned, int&);
template <typename Double, FMT_ENABLE_IF(sizeof(Double) != sizeof(uint64_t))>
inline bool grisu_format(Double, buffer<char>&, int, unsigned, int&) {
inline bool grisu_format(Double, size_t, buffer<char>&, int, unsigned, int&) {
return false;
}

Expand Down Expand Up @@ -2806,7 +2806,7 @@ void internal::basic_writer<Range>::write_fp(T value,
(specs.type != 'a' && specs.type != 'A' &&
specs.type != 'e' && specs.type != 'E') &&
internal::grisu_format(
static_cast<double>(value), buffer, precision,
static_cast<double>(value), sizeof(value), buffer, precision,
handler.fixed ? internal::grisu_options::fixed : 0, exp);
char* decimal_point_pos = nullptr;
if (!use_grisu)
Expand Down
4 changes: 2 additions & 2 deletions src/format.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ FMT_BEGIN_NAMESPACE
template struct FMT_API internal::basic_data<void>;

// Workaround a bug in MSVC2013 that prevents instantiation of grisu_format.
bool (*instantiate_grisu_format)(double, internal::buffer<char>&, int, unsigned,
int&) = internal::grisu_format;
bool (*instantiate_grisu_format)(double, size_t, internal::buffer<char>&, int,
unsigned, int&) = internal::grisu_format;

#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template FMT_API internal::locale_ref::locale_ref(const std::locale& loc);
Expand Down
2 changes: 1 addition & 1 deletion test/format-impl-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ TEST(FPTest, FixedHandler) {
TEST(FPTest, GrisuFormatCompilesWithNonIEEEDouble) {
fmt::memory_buffer buf;
int exp = 0;
grisu_format(4.2f, buf, -1, false, exp);
grisu_format(4.2f, sizeof(double), buf, -1, false, exp);
}

template <typename T> struct value_extractor {
Expand Down
2 changes: 2 additions & 0 deletions test/grisu-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ TEST(GrisuTest, Prettify) {
EXPECT_EQ("12340000000.0", fmt::format("{}", 1234e7));
EXPECT_EQ("12.34", fmt::format("{}", 1234e-2));
EXPECT_EQ("0.001234", fmt::format("{}", 1234e-6));
EXPECT_EQ("0.1", fmt::format("{}", 0.1f));
EXPECT_EQ("0.10000000149011612", fmt::format("{}", double(0.1f)));
}

TEST(GrisuTest, ZeroPrecision) { EXPECT_EQ("1", fmt::format("{:.0}", 1.0)); }

0 comments on commit d69177c

Please sign in to comment.