diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 58bcfbc1..5c6f1a84 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -571,41 +571,37 @@ struct fixed_handler { int exp10; bool fixed; - bool enough_precision(int full_exp) const { - return /*full_exp <= 0 &&*/ -full_exp >= precision; - } - digits::result on_start(uint64_t divisor, uint64_t remainder, uint64_t error, int& exp) { // Non-fixed formats require at least one digit and no precision adjustment. if (!fixed) return digits::more; - int full_exp = exp + exp10; - // Increase precision by the number of integer digits, e.g. - // format("{:.2f}", 1.23) should produce "1.23", not "1.2". - if (full_exp > 0) precision += full_exp; - if (!enough_precision(full_exp)) return digits::more; + // Adjust fixed precision by exponent because it is relative to decimal + // point. + precision += exp + exp10; + // Check if precision is satisfied just by leading zeros, e.g. + // format("{:.2f}", 0.001) gives "0.00" without generating any digits. + if (precision > 0) return digits::more; auto dir = get_round_direction(divisor, remainder, error); if (dir == unknown) return digits::error; buf[size++] = dir == up ? '1' : '0'; return digits::done; } - // TODO: test digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder, uint64_t error, int& exp, bool integral) { - assert(remainder < divisor); + FMT_ASSERT(remainder < divisor, ""); buf[size++] = digit; - if (size != precision && !enough_precision(exp + exp10)) - return digits::more; + if (size != precision) return digits::more; if (!integral) { // Check if error * 2 < divisor with overflow prevention. // The check is not needed for the integral part because error = 1 // and divisor > (1 << 32) there. if (error >= divisor || error >= divisor - error) return digits::error; } else { - assert(error == 1 && divisor > 2); + FMT_ASSERT(error == 1 && divisor > 2, ""); } auto dir = get_round_direction(divisor, remainder, error); + // TODO: test rounding if (dir != up) return dir == down ? digits::done : digits::error; ++buf[size - 1]; for (int i = size - 1; i > 0 && buf[i] > '9'; --i) { diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index ea163687..d1c2ecb6 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -121,6 +121,28 @@ TEST(FPTest, GetRoundDirection) { EXPECT_EQ(fmt::internal::up, get_round_direction(max, max - 1, 1)); } +TEST(FPTest, FixedHandler) { + struct handler : fmt::internal::fixed_handler { + char buffer[10]; + handler(int prec = 0) : fmt::internal::fixed_handler() { + buf = buffer; + precision = prec; + } + }; + int exp = 0; + handler().on_digit('0', 100, 99, 0, exp, false); + EXPECT_THROW(handler().on_digit('0', 100, 100, 0, exp, false), + assertion_failure); + namespace digits = fmt::internal::digits; + EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::done); + // Check that divisor - error doesn't overflow. + EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error); + // Check that 2 * error doesn't overflow. + uint64_t max = std::numeric_limits::max(); + EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, exp, false), + digits::error); +} + TEST(FPTest, Grisu2FormatCompilesWithNonIEEEDouble) { fmt::memory_buffer buf; int exp = 0; diff --git a/test/format-test.cc b/test/format-test.cc index bae3df57..afaec81b 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1474,6 +1474,9 @@ TEST(FormatterTest, PrecisionRounding) { EXPECT_EQ("0", format("{:.0f}", 0.1)); EXPECT_EQ("0.000", format("{:.3f}", 0.00049)); EXPECT_EQ("0.001", format("{:.3f}", 0.0005)); + EXPECT_EQ("0.001", format("{:.3f}", 0.00149)); + EXPECT_EQ("0.002", format("{:.3f}", 0.0015)); + EXPECT_EQ("0.00123", format("{:.3}", 0.00123)); } TEST(FormatterTest, FormatNaN) {