Skip to content

Commit a1358fa

Browse files
Zaggy1024gmta
authored andcommitted
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.
1 parent ec14948 commit a1358fa

File tree

3 files changed

+81
-0
lines changed

3 files changed

+81
-0
lines changed

AK/Time.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,28 @@ Duration Duration::from_timeval(const struct timeval& tv)
6767
return Duration::from_half_sanitized(tv.tv_sec, extra_secs, usecs * 1'000);
6868
}
6969

70+
Duration Duration::from_time_units(i64 time_units, u32 numerator, u32 denominator)
71+
{
72+
VERIFY(numerator != 0);
73+
VERIFY(denominator != 0);
74+
75+
auto seconds_checked = Checked<i64>(time_units);
76+
seconds_checked.mul(numerator);
77+
seconds_checked.div(denominator);
78+
if (time_units < 0)
79+
seconds_checked.sub(1);
80+
81+
if (seconds_checked.has_overflow())
82+
return Duration(time_units >= 0 ? NumericLimits<i64>::max() : NumericLimits<i64>::min(), 0);
83+
auto seconds = seconds_checked.value_unchecked();
84+
auto seconds_in_time_units = seconds * denominator / numerator;
85+
auto remainder_in_time_units = time_units - seconds_in_time_units;
86+
auto nanoseconds = ((remainder_in_time_units * 1'000'000'000 * numerator) + (denominator / 2)) / denominator;
87+
VERIFY(nanoseconds >= 0);
88+
VERIFY(nanoseconds < 1'000'000'000);
89+
return Duration(seconds, static_cast<u32>(nanoseconds));
90+
}
91+
7092
i64 Duration::to_truncated_seconds() const
7193
{
7294
VERIFY(m_nanoseconds < 1'000'000'000);
@@ -196,6 +218,22 @@ timeval Duration::to_timeval() const
196218
return { static_cast<sec_type>(m_seconds), static_cast<usec_type>(m_nanoseconds) / 1000 };
197219
}
198220

221+
i64 Duration::to_time_units(u32 numerator, u32 denominator) const
222+
{
223+
VERIFY(numerator != 0);
224+
VERIFY(denominator != 0);
225+
226+
auto seconds_product = Checked<i64>::saturating_mul(m_seconds, denominator);
227+
auto time_units = seconds_product / numerator;
228+
auto remainder = seconds_product % numerator;
229+
230+
auto remainder_in_nanoseconds = remainder * 1'000'000'000;
231+
auto rounding_half = static_cast<i64>(numerator) * 500'000'000;
232+
time_units = Checked<i64>::saturating_add(time_units, ((static_cast<i64>(m_nanoseconds) * denominator + remainder_in_nanoseconds + rounding_half) / numerator) / 1'000'000'000);
233+
234+
return time_units;
235+
}
236+
199237
Duration Duration::from_half_sanitized(i64 seconds, i32 extra_seconds, u32 nanoseconds)
200238
{
201239
VERIFY(nanoseconds < 1'000'000'000);

AK/Time.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ class Duration {
245245
[[nodiscard]] static Duration from_ticks(clock_t, time_t);
246246
[[nodiscard]] static Duration from_timespec(const struct timespec&);
247247
[[nodiscard]] static Duration from_timeval(const struct timeval&);
248+
[[nodiscard]] static Duration from_time_units(i64 units, u32 numerator, u32 denominator);
248249
// We don't pull in <stdint.h> for the pretty min/max definitions because this file is also included in the Kernel
249250
[[nodiscard]] constexpr static Duration min() { return Duration(-__INT64_MAX__ - 1LL, 0); }
250251
[[nodiscard]] constexpr static Duration zero() { return Duration(0, 0); }
@@ -263,6 +264,7 @@ class Duration {
263264
[[nodiscard]] timespec to_timespec() const;
264265
// Rounds towards -inf (it was the easiest to implement).
265266
[[nodiscard]] timeval to_timeval() const;
267+
[[nodiscard]] i64 to_time_units(u32 numerator, u32 denominator) const;
266268

267269
[[nodiscard]] bool is_zero() const { return (m_seconds == 0) && (m_nanoseconds == 0); }
268270
[[nodiscard]] bool is_negative() const { return m_seconds < 0; }

Tests/AK/TestTime.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,3 +939,44 @@ TEST_CASE(from_f64_seconds)
939939

940940
EXPECT_DEATH("Converting float NaN seconds", (void)Duration::from_seconds_f64(NAN));
941941
}
942+
943+
TEST_CASE(time_units)
944+
{
945+
EXPECT_EQ(Duration::from_time_units(1, 1, 1), Duration::from_seconds(1));
946+
EXPECT_EQ(Duration::from_time_units(-312, 1, 48'000), Duration::from_microseconds(-6'500));
947+
EXPECT_EQ(Duration::from_time_units(960, 1, 48'000), Duration::from_microseconds(20'000));
948+
EXPECT_EQ(Duration::from_time_units(960, 1, 48'000), Duration::from_microseconds(20'000));
949+
EXPECT_EQ(Duration::from_time_units(8, 4, 1), Duration::from_seconds(32));
950+
EXPECT_EQ(Duration::from_time_units(3, 3, 2'000'000'000), Duration::from_nanoseconds(5));
951+
EXPECT_EQ(Duration::from_time_units(4, 3, 2'000'000'000), Duration::from_nanoseconds(6));
952+
EXPECT_EQ(Duration::from_time_units(999'999'998, 1, 2'000'000'000), Duration::from_nanoseconds(499'999'999));
953+
EXPECT_EQ(Duration::from_time_units(999'999'999, 1, 2'000'000'000), Duration::from_nanoseconds(500'000'000));
954+
EXPECT_EQ(Duration::from_time_units(1'000'000'000, 1, 2'000'000'000), Duration::from_nanoseconds(500'000'000));
955+
956+
EXPECT_EQ(Duration::from_time_units(NumericLimits<i64>::max(), 1, 2), Duration::from_seconds(NumericLimits<i64>::max() / 2) + Duration::from_milliseconds(500));
957+
EXPECT_EQ(Duration::from_time_units((NumericLimits<i64>::max() / 2), 2, 1), Duration::from_seconds(NumericLimits<i64>::max() - 1));
958+
EXPECT_EQ(Duration::from_time_units((NumericLimits<i64>::max() / 2) + 1, 2, 1), Duration::from_seconds(NumericLimits<i64>::max()));
959+
EXPECT_EQ(Duration::from_time_units((NumericLimits<i64>::min() / 2), 2, 1), Duration::from_seconds(NumericLimits<i64>::min()));
960+
EXPECT_EQ(Duration::from_time_units((NumericLimits<i64>::min() / 2) - 1, 2, 1), Duration::from_seconds(NumericLimits<i64>::min()));
961+
962+
EXPECT_EQ(Duration::from_milliseconds(999).to_time_units(1, 48'000), 47'952);
963+
EXPECT_EQ(Duration::from_milliseconds(-12'500).to_time_units(1, 1'000), -12'500);
964+
EXPECT_EQ(Duration::from_milliseconds(-12'500).to_time_units(1, 1'000), -12'500);
965+
966+
EXPECT_EQ(Duration::from_nanoseconds(154'489'696).to_time_units(1, 48'000), 7'416);
967+
EXPECT_EQ(Duration::from_nanoseconds(154'489'375).to_time_units(1, 48'000), 7'415);
968+
EXPECT_EQ(Duration::from_nanoseconds(-154'489'696).to_time_units(1, 48'000), -7'416);
969+
EXPECT_EQ(Duration::from_nanoseconds(-154'489'375).to_time_units(1, 48'000), -7'415);
970+
EXPECT_EQ(Duration::from_nanoseconds(1'900'000'000).to_time_units(3, 2), 1);
971+
EXPECT_EQ(Duration::from_nanoseconds(1'800'000'000).to_time_units(3, 1), 1);
972+
EXPECT_EQ(Duration::from_seconds(3).to_time_units(4, 1), 1);
973+
EXPECT_EQ(Duration::from_seconds(4).to_time_units(4, 1), 1);
974+
EXPECT_EQ(Duration::from_seconds(5).to_time_units(4, 1), 1);
975+
EXPECT_EQ(Duration::from_seconds(6).to_time_units(4, 1), 2);
976+
977+
EXPECT_EQ(Duration::from_seconds(2'147'483'649).to_time_units(1, NumericLimits<u32>::max()), NumericLimits<i64>::max());
978+
EXPECT_EQ(Duration::from_seconds(2'147'483'648).to_time_units(1, NumericLimits<u32>::max()), NumericLimits<i64>::max() - (NumericLimits<u32>::max() / 2));
979+
980+
EXPECT_DEATH("From time units with zero numerator", (void)Duration::from_time_units(1, 0, 1));
981+
EXPECT_DEATH("From time units with zero denominator", (void)Duration::from_time_units(1, 1, 0));
982+
}

0 commit comments

Comments
 (0)