From 5985f0a7d2a725f6e843e8bc1a01e052a31d8e0b Mon Sep 17 00:00:00 2001 From: matrackif Date: Mon, 17 Jan 2022 21:14:59 +0100 Subject: [PATCH] Fix overflow for chrono durations (#2722) --- include/fmt/chrono.h | 28 +++++++++++++++++++--------- test/chrono-test.cc | 4 ++++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 775dc17d2019..df4b81ae54cb 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1470,14 +1470,23 @@ inline std::chrono::duration get_milliseconds( #endif } -// Returns the number of fractional digits in the range [0, 18] according to the +// Counts the number of fractional digits in the range [0, 18] according to the // C++20 spec. If more than 18 fractional digits are required then returns 6 for // microseconds precision. -constexpr int count_fractional_digits(long long num, long long den, int n = 0) { - return num % den == 0 - ? n - : (n > 18 ? 6 : count_fractional_digits(num * 10, den, n + 1)); -} +template ::max() / 10)> +struct count_fractional_digits { + static constexpr int value = + Num % Den == 0 ? N : count_fractional_digits::value; +}; + +// Base case that doesn't instantiate any more templates +// in order to avoid overflow. +template +struct count_fractional_digits { + static constexpr int value = (Num % Den == 0) ? N : 6; +}; constexpr long long pow10(std::uint32_t n) { return n == 0 ? 1 : 10 * pow10(n - 1); @@ -1666,7 +1675,8 @@ struct chrono_formatter { template void write_fractional_seconds(Duration d) { FMT_ASSERT(!std::is_floating_point::value, ""); constexpr auto num_fractional_digits = - count_fractional_digits(Duration::period::num, Duration::period::den); + count_fractional_digits::value; using subsecond_precision = std::chrono::duration< typename std::common_type::value) { - auto num_fractional_digits = - count_fractional_digits(Period::num, Period::den); + constexpr auto num_fractional_digits = + count_fractional_digits::value; auto buf = memory_buffer(); format_to(std::back_inserter(buf), runtime("{:.{}f}"), std::fmod(val * static_cast(Period::num) / diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 6d7e6044c767..959fb65a3e7c 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -623,6 +623,10 @@ TEST(chrono_test, cpp20_duration_subsecond_support) { // fixed precision, and print zeros even if there is no fractional part. EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}), "07.000000"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration>(1)), + "00.333333"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration>(1)), + "00.142857"); } #endif // FMT_STATIC_THOUSANDS_SEPARATOR