Skip to content

Commit

Permalink
Fix overflow for chrono durations (#2722)
Browse files Browse the repository at this point in the history
  • Loading branch information
matrackif authored Jan 17, 2022
1 parent 8f8a1a0 commit 5985f0a
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 9 deletions.
28 changes: 19 additions & 9 deletions include/fmt/chrono.h
Original file line number Diff line number Diff line change
Expand Up @@ -1470,14 +1470,23 @@ inline std::chrono::duration<Rep, std::milli> 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 <long long Num, long long Den, int N = 0,
bool Enabled =
(N < 19) && (Num <= std::numeric_limits<long long>::max() / 10)>
struct count_fractional_digits {
static constexpr int value =
Num % Den == 0 ? N : count_fractional_digits<Num * 10, Den, N + 1>::value;
};

// Base case that doesn't instantiate any more templates
// in order to avoid overflow.
template <long long Num, long long Den, int N>
struct count_fractional_digits<Num, Den, N, false> {
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);
Expand Down Expand Up @@ -1666,7 +1675,8 @@ struct chrono_formatter {
template <typename Duration> void write_fractional_seconds(Duration d) {
FMT_ASSERT(!std::is_floating_point<typename Duration::rep>::value, "");
constexpr auto num_fractional_digits =
count_fractional_digits(Duration::period::num, Duration::period::den);
count_fractional_digits<Duration::period::num,
Duration::period::den>::value;

using subsecond_precision = std::chrono::duration<
typename std::common_type<typename Duration::rep,
Expand Down Expand Up @@ -1769,8 +1779,8 @@ struct chrono_formatter {

if (ns == numeric_system::standard) {
if (std::is_floating_point<rep>::value) {
auto num_fractional_digits =
count_fractional_digits(Period::num, Period::den);
constexpr auto num_fractional_digits =
count_fractional_digits<Period::num, Period::den>::value;
auto buf = memory_buffer();
format_to(std::back_inserter(buf), runtime("{:.{}f}"),
std::fmod(val * static_cast<rep>(Period::num) /
Expand Down
4 changes: 4 additions & 0 deletions test/chrono-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<long long, std::ratio<1, 3>>(1)),
"00.333333");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<long long, std::ratio<1, 7>>(1)),
"00.142857");
}

#endif // FMT_STATIC_THOUSANDS_SEPARATOR

0 comments on commit 5985f0a

Please sign in to comment.