AK+Tests: Add time units conversion functions to Duration

These take a numerator and denominator defining the unit in a fractions
of a second. Conversion is done in integers, meaning that these must
clamp when approaching numeric limits.
This commit is contained in:
Zaggy1024 2025-11-10 16:07:17 -06:00 committed by Jelle Raaijmakers
parent ec14948175
commit a1358fa970
Notes: github-actions[bot] 2025-11-17 15:53:19 +00:00
3 changed files with 81 additions and 0 deletions

View file

@ -67,6 +67,28 @@ Duration Duration::from_timeval(const struct timeval& tv)
return Duration::from_half_sanitized(tv.tv_sec, extra_secs, usecs * 1'000);
}
Duration Duration::from_time_units(i64 time_units, u32 numerator, u32 denominator)
{
VERIFY(numerator != 0);
VERIFY(denominator != 0);
auto seconds_checked = Checked<i64>(time_units);
seconds_checked.mul(numerator);
seconds_checked.div(denominator);
if (time_units < 0)
seconds_checked.sub(1);
if (seconds_checked.has_overflow())
return Duration(time_units >= 0 ? NumericLimits<i64>::max() : NumericLimits<i64>::min(), 0);
auto seconds = seconds_checked.value_unchecked();
auto seconds_in_time_units = seconds * denominator / numerator;
auto remainder_in_time_units = time_units - seconds_in_time_units;
auto nanoseconds = ((remainder_in_time_units * 1'000'000'000 * numerator) + (denominator / 2)) / denominator;
VERIFY(nanoseconds >= 0);
VERIFY(nanoseconds < 1'000'000'000);
return Duration(seconds, static_cast<u32>(nanoseconds));
}
i64 Duration::to_truncated_seconds() const
{
VERIFY(m_nanoseconds < 1'000'000'000);
@ -196,6 +218,22 @@ timeval Duration::to_timeval() const
return { static_cast<sec_type>(m_seconds), static_cast<usec_type>(m_nanoseconds) / 1000 };
}
i64 Duration::to_time_units(u32 numerator, u32 denominator) const
{
VERIFY(numerator != 0);
VERIFY(denominator != 0);
auto seconds_product = Checked<i64>::saturating_mul(m_seconds, denominator);
auto time_units = seconds_product / numerator;
auto remainder = seconds_product % numerator;
auto remainder_in_nanoseconds = remainder * 1'000'000'000;
auto rounding_half = static_cast<i64>(numerator) * 500'000'000;
time_units = Checked<i64>::saturating_add(time_units, ((static_cast<i64>(m_nanoseconds) * denominator + remainder_in_nanoseconds + rounding_half) / numerator) / 1'000'000'000);
return time_units;
}
Duration Duration::from_half_sanitized(i64 seconds, i32 extra_seconds, u32 nanoseconds)
{
VERIFY(nanoseconds < 1'000'000'000);

View file

@ -245,6 +245,7 @@ public:
[[nodiscard]] static Duration from_ticks(clock_t, time_t);
[[nodiscard]] static Duration from_timespec(const struct timespec&);
[[nodiscard]] static Duration from_timeval(const struct timeval&);
[[nodiscard]] static Duration from_time_units(i64 units, u32 numerator, u32 denominator);
// We don't pull in <stdint.h> for the pretty min/max definitions because this file is also included in the Kernel
[[nodiscard]] constexpr static Duration min() { return Duration(-__INT64_MAX__ - 1LL, 0); }
[[nodiscard]] constexpr static Duration zero() { return Duration(0, 0); }
@ -263,6 +264,7 @@ public:
[[nodiscard]] timespec to_timespec() const;
// Rounds towards -inf (it was the easiest to implement).
[[nodiscard]] timeval to_timeval() const;
[[nodiscard]] i64 to_time_units(u32 numerator, u32 denominator) const;
[[nodiscard]] bool is_zero() const { return (m_seconds == 0) && (m_nanoseconds == 0); }
[[nodiscard]] bool is_negative() const { return m_seconds < 0; }

View file

@ -939,3 +939,44 @@ TEST_CASE(from_f64_seconds)
EXPECT_DEATH("Converting float NaN seconds", (void)Duration::from_seconds_f64(NAN));
}
TEST_CASE(time_units)
{
EXPECT_EQ(Duration::from_time_units(1, 1, 1), Duration::from_seconds(1));
EXPECT_EQ(Duration::from_time_units(-312, 1, 48'000), Duration::from_microseconds(-6'500));
EXPECT_EQ(Duration::from_time_units(960, 1, 48'000), Duration::from_microseconds(20'000));
EXPECT_EQ(Duration::from_time_units(960, 1, 48'000), Duration::from_microseconds(20'000));
EXPECT_EQ(Duration::from_time_units(8, 4, 1), Duration::from_seconds(32));
EXPECT_EQ(Duration::from_time_units(3, 3, 2'000'000'000), Duration::from_nanoseconds(5));
EXPECT_EQ(Duration::from_time_units(4, 3, 2'000'000'000), Duration::from_nanoseconds(6));
EXPECT_EQ(Duration::from_time_units(999'999'998, 1, 2'000'000'000), Duration::from_nanoseconds(499'999'999));
EXPECT_EQ(Duration::from_time_units(999'999'999, 1, 2'000'000'000), Duration::from_nanoseconds(500'000'000));
EXPECT_EQ(Duration::from_time_units(1'000'000'000, 1, 2'000'000'000), Duration::from_nanoseconds(500'000'000));
EXPECT_EQ(Duration::from_time_units(NumericLimits<i64>::max(), 1, 2), Duration::from_seconds(NumericLimits<i64>::max() / 2) + Duration::from_milliseconds(500));
EXPECT_EQ(Duration::from_time_units((NumericLimits<i64>::max() / 2), 2, 1), Duration::from_seconds(NumericLimits<i64>::max() - 1));
EXPECT_EQ(Duration::from_time_units((NumericLimits<i64>::max() / 2) + 1, 2, 1), Duration::from_seconds(NumericLimits<i64>::max()));
EXPECT_EQ(Duration::from_time_units((NumericLimits<i64>::min() / 2), 2, 1), Duration::from_seconds(NumericLimits<i64>::min()));
EXPECT_EQ(Duration::from_time_units((NumericLimits<i64>::min() / 2) - 1, 2, 1), Duration::from_seconds(NumericLimits<i64>::min()));
EXPECT_EQ(Duration::from_milliseconds(999).to_time_units(1, 48'000), 47'952);
EXPECT_EQ(Duration::from_milliseconds(-12'500).to_time_units(1, 1'000), -12'500);
EXPECT_EQ(Duration::from_milliseconds(-12'500).to_time_units(1, 1'000), -12'500);
EXPECT_EQ(Duration::from_nanoseconds(154'489'696).to_time_units(1, 48'000), 7'416);
EXPECT_EQ(Duration::from_nanoseconds(154'489'375).to_time_units(1, 48'000), 7'415);
EXPECT_EQ(Duration::from_nanoseconds(-154'489'696).to_time_units(1, 48'000), -7'416);
EXPECT_EQ(Duration::from_nanoseconds(-154'489'375).to_time_units(1, 48'000), -7'415);
EXPECT_EQ(Duration::from_nanoseconds(1'900'000'000).to_time_units(3, 2), 1);
EXPECT_EQ(Duration::from_nanoseconds(1'800'000'000).to_time_units(3, 1), 1);
EXPECT_EQ(Duration::from_seconds(3).to_time_units(4, 1), 1);
EXPECT_EQ(Duration::from_seconds(4).to_time_units(4, 1), 1);
EXPECT_EQ(Duration::from_seconds(5).to_time_units(4, 1), 1);
EXPECT_EQ(Duration::from_seconds(6).to_time_units(4, 1), 2);
EXPECT_EQ(Duration::from_seconds(2'147'483'649).to_time_units(1, NumericLimits<u32>::max()), NumericLimits<i64>::max());
EXPECT_EQ(Duration::from_seconds(2'147'483'648).to_time_units(1, NumericLimits<u32>::max()), NumericLimits<i64>::max() - (NumericLimits<u32>::max() / 2));
EXPECT_DEATH("From time units with zero numerator", (void)Duration::from_time_units(1, 0, 1));
EXPECT_DEATH("From time units with zero denominator", (void)Duration::from_time_units(1, 1, 0));
}