From d6a06548e86265dcb54f948cd2caa445279656da Mon Sep 17 00:00:00 2001 From: Orivej Desh Date: Sat, 12 Oct 2019 12:17:27 +0000 Subject: [PATCH] Support single precision floats in grisu formatting Fixes #1336 --- include/fmt/format-inl.h | 17 ++++++++++++++++- include/fmt/format.h | 9 +++++++-- test/format-impl-test.cc | 22 ++++++++++++++++++++++ test/grisu-test.cc | 2 ++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 277acaccd170..864f2a3cba3a 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -362,6 +362,8 @@ class fp { // normalized form. static FMT_CONSTEXPR_DECL const int double_significand_size = std::numeric_limits::digits - 1; + static FMT_CONSTEXPR_DECL const int float_significand_size = + std::numeric_limits::digits - 1; static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = 1ull << double_significand_size; @@ -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. @@ -966,7 +977,11 @@ FMT_API bool grisu_format(Double value, buffer& 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. diff --git a/include/fmt/format.h b/include/fmt/format.h index 12bf0f48d215..abe6545cea1a 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -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: @@ -2802,12 +2802,17 @@ void internal::basic_writer::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(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); diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index 70429f06cf47..a87552c7bdb5 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -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); diff --git a/test/grisu-test.cc b/test/grisu-test.cc index 79a131913c18..71a0fb7368b2 100644 --- a/test/grisu-test.cc +++ b/test/grisu-test.cc @@ -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)); }