diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 30de39b26de6..da8060a68e84 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -423,6 +423,18 @@ class fp { lower.f <<= lower.e - upper.e; lower.e = upper.e; } + + void compute_float_boundaries(fp& lower, fp& upper) const { + constexpr int min_normal_e = std::numeric_limits::min_exponent - + std::numeric_limits::digits; + significand_type half_ulp = 1 << (std::numeric_limits::digits - + std::numeric_limits::digits - 1); + if (min_normal_e > e) half_ulp <<= min_normal_e - e; + upper = normalize<0>(fp(f + half_ulp, e)); + lower = fp(f - (half_ulp >> (f == implicit_bit && e > min_normal_e)), e); + lower.f <<= lower.e - upper.e; + lower.e = upper.e; + } }; // Returns an fp number representing x - y. Result may not be normalized. @@ -1045,7 +1057,11 @@ 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 007fe8f2c4e3..b9935902607a 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, grisu2 = 2 }; +enum { fixed = 1, grisu2 = 2, binary32 = 4 }; } // Formats value using the Grisu algorithm: @@ -2809,12 +2809,16 @@ 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 c3f819046088..852b91a11fbe 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -221,6 +221,36 @@ TEST(FPTest, ComputeBoundaries) { EXPECT_EQ(31, upper.e); } +TEST(FPTest, ComputeFloatBoundaries) { + struct { + double x, lower, upper; + } tests[] = { + // regular + {1.5f, 1.4999999403953552, 1.5000000596046448}, + // boundary + {1.0f, 0.9999999701976776, 1.0000000596046448}, + // min normal + {1.1754944e-38f, 1.1754942807573643e-38, 1.1754944208872107e-38}, + // max subnormal + {1.1754942e-38f, 1.1754941406275179e-38, 1.1754942807573643e-38}, + // min subnormal + {1e-45f, 7.006492321624085e-46, 2.1019476964872256e-45}, + }; + for (auto test : tests) { + auto v = fp(test.x); + fp vlower = normalize(fp(test.lower)); + fp 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 6b03b9c7ee81..b66c6b7f5cd3 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)); }