Support single precision floats in grisu formatting
Depends on #1360

Fixes #1336
{"label": "fmtlib:master", "ref": "master", "sha": "a5abe5d95cb8a8015913be9748a9661f3e1fbda8"}
Currently `fmt::format("{}", 0.1f)` with grisu produces `0.10000000149011612` (as would have been expected from `double(0.1f)`) rather than `0.1`.

Single precision formatting differs from double precision only in the calculation of the boundaries: https://github.com/google/double-conversion/blob/v3.1.5/double-conversion/fast-dtoa.cc#L525-L536
Support single precision floats in grisu formatting
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<float>::min_exponent -
+                                 std::numeric_limits<double>::digits;
+    significand_type half_ulp = 1 << (std::numeric_limits<double>::digits -
+                                      std::numeric_limits<float>::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<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.
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<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);

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)); }

{"locale-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "compile-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "ostream-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "ranges-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "core-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "scan-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "color-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "grisu-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "chrono-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "format-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "gtest-extra-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "posix-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assert-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "posix-mock-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "printf-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "format-impl-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "custom-formatter-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"locale-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "compile-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "ostream-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "ranges-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "core-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "scan-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "color-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "grisu-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "chrono-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "format-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "gtest-extra-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "posix-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assert-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "posix-mock-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "printf-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "format-impl-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "custom-formatter-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"passed_count": 17, "failed_count": 0, "skipped_count": 0, "passed_tests": ["format-test", "gtest-extra-test", "locale-test", "compile-test", "posix-test", "ostream-test", "ranges-test", "assert-test", "posix-mock-test", "core-test", "printf-test", "color-test", "scan-test", "format-impl-test", "grisu-test", "custom-formatter-test", "chrono-test"], "failed_tests": [], "skipped_tests": []}
{"passed_count": 0, "failed_count": 0, "skipped_count": 0, "passed_tests": [], "failed_tests": [], "skipped_tests": []}
{"passed_count": 17, "failed_count": 0, "skipped_count": 0, "passed_tests": ["format-test", "gtest-extra-test", "locale-test", "compile-test", "posix-test", "ostream-test", "ranges-test", "assert-test", "posix-mock-test", "core-test", "printf-test", "color-test", "scan-test", "format-impl-test", "grisu-test", "custom-formatter-test", "chrono-test"], "failed_tests": [], "skipped_tests": []}