diff --git a/include/fmt/format.h b/include/fmt/format.h index 59b8077b..2c23b478 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2554,11 +2554,30 @@ template struct arg_formatter { FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> iterator { return detail::write(out, value, specs, locale); } + + FMT_CONSTEXPR FMT_INLINE auto operator()(float value) -> iterator { + return detail::write(out, prevent_negative_zero(value), specs, locale); + } + FMT_CONSTEXPR FMT_INLINE auto operator()(double value) -> iterator { + return detail::write(out, prevent_negative_zero(value), specs, locale); + } + FMT_CONSTEXPR FMT_INLINE auto operator()(long double value) -> iterator { + return detail::write(out, prevent_negative_zero(value), specs, locale); + } auto operator()(typename basic_format_arg::handle) -> iterator { // User-defined types are handled separately because they require access // to the parse context. return out; } + private: + template + FMT_CONSTEXPR FMT_INLINE auto prevent_negative_zero(T value) -> T { + if (this->specs.precision > 0 and + (T{1} / static_cast(std::pow(10, this->specs.precision)) > std::fabs(value))) { + value = 0; + } + return value; + } }; template struct custom_formatter { diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 19d550f6..b6756661 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -267,6 +267,10 @@ class printf_arg_formatter : public arg_formatter { template ::value)> OutputIt operator()(T value) { + if (this->specs.precision > 0 and + (T{1} / static_cast(std::pow(10, this->specs.precision)) > std::fabs(value))) { + value = 0; + } return base::operator()(value); } diff --git a/test/printf-test.cc b/test/printf-test.cc index 91869c2f..2f6861a3 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -581,3 +581,35 @@ TEST(printf_test, vsprintf_make_wargs_example) { fmt::vsprintf(L"[%d] %s happened", {fmt::make_wprintf_args(42, L"something")})); } + +TEST(printf_test, check_sign_truncation_on_zero) { + float fval = 0.0f - 1e-7f; + EXPECT_EQ(fmt::vsprintf("%1.7f", {fmt::make_printf_args(fval)}), "-0.0000001"); + EXPECT_EQ(fmt::format("{:1.7f}", fval), "-0.0000001"); + fval = std::nexttoward(fval, 1.f); + EXPECT_EQ(fmt::vsprintf("%1.7f", {fmt::make_printf_args(fval)}), "0.0000000"); + EXPECT_EQ(fmt::format("{:1.7f}", fval), "0.0000000"); + fval = std::nexttoward(fval, -1.f); + EXPECT_EQ(fmt::vsprintf("%1.7f", {fmt::make_printf_args(fval)}), "-0.0000001"); + EXPECT_EQ(fmt::format("{:1.7f}", fval), "-0.0000001"); + + double dval = 0.0 - 1e-15; + EXPECT_EQ(fmt::vsprintf("%1.15lf", {fmt::make_printf_args(dval)}), "-0.000000000000001"); + EXPECT_EQ(fmt::format("{:1.15f}", dval), "-0.000000000000001"); + dval = std::nexttoward(dval, 1.f); + EXPECT_EQ(fmt::vsprintf("%1.15lf", {fmt::make_printf_args(dval)}), "0.000000000000000"); + EXPECT_EQ(fmt::format("{:1.15f}", dval), "0.000000000000000"); + dval = std::nexttoward(dval, -1.f); + EXPECT_EQ(fmt::vsprintf("%1.15lf", {fmt::make_printf_args(dval)}), "-0.000000000000001"); + EXPECT_EQ(fmt::format("{:1.15f}", dval), "-0.000000000000001"); + + long double ldval = 0.0L - 1e-18L; + EXPECT_EQ(fmt::vsprintf("%1.18Lf", {fmt::make_printf_args(ldval)}), "-0.000000000000000001"); + EXPECT_EQ(fmt::format("{:1.18f}", ldval), "-0.000000000000000001"); + ldval = std::nexttoward(ldval, 1.f); + EXPECT_EQ(fmt::vsprintf("%1.18Lf", {fmt::make_printf_args(ldval)}), "0.000000000000000000"); + EXPECT_EQ(fmt::format("{:1.18f}", ldval), "0.000000000000000000"); + ldval = std::nexttoward(ldval, -1.f); + EXPECT_EQ(fmt::vsprintf("%1.18Lf", {fmt::make_printf_args(ldval)}), "-0.000000000000000001"); + EXPECT_EQ(fmt::format("{:1.18f}", ldval), "-0.000000000000000001"); +}