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) { inline auto constexpr DivideRoundToNearest(Dividend dividend, Divisor divisor) {
static_assert(std::is_integral<Dividend>(), ""); static_assert(std::is_integral<Dividend>(), "");
static_assert(std::is_integral<Divisor>(), ""); static_assert(std::is_integral<Divisor>(), "");
RTC_DCHECK_GE(dividend, 0);
RTC_DCHECK_GT(divisor, 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 half_of_divisor = (divisor - 1) / 2;
auto quotient = dividend / divisor; auto quotient = dividend / divisor;
auto remainder = 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 } // namespace webrtc

View file

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

View file

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

View file

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