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/. */
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"
21 #include "nsPrintfCString.h"
22 #include "nsStringFwd.h"
24 namespace mozilla::media
{
26 } // namespace mozilla::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
42 double inBase
= aValue
* static_cast<double>(aBase
);
43 if (std::abs(inBase
) >
44 static_cast<double>(std::numeric_limits
<int64_t>::max())) {
46 nsPrintfCString("Warning: base %" PRId64
47 " is too high to represent %lfs, returning Infinity.",
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
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.",
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()),
78 TimeUnit
TimeUnit::Invalid() {
80 ret
.mTicks
= CheckedInt64(INT64_MAX
);
81 // Force an overflow to render the CheckedInt invalid.
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 {
95 return mTicks
.value();
98 return mTicks
.value() * aRate
/ mBase
;
101 bool TimeUnit::IsBase(int64_t aBase
) const { return aBase
== mBase
; }
103 double TimeUnit::ToSeconds() const {
105 return PositiveInfinity
<double>();
108 return NegativeInfinity
<double>();
110 return static_cast<double>(mTicks
.value()) / static_cast<double>(mBase
);
113 nsCString
TimeUnit::ToString() const {
115 if (mTicks
.isValid()) {
116 dump
+= nsPrintfCString("{%" PRId64
",%" PRId64
"}", mTicks
.value(), mBase
);
118 dump
+= nsLiteralCString("{invalid}"_ns
);
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
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())) {
162 if ((IsPosInf() && !aOther
.IsPosInf()) ||
163 (IsNegInf() && !aOther
.IsNegInf())) {
166 CheckedInt
<int64_t> lhs
= mTicks
* aOther
.mBase
;
167 CheckedInt
<int64_t> rhs
= aOther
.mTicks
* mBase
;
168 if (lhs
.isValid() && rhs
.isValid()) {
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
182 static_cast<double>(a
.mTicks
.value()) * static_cast<double>(a
.mBase
);
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())) {
201 if ((IsPosInf() && !aOther
.IsPosInf()) ||
202 (!IsNegInf() && aOther
.IsNegInf())) {
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())) {
234 if ((IsPosInf() && !aOther
.IsPosInf()) ||
235 (!IsNegInf() && aOther
.IsNegInf())) {
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
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()) {
288 TimeUnit inBase
= aOther
.ToBase(mBase
, error
);
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
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()) {
315 return TimeUnit(-aOther
.mTicks
, aOther
.mBase
);
319 TimeUnit inBase
= aOther
.ToBase(mBase
, error
);
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
;
334 *this = *this + aOther
;
337 TimeUnit
& TimeUnit::operator-=(const TimeUnit
& aOther
) {
338 if (aOther
.mBase
== mBase
) {
339 mTicks
-= aOther
.mTicks
;
342 *this = *this - aOther
;
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
353 " high for the result to be exact",
354 mTicks
.value(), aVal
);
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
) {
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
381 if (aRatio
< mBase
&& (mBase
% aRatio
) == 0) {
382 int64_t exactDivisor
= mBase
/ aRatio
;
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 {
421 for (const auto& interval
: mIntervals
) {
422 TimeRange reducedPrecision
{RoundToMicrosecondResolution(interval
.mStart
),
423 RoundToMicrosecondResolution(interval
.mEnd
),
424 RoundToMicrosecondResolution(interval
.mFuzz
)};
425 output
+= reducedPrecision
;
430 }; // namespace media
432 } // namespace mozilla