diff --git a/AK/Time.cpp b/AK/Time.cpp index 2e1ecca693..de52acba26 100644 --- a/AK/Time.cpp +++ b/AK/Time.cpp @@ -95,6 +95,45 @@ Duration Duration::from_time_units(i64 time_units, u32 numerator, u32 denominato return Duration(seconds, static_cast(nanoseconds)); } +Duration Duration::scaled_by(u32 numerator, u32 denominator) const +{ + VERIFY(denominator != 0); + + if (numerator == 0) + return Duration::zero(); + if (numerator == denominator) + return *this; + + if (Checked::multiplication_would_overflow(m_seconds, numerator)) + return Duration(m_seconds < 0 ? NumericLimits::min() : NumericLimits::max(), 0); + auto seconds_product = m_seconds * static_cast(numerator); + auto seconds = seconds_product / static_cast(denominator); + auto seconds_remainder = seconds_product % static_cast(denominator); + if (seconds_remainder < 0) { + if (seconds == NumericLimits::min()) + return Duration(NumericLimits::min(), 0); + seconds--; + seconds_remainder += denominator; + } + + auto numerator_ns = seconds_remainder * 1'000'000'000; + numerator_ns += m_nanoseconds * static_cast(numerator); + auto total_ns = (numerator_ns + denominator / 2) / static_cast(denominator); + + auto extra_seconds = total_ns / 1'000'000'000; + auto nanoseconds = total_ns % 1'000'000'000; + if (extra_seconds > 0) { + Checked total_seconds = seconds; + total_seconds += extra_seconds; + if (total_seconds.has_overflow()) + return Duration(NumericLimits::max(), 999'999'999); + seconds = total_seconds.value(); + } + VERIFY(nanoseconds >= 0); + VERIFY(nanoseconds < 1'000'000'000); + return Duration(seconds, static_cast(nanoseconds)); +} + i64 Duration::to_truncated_seconds() const { VERIFY(m_nanoseconds < 1'000'000'000); diff --git a/AK/Time.h b/AK/Time.h index 0a10ff4b92..ad41cbddd6 100644 --- a/AK/Time.h +++ b/AK/Time.h @@ -247,6 +247,7 @@ public: [[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); + [[nodiscard]] Duration scaled_by(u32 numerator, u32 denominator) const; // We don't pull in 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); } diff --git a/Tests/AK/TestTime.cpp b/Tests/AK/TestTime.cpp index bc47c9c769..b6a233b651 100644 --- a/Tests/AK/TestTime.cpp +++ b/Tests/AK/TestTime.cpp @@ -991,3 +991,51 @@ TEST_CASE(time_units) 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)); } + +TEST_CASE(scaled_by) +{ + // Identity and trivial cases. + EXPECT_EQ(Duration::zero().scaled_by(5, 7), Duration::zero()); + EXPECT_EQ(Duration::from_seconds(7).scaled_by(0, 1), Duration::zero()); + EXPECT_EQ(Duration::from_seconds(7).scaled_by(1, 1), Duration::from_seconds(7)); + EXPECT_EQ(Duration::from_milliseconds(2'500).scaled_by(5, 5), Duration::from_milliseconds(2'500)); + + // Halving and doubling. + EXPECT_EQ(Duration::from_seconds(2).scaled_by(1, 2), Duration::from_seconds(1)); + EXPECT_EQ(Duration::from_seconds(1).scaled_by(2, 1), Duration::from_seconds(2)); + EXPECT_EQ(Duration::from_milliseconds(500).scaled_by(2, 1), Duration::from_seconds(1)); + EXPECT_EQ(Duration::from_seconds(1).scaled_by(1, 2), Duration::from_milliseconds(500)); + + // Fractional scales using both seconds and nanoseconds parts. + EXPECT_EQ(Duration::from_milliseconds(2'500).scaled_by(1, 3), Duration::from_nanoseconds(833'333'333)); + EXPECT_EQ(Duration::from_milliseconds(2'500).scaled_by(2, 3), Duration::from_nanoseconds(1'666'666'667)); + EXPECT_EQ(Duration::from_seconds(3).scaled_by(7, 2), Duration::from_milliseconds(10'500)); + EXPECT_EQ(Duration::from_milliseconds(1).scaled_by(48'000, 1'000), Duration::from_milliseconds(48)); + + // Negative durations: -1.5s × 2/3 → -1s exactly. + EXPECT_EQ(Duration::from_milliseconds(-1'500).scaled_by(2, 3), Duration::from_seconds(-1)); + EXPECT_EQ(Duration::from_seconds(-3).scaled_by(2, 3), Duration::from_seconds(-2)); + EXPECT_EQ(Duration::from_milliseconds(-500).scaled_by(3, 2), Duration::from_milliseconds(-750)); + EXPECT_EQ(Duration::from_nanoseconds(-1).scaled_by(1, 1), Duration::from_nanoseconds(-1)); + + // Round-to-nearest at the nanosecond boundary. + EXPECT_EQ(Duration::from_nanoseconds(1).scaled_by(1, 2), Duration::from_nanoseconds(1)); // 0.5 ns rounds up + EXPECT_EQ(Duration::from_nanoseconds(1).scaled_by(1, 3), Duration::from_nanoseconds(0)); // 0.333 ns rounds down + EXPECT_EQ(Duration::from_nanoseconds(2).scaled_by(1, 3), Duration::from_nanoseconds(1)); // 0.667 ns rounds up + EXPECT_EQ(Duration::from_nanoseconds(3).scaled_by(2, 3), Duration::from_nanoseconds(2)); // 2.0 exact + EXPECT_EQ(Duration::from_nanoseconds(-1).scaled_by(1, 2), Duration::from_nanoseconds(0)); + + // Carrying nanoseconds past 1e9 into seconds. + EXPECT_EQ(Duration::from_nanoseconds(999'999'999).scaled_by(1, 1), Duration::from_nanoseconds(999'999'999)); + EXPECT_EQ((Duration::from_seconds(0) + Duration::from_nanoseconds(999'999'999)).scaled_by(2, 1), + Duration::from_seconds(1) + Duration::from_nanoseconds(999'999'998)); + + // Saturation on m_seconds × numerator overflow. + EXPECT_EQ(Duration::from_seconds(NumericLimits::max()).scaled_by(2, 1), + Duration::from_seconds(NumericLimits::max())); + EXPECT_EQ(Duration::from_seconds(NumericLimits::min()).scaled_by(2, 1), + Duration::from_seconds(NumericLimits::min())); + + // Death on zero denominator. + EXPECT_DEATH("Scaled by with zero denominator", (void)Duration::from_seconds(1).scaled_by(1, 0)); +}