Extend DivideRoundToNearest to support negative numbers and use for UnitBase

UnitBase own function to divide with rounding overflows when dividend is close to the max int64_t

Bug: b/262999013
Change-Id: I5b0c3f4408165a0f03690cab80bd098e506fc984
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/288521
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Commit-Queue: Danil Chapovalov <danilchap@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38943}
This commit is contained in:
Danil Chapovalov 2022-12-20 14:53:35 +00:00 committed by WebRTC LUCI CQ
parent e04c397099
commit f4b21da965
4 changed files with 38 additions and 22 deletions

View file

@ -34,13 +34,25 @@ template <typename Dividend, typename Divisor>
inline auto constexpr DivideRoundToNearest(Dividend dividend, Divisor divisor) {
static_assert(std::is_integral<Dividend>(), "");
static_assert(std::is_integral<Divisor>(), "");
RTC_DCHECK_GE(dividend, 0);
RTC_DCHECK_GT(divisor, 0);
if (dividend < Dividend{0}) {
auto half_of_divisor = divisor / 2;
auto quotient = dividend / divisor;
auto remainder = dividend % divisor;
if (rtc::SafeGt(-remainder, half_of_divisor)) {
--quotient;
}
return quotient;
}
auto half_of_divisor = (divisor - 1) / 2;
auto quotient = dividend / divisor;
auto remainder = dividend % divisor;
return quotient + (rtc::SafeGt(remainder, half_of_divisor) ? 1 : 0);
if (rtc::SafeGt(remainder, half_of_divisor)) {
++quotient;
}
return quotient;
}
} // namespace webrtc

View file

@ -39,11 +39,18 @@ TEST(DivideRoundUpTest, WorksForMaxDividend) {
TEST(DivideRoundToNearestTest, CanBeUsedAsConstexpr) {
static constexpr int kOne = DivideRoundToNearest(5, 4);
static constexpr int kTwo = DivideRoundToNearest(7, 4);
static_assert(kOne == 1, "");
static_assert(kTwo == 2, "");
static_assert(kOne == 1);
static_assert(kTwo == 2);
static_assert(DivideRoundToNearest(-5, 4) == -1);
static_assert(DivideRoundToNearest(-7, 4) == -2);
}
TEST(DivideRoundToNearestTest, DivideByOddNumber) {
EXPECT_EQ(DivideRoundToNearest(-5, 3), -2);
EXPECT_EQ(DivideRoundToNearest(-4, 3), -1);
EXPECT_EQ(DivideRoundToNearest(-3, 3), -1);
EXPECT_EQ(DivideRoundToNearest(-2, 3), -1);
EXPECT_EQ(DivideRoundToNearest(-1, 3), 0);
EXPECT_EQ(DivideRoundToNearest(0, 3), 0);
EXPECT_EQ(DivideRoundToNearest(1, 3), 0);
EXPECT_EQ(DivideRoundToNearest(2, 3), 1);
@ -54,6 +61,13 @@ TEST(DivideRoundToNearestTest, DivideByOddNumber) {
}
TEST(DivideRoundToNearestTest, DivideByEvenNumberTieRoundsUp) {
EXPECT_EQ(DivideRoundToNearest(-7, 4), -2);
EXPECT_EQ(DivideRoundToNearest(-6, 4), -1);
EXPECT_EQ(DivideRoundToNearest(-5, 4), -1);
EXPECT_EQ(DivideRoundToNearest(-4, 4), -1);
EXPECT_EQ(DivideRoundToNearest(-3, 4), -1);
EXPECT_EQ(DivideRoundToNearest(-2, 4), 0);
EXPECT_EQ(DivideRoundToNearest(-1, 4), 0);
EXPECT_EQ(DivideRoundToNearest(0, 4), 0);
EXPECT_EQ(DivideRoundToNearest(1, 4), 0);
EXPECT_EQ(DivideRoundToNearest(2, 4), 1);
@ -68,6 +82,9 @@ TEST(DivideRoundToNearestTest, LargeDivisor) {
EXPECT_EQ(DivideRoundToNearest(std::numeric_limits<int>::max() - 1,
std::numeric_limits<int>::max()),
1);
EXPECT_EQ(DivideRoundToNearest(std::numeric_limits<int>::min(),
std::numeric_limits<int>::max()),
-1);
}
TEST(DivideRoundToNearestTest, DivideSmallTypeByLargeType) {

View file

@ -10,13 +10,14 @@ import("../../webrtc.gni")
rtc_source_set("unit_base") {
visibility = [
"../../api/units:*",
":*",
"../../api/units:*",
]
sources = [ "unit_base.h" ]
deps = [
"../../rtc_base:checks",
"../../rtc_base:divide_round",
"../../rtc_base:safe_conversions",
]
}

View file

@ -18,6 +18,7 @@
#include <type_traits>
#include "rtc_base/checks.h"
#include "rtc_base/numerics/divide_round.h"
#include "rtc_base/numerics/safe_conversions.h"
namespace webrtc {
@ -154,12 +155,7 @@ class UnitBase {
constexpr typename std::enable_if<std::is_integral<T>::value, T>::type
ToFraction() const {
RTC_DCHECK(IsFinite());
if (Unit_T::one_sided) {
return rtc::dchecked_cast<T>(
DivRoundPositiveToNearest(value_, Denominator));
} else {
return rtc::dchecked_cast<T>(DivRoundToNearest(value_, Denominator));
}
return rtc::dchecked_cast<T>(DivideRoundToNearest(value_, Denominator));
}
template <int64_t Denominator, typename T>
constexpr typename std::enable_if<std::is_floating_point<T>::value, T>::type
@ -169,9 +165,7 @@ class UnitBase {
template <int64_t Denominator>
constexpr int64_t ToFractionOr(int64_t fallback_value) const {
return IsFinite() ? Unit_T::one_sided
? DivRoundPositiveToNearest(value_, Denominator)
: DivRoundToNearest(value_, Denominator)
return IsFinite() ? DivideRoundToNearest(value_, Denominator)
: fallback_value;
}
@ -205,14 +199,6 @@ class UnitBase {
constexpr const Unit_T& AsSubClassRef() const {
return static_cast<const Unit_T&>(*this);
}
// Assumes that n >= 0 and d > 0.
static constexpr int64_t DivRoundPositiveToNearest(int64_t n, int64_t d) {
return (n + d / 2) / d;
}
// Assumes that d > 0.
static constexpr int64_t DivRoundToNearest(int64_t n, int64_t d) {
return (n + (n >= 0 ? d / 2 : -d / 2)) / d;
}
int64_t value_;
};