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 13, 2019
1 parent 3a15ea3 commit d6a0654
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 3 deletions.
17 changes: 16 additions & 1 deletion 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,15 @@ 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_ulp =
1 << (double_significand_size - float_significand_size - 1);
upper = normalize<0>(fp(f + half_ulp, e));
lower = fp(f - (half_ulp >> (f == implicit_bit)), 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 @@ -966,7 +977,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 ((options & grisu_options::binary32) != 0)
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
9 changes: 7 additions & 2 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -1111,7 +1111,7 @@ It grisu_prettify(const char* digits, int size, int exp, It it,
}

namespace grisu_options {
enum { fixed = 1, grisu3 = 2 };
enum { fixed = 1, grisu3 = 2, binary32 = 4 };
}

// Formats value using the Grisu algorithm:
Expand Down Expand Up @@ -2802,12 +2802,17 @@ void internal::basic_writer<Range>::write_fp(T value,
memory_buffer buffer;
int exp = 0;
int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6;
unsigned options = 0;
if (handler.fixed)
options |= internal::grisu_options::fixed;
if (sizeof(value) == sizeof(float))
options |= internal::grisu_options::binary32;
bool use_grisu = USE_GRISU &&
(specs.type != 'a' && specs.type != 'A' &&
specs.type != 'e' && specs.type != 'E') &&
internal::grisu_format(
static_cast<double>(value), buffer, precision,
handler.fixed ? internal::grisu_options::fixed : 0, exp);
options, exp);
char* decimal_point_pos = nullptr;
if (!use_grisu)
decimal_point_pos = internal::sprintf_format(value, buffer, specs);
Expand Down
22 changes: 22 additions & 0 deletions test/format-impl-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,28 @@ TEST(FPTest, ComputeBoundaries) {
EXPECT_EQ(31, upper.e);
}

TEST(FPTest, ComputeFloatBoundaries) {
struct { float x; double lower, upper; } tests[] = {
{1.5f, 1.4999999403953552, 1.5000000596046448}, // regular
{1.0f, 0.9999999701976776, 1.0000000596046448}, // boundary
{1e-45f, 7.006492321624085e-46, 2.1019476964872256e-45}, // subnormal
};
for (auto test : tests) {
auto v = fp(double(test.x));
auto vn = normalize(v);
auto vlower = normalize(fp(test.lower));
auto vupper = normalize(fp(test.upper));
vlower.f >>= vupper.e - vlower.e;
vlower.e = vupper.e;
fp lower, upper;
v.compute_float_boundaries(lower, upper);
EXPECT_EQ(vlower.f, lower.f);
EXPECT_EQ(vlower.e, lower.e);
EXPECT_EQ(vupper.f, upper.f);
EXPECT_EQ(vupper.e, upper.e);
}
}

TEST(FPTest, Subtract) {
auto v = fp(123, 1) - fp(102, 1);
EXPECT_EQ(v.f, 21u);
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 d6a0654

Please sign in to comment.