Fix overflow for chrono durations (#2722)

This commit is contained in:
matrackif
2022-01-17 21:14:59 +01:00
committed by GitHub
parent 8f8a1a02d5
commit 5985f0a7d2
2 changed files with 23 additions and 9 deletions

View File

@@ -1470,14 +1470,23 @@ inline std::chrono::duration<Rep, std::milli> get_milliseconds(
#endif #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 // C++20 spec. If more than 18 fractional digits are required then returns 6 for
// microseconds precision. // microseconds precision.
constexpr int count_fractional_digits(long long num, long long den, int n = 0) { template <long long Num, long long Den, int N = 0,
return num % den == 0 bool Enabled =
? n (N < 19) && (Num <= std::numeric_limits<long long>::max() / 10)>
: (n > 18 ? 6 : count_fractional_digits(num * 10, den, n + 1)); 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) { constexpr long long pow10(std::uint32_t n) {
return n == 0 ? 1 : 10 * pow10(n - 1); return n == 0 ? 1 : 10 * pow10(n - 1);
@@ -1666,7 +1675,8 @@ struct chrono_formatter {
template <typename Duration> void write_fractional_seconds(Duration d) { template <typename Duration> void write_fractional_seconds(Duration d) {
FMT_ASSERT(!std::is_floating_point<typename Duration::rep>::value, ""); FMT_ASSERT(!std::is_floating_point<typename Duration::rep>::value, "");
constexpr auto num_fractional_digits = 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< using subsecond_precision = std::chrono::duration<
typename std::common_type<typename Duration::rep, typename std::common_type<typename Duration::rep,
@@ -1769,8 +1779,8 @@ struct chrono_formatter {
if (ns == numeric_system::standard) { if (ns == numeric_system::standard) {
if (std::is_floating_point<rep>::value) { if (std::is_floating_point<rep>::value) {
auto num_fractional_digits = constexpr auto num_fractional_digits =
count_fractional_digits(Period::num, Period::den); count_fractional_digits<Period::num, Period::den>::value;
auto buf = memory_buffer(); auto buf = memory_buffer();
format_to(std::back_inserter(buf), runtime("{:.{}f}"), format_to(std::back_inserter(buf), runtime("{:.{}f}"),
std::fmod(val * static_cast<rep>(Period::num) / std::fmod(val * static_cast<rep>(Period::num) /

View File

@@ -623,6 +623,10 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
// fixed precision, and print zeros even if there is no fractional part. // fixed precision, and print zeros even if there is no fractional part.
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}), EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}),
"07.000000"); "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 #endif // FMT_STATIC_THOUSANDS_SEPARATOR