diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index a95918e3..52c42e03 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -984,7 +984,7 @@ struct grisu_shortest_handler { // Floating-Point Printout ((FPP)^2) algorithm by Steele & White: // https://fmt.dev/p372-steele.pdf. template -void fallback_format(Double d, buffer& buf, int& exp10) { +void fallback_format(Double d, int num_digits, buffer& buf, int& exp10) { bigint numerator; // 2 * R in (FPP)^2. bigint denominator; // 2 * S in (FPP)^2. // lower and upper are differences between value and corresponding boundaries. @@ -1031,34 +1031,71 @@ void fallback_format(Double d, buffer& buf, int& exp10) { upper = &upper_store; } } - if (!upper) upper = &lower; // Invariant: value == (numerator / denominator) * pow(10, exp10). - bool even = (value.f & 1) == 0; - int num_digits = 0; - char* data = buf.data(); - for (;;) { - int digit = numerator.divmod_assign(denominator); - bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. - // numerator + upper >[=] pow10: - bool high = add_compare(numerator, *upper, denominator) + even > 0; - data[num_digits++] = static_cast('0' + digit); - if (low || high) { - if (!low) { - ++data[num_digits - 1]; - } else if (high) { - int result = add_compare(numerator, numerator, denominator); - // Round half to even. - if (result > 0 || (result == 0 && (digit % 2) != 0)) + if (num_digits < 0) { + // Generate the shortest representation. + if (!upper) upper = &lower; + bool even = (value.f & 1) == 0; + num_digits = 0; + char* data = buf.data(); + for (;;) { + int digit = numerator.divmod_assign(denominator); + bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. + // numerator + upper >[=] pow10: + bool high = add_compare(numerator, *upper, denominator) + even > 0; + data[num_digits++] = static_cast('0' + digit); + if (low || high) { + if (!low) { ++data[num_digits - 1]; + } else if (high) { + int result = add_compare(numerator, numerator, denominator); + // Round half to even. + if (result > 0 || (result == 0 && (digit % 2) != 0)) + ++data[num_digits - 1]; + } + buf.try_resize(to_unsigned(num_digits)); + exp10 -= num_digits - 1; + return; + } + numerator *= 10; + lower *= 10; + if (upper != &lower) *upper *= 10; + } + } + // Generate the given number of digits. + exp10 -= num_digits - 1; + if (num_digits == 0) { + buf.try_resize(1); + denominator *= 10; + buf[0] = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; + return; + } + buf.try_resize(to_unsigned(num_digits)); + for (int i = 0; i < num_digits - 1; ++i) { + int digit = numerator.divmod_assign(denominator); + buf[i] = static_cast('0' + digit); + numerator *= 10; + } + int digit = numerator.divmod_assign(denominator); + auto result = add_compare(numerator, numerator, denominator); + if (result > 0 || (result == 0 && (digit % 2) != 0)) { + if (digit == 9) { + const auto overflow = '0' + 10; + buf[num_digits - 1] = overflow; + // Propagate the carry. + for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] == overflow) { + buf[0] = '0'; + ++exp10; } - buf.try_resize(to_unsigned(num_digits)); - exp10 -= num_digits - 1; return; } - numerator *= 10; - lower *= 10; - if (upper != &lower) *upper *= 10; + ++digit; } + buf[num_digits - 1] = static_cast('0' + digit); } // Formats value using the Grisu algorithm @@ -1110,7 +1147,7 @@ int format_float(T value, int precision, float_specs specs, buffer& buf) { boundaries.upper - boundaries.lower, exp, handler); if (result == digits::error) { exp += handler.size - cached_exp10 - 1; - fallback_format(value, buf, exp); + fallback_format(value, -1, buf, exp); return exp; } buf.try_resize(to_unsigned(handler.size)); @@ -1121,8 +1158,11 @@ int format_float(T value, int precision, float_specs specs, buffer& buf) { min_exp - (normalized.e + fp::significand_size), cached_exp10); normalized = normalized * cached_pow; fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; - if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) - return snprintf_float(value, precision, specs, buf); + if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) { + exp += handler.size - cached_exp10 - 1; + fallback_format(value, handler.size, buf, exp); + return exp; + } int num_digits = handler.size; if (!fixed && !specs.showpoint) { // Remove trailing zeros. diff --git a/test/format-test.cc b/test/format-test.cc index e9666560..fd1c3ee5 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -762,8 +762,7 @@ TEST(FormatterTest, HashFlag) { EXPECT_EQ("4.e+01", format("{:#.0e}", 42.0)); EXPECT_EQ("0.", format("{:#.0f}", 0.01)); EXPECT_EQ("0.50", format("{:#.2g}", 0.5)); - auto s = format("{:#.0f}", 0.5); // MSVC's printf uses wrong rounding mode. - EXPECT_TRUE(s == "0." || s == "1."); + EXPECT_EQ("0.", format("{:#.0f}", 0.5)); EXPECT_THROW_MSG(format("{0:#", 'c'), format_error, "missing '}' in format string"); EXPECT_THROW_MSG(format("{0:#}", 'c'), format_error, @@ -1279,6 +1278,9 @@ TEST(FormatterTest, PrecisionRounding) { char buffer[64]; safe_sprintf(buffer, "%f", n); EXPECT_EQ(buffer, format("{:f}", n)); + EXPECT_EQ("225.51575035152063720", + fmt::format("{:.17f}", 225.51575035152064)); + EXPECT_EQ("-761519619559038.2", fmt::format("{:.1f}", -761519619559038.2)); } TEST(FormatterTest, FormatNaN) {