Backed out changeset 06f41c22f3a6 (bug 1888460) for causing linux xpcshell failures...
[gecko.git] / dom / media / TimeUnits.cpp
blobe5865bb3c487b3877b80c6b72c1aa5547c8eaa12
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include <cstdint>
8 #include <cmath>
9 #include <inttypes.h>
10 #include <limits>
11 #include <type_traits>
13 #include "TimeUnits.h"
14 #include "Intervals.h"
15 #include "mozilla/CheckedInt.h"
16 #include "mozilla/FloatingPoint.h"
17 #include "mozilla/Maybe.h"
18 #include "mozilla/TimeStamp.h"
19 #include "mozilla/IntegerPrintfMacros.h"
20 #include "nsDebug.h"
21 #include "nsPrintfCString.h"
22 #include "nsStringFwd.h"
24 namespace mozilla::media {
25 class TimeIntervals;
26 } // namespace mozilla::media
28 namespace mozilla {
30 namespace media {
32 TimeUnit TimeUnit::FromSeconds(double aValue, int64_t aBase) {
33 MOZ_ASSERT(!std::isnan(aValue));
34 MOZ_ASSERT(aBase > 0);
36 if (std::isinf(aValue)) {
37 return aValue > 0 ? FromInfinity() : FromNegativeInfinity();
39 // Warn that a particular value won't be able to be roundtrip at the same
40 // base -- we can keep this for some time until we're confident this is
41 // stable.
42 double inBase = aValue * static_cast<double>(aBase);
43 if (std::abs(inBase) >
44 static_cast<double>(std::numeric_limits<int64_t>::max())) {
45 NS_WARNING(
46 nsPrintfCString("Warning: base %" PRId64
47 " is too high to represent %lfs, returning Infinity.",
48 aBase, aValue)
49 .get());
50 if (inBase > 0) {
51 return TimeUnit::FromInfinity();
53 return TimeUnit::FromNegativeInfinity();
56 // inBase can large enough that it doesn't map to an exact integer, warn in
57 // this case. This happens if aBase is large, and so the loss of precision is
58 // likely small.
59 if (inBase > std::pow(2, std::numeric_limits<double>::digits) - 1) {
60 NS_WARNING(nsPrintfCString("Warning: base %" PRId64
61 " is too high to represent %lfs accurately.",
62 aBase, aValue)
63 .get());
65 return TimeUnit(static_cast<int64_t>(std::round(inBase)), aBase);
68 TimeUnit TimeUnit::FromInfinity() { return TimeUnit(INT64_MAX); }
70 TimeUnit TimeUnit::FromNegativeInfinity() { return TimeUnit(INT64_MIN); }
72 TimeUnit TimeUnit::FromTimeDuration(const TimeDuration& aDuration) {
73 // This could be made to choose the base
74 return TimeUnit(AssertedCast<int64_t>(aDuration.ToMicroseconds()),
75 USECS_PER_S);
78 TimeUnit TimeUnit::Invalid() {
79 TimeUnit ret;
80 ret.mTicks = CheckedInt64(INT64_MAX);
81 // Force an overflow to render the CheckedInt invalid.
82 ret.mTicks += 1;
83 return ret;
86 int64_t TimeUnit::ToMilliseconds() const { return ToCommonUnit(MSECS_PER_S); }
88 int64_t TimeUnit::ToMicroseconds() const { return ToCommonUnit(USECS_PER_S); }
90 int64_t TimeUnit::ToNanoseconds() const { return ToCommonUnit(NSECS_PER_S); }
92 int64_t TimeUnit::ToTicksAtRate(int64_t aRate) const {
93 // Common case
94 if (aRate == mBase) {
95 return mTicks.value();
97 // Approximation
98 return mTicks.value() * aRate / mBase;
101 bool TimeUnit::IsBase(int64_t aBase) const { return aBase == mBase; }
103 double TimeUnit::ToSeconds() const {
104 if (IsPosInf()) {
105 return PositiveInfinity<double>();
107 if (IsNegInf()) {
108 return NegativeInfinity<double>();
110 return static_cast<double>(mTicks.value()) / static_cast<double>(mBase);
113 nsCString TimeUnit::ToString() const {
114 nsCString dump;
115 if (mTicks.isValid()) {
116 dump += nsPrintfCString("{%" PRId64 ",%" PRId64 "}", mTicks.value(), mBase);
117 } else {
118 dump += nsLiteralCString("{invalid}"_ns);
120 return dump;
123 TimeDuration TimeUnit::ToTimeDuration() const {
124 return TimeDuration::FromSeconds(ToSeconds());
127 bool TimeUnit::IsInfinite() const { return IsPosInf() || IsNegInf(); }
129 bool TimeUnit::IsPositive() const { return mTicks.value() > 0; }
131 bool TimeUnit::IsPositiveOrZero() const { return mTicks.value() >= 0; }
133 bool TimeUnit::IsZero() const { return mTicks.value() == 0; }
135 bool TimeUnit::IsNegative() const { return mTicks.value() < 0; }
137 // Returns true if the fractions are equal when converted to the smallest
138 // base.
139 bool TimeUnit::EqualsAtLowestResolution(const TimeUnit& aOther) const {
140 MOZ_ASSERT(IsValid() && aOther.IsValid());
141 if (aOther.mBase == mBase) {
142 return mTicks == aOther.mTicks;
144 if (mBase > aOther.mBase) {
145 TimeUnit thisInBase = ToBase(aOther.mBase);
146 return thisInBase.mTicks == aOther.mTicks;
148 TimeUnit otherInBase = aOther.ToBase(mBase);
149 return otherInBase.mTicks == mTicks;
152 // Strict equality -- the fractions must be exactly equal
153 bool TimeUnit::operator==(const TimeUnit& aOther) const {
154 MOZ_ASSERT(IsValid() && aOther.IsValid());
155 if (aOther.mBase == mBase) {
156 return mTicks == aOther.mTicks;
158 // debatable mathematically
159 if ((IsPosInf() && aOther.IsPosInf()) || (IsNegInf() && aOther.IsNegInf())) {
160 return true;
162 if ((IsPosInf() && !aOther.IsPosInf()) ||
163 (IsNegInf() && !aOther.IsNegInf())) {
164 return false;
166 CheckedInt<int64_t> lhs = mTicks * aOther.mBase;
167 CheckedInt<int64_t> rhs = aOther.mTicks * mBase;
168 if (lhs.isValid() && rhs.isValid()) {
169 return lhs == rhs;
171 // Reduce the fractions and try again
172 const TimeUnit a = Reduced();
173 const TimeUnit b = aOther.Reduced();
174 lhs = a.mTicks * b.mBase;
175 rhs = b.mTicks * a.mBase;
177 if (lhs.isValid() && rhs.isValid()) {
178 return lhs.value() == rhs.value();
180 // last ditch, convert the reduced fractions to doubles
181 double lhsFloating =
182 static_cast<double>(a.mTicks.value()) * static_cast<double>(a.mBase);
183 double rhsFloating =
184 static_cast<double>(b.mTicks.value()) * static_cast<double>(b.mBase);
186 return lhsFloating == rhsFloating;
188 bool TimeUnit::operator!=(const TimeUnit& aOther) const {
189 MOZ_ASSERT(IsValid() && aOther.IsValid());
190 return !(aOther == *this);
192 bool TimeUnit::operator>=(const TimeUnit& aOther) const {
193 MOZ_ASSERT(IsValid() && aOther.IsValid());
194 if (aOther.mBase == mBase) {
195 return mTicks.value() >= aOther.mTicks.value();
197 if ((!IsPosInf() && aOther.IsPosInf()) ||
198 (IsNegInf() && !aOther.IsNegInf())) {
199 return false;
201 if ((IsPosInf() && !aOther.IsPosInf()) ||
202 (!IsNegInf() && aOther.IsNegInf())) {
203 return true;
205 CheckedInt<int64_t> lhs = mTicks * aOther.mBase;
206 CheckedInt<int64_t> rhs = aOther.mTicks * mBase;
207 if (lhs.isValid() && rhs.isValid()) {
208 return lhs.value() >= rhs.value();
210 // Reduce the fractions and try again
211 const TimeUnit a = Reduced();
212 const TimeUnit b = aOther.Reduced();
213 lhs = a.mTicks * b.mBase;
214 rhs = b.mTicks * a.mBase;
216 if (lhs.isValid() && rhs.isValid()) {
217 return lhs.value() >= rhs.value();
219 // last ditch, convert the reduced fractions to doubles
220 return ToSeconds() >= aOther.ToSeconds();
222 bool TimeUnit::operator>(const TimeUnit& aOther) const {
223 return !(*this <= aOther);
225 bool TimeUnit::operator<=(const TimeUnit& aOther) const {
226 MOZ_ASSERT(IsValid() && aOther.IsValid());
227 if (aOther.mBase == mBase) {
228 return mTicks.value() <= aOther.mTicks.value();
230 if ((!IsPosInf() && aOther.IsPosInf()) ||
231 (IsNegInf() && !aOther.IsNegInf())) {
232 return true;
234 if ((IsPosInf() && !aOther.IsPosInf()) ||
235 (!IsNegInf() && aOther.IsNegInf())) {
236 return false;
238 CheckedInt<int64_t> lhs = mTicks * aOther.mBase;
239 CheckedInt<int64_t> rhs = aOther.mTicks * mBase;
240 if (lhs.isValid() && rhs.isValid()) {
241 return lhs.value() <= rhs.value();
243 // Reduce the fractions and try again
244 const TimeUnit a = Reduced();
245 const TimeUnit b = aOther.Reduced();
246 lhs = a.mTicks * b.mBase;
247 rhs = b.mTicks * a.mBase;
248 if (lhs.isValid() && rhs.isValid()) {
249 return lhs.value() <= rhs.value();
251 // last ditch, convert the reduced fractions to doubles
252 return ToSeconds() <= aOther.ToSeconds();
254 bool TimeUnit::operator<(const TimeUnit& aOther) const {
255 return !(*this >= aOther);
258 TimeUnit TimeUnit::operator%(const TimeUnit& aOther) const {
259 MOZ_ASSERT(IsValid() && aOther.IsValid());
260 if (aOther.mBase == mBase) {
261 return TimeUnit(mTicks % aOther.mTicks, mBase);
263 // This path can be made better if need be.
264 double a = ToSeconds();
265 double b = aOther.ToSeconds();
266 return TimeUnit::FromSeconds(fmod(a, b), mBase);
269 TimeUnit TimeUnit::operator+(const TimeUnit& aOther) const {
270 if (IsInfinite() || aOther.IsInfinite()) {
271 // When adding at least one infinite value, the result is either
272 // +/-Inf, or NaN. So do the calculation in floating point for
273 // simplicity.
274 double result = ToSeconds() + aOther.ToSeconds();
275 return std::isnan(result) ? TimeUnit::Invalid() : FromSeconds(result);
277 if (aOther.mBase == mBase) {
278 return TimeUnit(mTicks + aOther.mTicks, mBase);
280 if (aOther.IsZero()) {
281 return *this;
283 if (IsZero()) {
284 return aOther;
287 double error;
288 TimeUnit inBase = aOther.ToBase(mBase, error);
289 if (error == 0.0) {
290 return *this + inBase;
293 // Last ditch: not exact
294 double a = ToSeconds();
295 double b = aOther.ToSeconds();
296 return TimeUnit::FromSeconds(a + b, mBase);
299 TimeUnit TimeUnit::operator-(const TimeUnit& aOther) const {
300 if (IsInfinite() || aOther.IsInfinite()) {
301 // When subtracting at least one infinite value, the result is either
302 // +/-Inf, or NaN. So do the calculation in floating point for
303 // simplicity.
304 double result = ToSeconds() - aOther.ToSeconds();
305 return std::isnan(result) ? TimeUnit::Invalid() : FromSeconds(result);
307 if (aOther.mBase == mBase) {
308 return TimeUnit(mTicks - aOther.mTicks, mBase);
310 if (aOther.IsZero()) {
311 return *this;
314 if (IsZero()) {
315 return TimeUnit(-aOther.mTicks, aOther.mBase);
318 double error = 0.0;
319 TimeUnit inBase = aOther.ToBase(mBase, error);
320 if (error == 0) {
321 return *this - inBase;
324 // Last ditch: not exact
325 double a = ToSeconds();
326 double b = aOther.ToSeconds();
327 return TimeUnit::FromSeconds(a - b, mBase);
329 TimeUnit& TimeUnit::operator+=(const TimeUnit& aOther) {
330 if (aOther.mBase == mBase) {
331 mTicks += aOther.mTicks;
332 return *this;
334 *this = *this + aOther;
335 return *this;
337 TimeUnit& TimeUnit::operator-=(const TimeUnit& aOther) {
338 if (aOther.mBase == mBase) {
339 mTicks -= aOther.mTicks;
340 return *this;
342 *this = *this - aOther;
343 return *this;
346 TimeUnit TimeUnit::MultDouble(double aVal) const {
347 double multiplied = AssertedCast<double>(mTicks.value()) * aVal;
348 // Check is the result of the multiplication can be represented exactly as
349 // an integer, in a double.
350 if (multiplied > std::pow(2, std::numeric_limits<double>::digits) - 1) {
351 printf_stderr("TimeUnit tick count after multiplication %" PRId64
352 " * %lf is too"
353 " high for the result to be exact",
354 mTicks.value(), aVal);
355 MOZ_CRASH();
357 // static_cast is ok, the magnitude of the number has been checked just above.
358 return TimeUnit(static_cast<int64_t>(multiplied), mBase);
361 bool TimeUnit::IsValid() const { return mTicks.isValid(); }
363 bool TimeUnit::IsPosInf() const {
364 return mTicks.isValid() && mTicks.value() == INT64_MAX;
366 bool TimeUnit::IsNegInf() const {
367 return mTicks.isValid() && mTicks.value() == INT64_MIN;
370 int64_t TimeUnit::ToCommonUnit(int64_t aRatio) const {
371 CheckedInt<int64_t> rv = mTicks;
372 // Avoid the risk overflowing in common cases, e.g. converting a TimeUnit
373 // with a base of 1e9 back to nanoseconds.
374 if (mBase == aRatio) {
375 return rv.value();
377 // Avoid overflowing in other common cases, e.g. converting a TimeUnit with
378 // a base of 1e9 to microseconds: the denominator is divisible by the target
379 // unit so we can reorder the computation and keep the number within int64_t
380 // range.
381 if (aRatio < mBase && (mBase % aRatio) == 0) {
382 int64_t exactDivisor = mBase / aRatio;
383 rv /= exactDivisor;
384 return rv.value();
386 rv *= aRatio;
387 rv /= mBase;
388 if (rv.isValid()) {
389 return rv.value();
391 // Last ditch, perform the computation in floating point.
392 double ratioFloating = AssertedCast<double>(aRatio);
393 double baseFloating = AssertedCast<double>(mBase);
394 double ticksFloating = static_cast<double>(mTicks.value());
395 double approx = ticksFloating * (ratioFloating / baseFloating);
396 // Clamp to a valid range. If this is clamped it's outside any usable time
397 // value even in nanoseconds (thousands of years).
398 if (approx > static_cast<double>(std::numeric_limits<int64_t>::max())) {
399 return std::numeric_limits<int64_t>::max();
401 if (approx < static_cast<double>(std::numeric_limits<int64_t>::lowest())) {
402 return std::numeric_limits<int64_t>::lowest();
404 return static_cast<int64_t>(approx);
407 // Reduce a TimeUnit to the smallest possible ticks and base. This is useful
408 // to comparison with big time values that can otherwise overflow.
409 TimeUnit TimeUnit::Reduced() const {
410 int64_t gcd = GCD(mTicks.value(), mBase);
411 return TimeUnit(mTicks.value() / gcd, mBase / gcd);
414 double RoundToMicrosecondResolution(double aSeconds) {
415 return std::round(aSeconds * USECS_PER_S) / USECS_PER_S;
418 TimeRanges TimeRanges::ToMicrosecondResolution() const {
419 TimeRanges output;
421 for (const auto& interval : mIntervals) {
422 TimeRange reducedPrecision{RoundToMicrosecondResolution(interval.mStart),
423 RoundToMicrosecondResolution(interval.mEnd),
424 RoundToMicrosecondResolution(interval.mFuzz)};
425 output += reducedPrecision;
427 return output;
430 }; // namespace media
432 } // namespace mozilla