tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TimeUnits.cpp (13629B)


      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/. */
      6 
      7 #include "TimeUnits.h"
      8 
      9 #include <inttypes.h>
     10 
     11 #include <cmath>
     12 #include <cstdint>
     13 #include <limits>
     14 
     15 #include "Intervals.h"
     16 #include "mozilla/CheckedInt.h"
     17 #include "mozilla/FloatingPoint.h"
     18 #include "mozilla/IntegerPrintfMacros.h"
     19 #include "mozilla/TimeStamp.h"
     20 #include "nsDebug.h"
     21 #include "nsPrintfCString.h"
     22 #include "nsStringFwd.h"
     23 
     24 namespace mozilla::media {
     25 class TimeIntervals;
     26 }  // namespace mozilla::media
     27 
     28 namespace mozilla {
     29 
     30 namespace {
     31 struct Int96 {
     32  bool operator==(const Int96& aOther) const {
     33    return high == aOther.high && low == aOther.low;
     34  }
     35  bool operator>=(const Int96& aOther) const {
     36    if (high == aOther.high) {
     37      return low >= aOther.low;
     38    }
     39    return high > aOther.high;
     40  }
     41  bool operator<=(const Int96& aOther) const {
     42    if (high == aOther.high) {
     43      return low <= aOther.low;
     44    }
     45    return high < aOther.high;
     46  }
     47 
     48  const int64_t high;
     49  const uint32_t low;
     50 };
     51 }  // anonymous namespace
     52 
     53 static Int96 MultS64xU32(const CheckedInt64& a, int64_t b) {
     54  MOZ_ASSERT(b >= 0);
     55  MOZ_ASSERT(b <= UINT32_MAX);
     56  // Right shift of negative signed integers is implementation-defined until
     57  // C++20, but the C++20 two's complement behavior, used by all compilers
     58  // even prior to C++20, is assumed here.
     59  // (Left shift of negative signed integers would be undefined until C++20).
     60  int64_t high = (a.value() >> 32) * b;
     61  uint64_t low = a.value() & 0xFFFFFFFF;
     62  low *= b;
     63  // Move overflow from low multiplication to high.
     64  // This will not overflow because we have divided by 2^32 and multiplied
     65  // by b, which is less than 2^32.
     66  high += AssertedCast<int64_t>(low >> 32);
     67  return Int96{high, AssertedCast<uint32_t>(low & 0xFFFFFFFF)};
     68 };
     69 
     70 namespace media {
     71 
     72 TimeUnit TimeUnit::FromSeconds(double aValue, int64_t aBase) {
     73  MOZ_ASSERT(!std::isnan(aValue));
     74  MOZ_ASSERT(aBase > 0);
     75 
     76  if (std::isinf(aValue)) {
     77    return aValue > 0 ? FromInfinity() : FromNegativeInfinity();
     78  }
     79  // Warn that a particular value won't be able to be roundtrip at the same
     80  // base -- we can keep this for some time until we're confident this is
     81  // stable.
     82  double inBase = aValue * static_cast<double>(aBase);
     83  if (std::abs(inBase) >
     84      static_cast<double>(std::numeric_limits<int64_t>::max())) {
     85    NS_WARNING(
     86        nsPrintfCString("Warning: base %" PRId64
     87                        " is too high to represent %lfs, returning Infinity.",
     88                        aBase, aValue)
     89            .get());
     90    if (inBase > 0) {
     91      return TimeUnit::FromInfinity();
     92    }
     93    return TimeUnit::FromNegativeInfinity();
     94  }
     95 
     96  // inBase can large enough that it doesn't map to an exact integer, warn in
     97  // this case. This happens if aBase is large, and so the loss of precision is
     98  // likely small.
     99  if (inBase > std::pow(2, std::numeric_limits<double>::digits) - 1) {
    100    NS_WARNING(nsPrintfCString("Warning: base %" PRId64
    101                               " is too high to represent %lfs accurately.",
    102                               aBase, aValue)
    103                   .get());
    104  }
    105  return TimeUnit(static_cast<int64_t>(std::round(inBase)), aBase);
    106 }
    107 
    108 TimeUnit TimeUnit::FromInfinity() { return TimeUnit(INT64_MAX); }
    109 
    110 TimeUnit TimeUnit::FromNegativeInfinity() { return TimeUnit(INT64_MIN); }
    111 
    112 TimeUnit TimeUnit::FromTimeDuration(const TimeDuration& aDuration) {
    113  // This could be made to choose the base
    114  return TimeUnit(AssertedCast<int64_t>(aDuration.ToMicroseconds()),
    115                  USECS_PER_S);
    116 }
    117 
    118 TimeUnit TimeUnit::Invalid() {
    119  TimeUnit ret;
    120  ret.mTicks = CheckedInt64(INT64_MAX);
    121  // Force an overflow to render the CheckedInt invalid.
    122  ret.mTicks += 1;
    123  return ret;
    124 }
    125 
    126 int64_t TimeUnit::ToMilliseconds() const { return ToCommonUnit(MSECS_PER_S); }
    127 
    128 int64_t TimeUnit::ToMicroseconds() const { return ToCommonUnit(USECS_PER_S); }
    129 
    130 int64_t TimeUnit::ToNanoseconds() const { return ToCommonUnit(NSECS_PER_S); }
    131 
    132 int64_t TimeUnit::ToTicksAtRate(int64_t aRate) const {
    133  // Common case
    134  if (aRate == mBase) {
    135    return mTicks.value();
    136  }
    137  // Approximation
    138  return mTicks.value() * aRate / mBase;
    139 }
    140 
    141 bool TimeUnit::IsBase(int64_t aBase) const { return aBase == mBase; }
    142 
    143 double TimeUnit::ToSeconds() const {
    144  if (IsPosInf()) {
    145    return PositiveInfinity<double>();
    146  }
    147  if (IsNegInf()) {
    148    return NegativeInfinity<double>();
    149  }
    150  return static_cast<double>(mTicks.value()) / static_cast<double>(mBase);
    151 }
    152 
    153 nsCString TimeUnit::ToString() const {
    154  nsCString dump;
    155  if (mTicks.isValid()) {
    156    dump += nsPrintfCString("{%" PRId64 ",%" PRId64 "}", mTicks.value(), mBase);
    157  } else {
    158    dump += nsLiteralCString("{invalid}"_ns);
    159  }
    160  return dump;
    161 }
    162 
    163 TimeDuration TimeUnit::ToTimeDuration() const {
    164  return TimeDuration::FromSeconds(ToSeconds());
    165 }
    166 
    167 bool TimeUnit::IsInfinite() const { return IsPosInf() || IsNegInf(); }
    168 
    169 bool TimeUnit::IsPositive() const { return mTicks.value() > 0; }
    170 
    171 bool TimeUnit::IsPositiveOrZero() const { return mTicks.value() >= 0; }
    172 
    173 bool TimeUnit::IsZero() const { return mTicks.value() == 0; }
    174 
    175 bool TimeUnit::IsNegative() const { return mTicks.value() < 0; }
    176 
    177 // Returns true if the fractions are equal when converted to the smallest
    178 // base.
    179 bool TimeUnit::EqualsAtLowestResolution(const TimeUnit& aOther) const {
    180  MOZ_ASSERT(IsValid() && aOther.IsValid());
    181  if (aOther.mBase == mBase) {
    182    return mTicks == aOther.mTicks;
    183  }
    184  if (mBase > aOther.mBase) {
    185    TimeUnit thisInBase = ToBase(aOther.mBase);
    186    return thisInBase.mTicks == aOther.mTicks;
    187  }
    188  TimeUnit otherInBase = aOther.ToBase(mBase);
    189  return otherInBase.mTicks == mTicks;
    190 }
    191 
    192 // Strict equality -- the fractions must be exactly equal
    193 bool TimeUnit::operator==(const TimeUnit& aOther) const {
    194  MOZ_ASSERT(IsValid() && aOther.IsValid());
    195  if (aOther.mBase == mBase) {
    196    return mTicks == aOther.mTicks;
    197  }
    198  // debatable mathematically
    199  if ((IsPosInf() && aOther.IsPosInf()) || (IsNegInf() && aOther.IsNegInf())) {
    200    return true;
    201  }
    202  if ((IsPosInf() && !aOther.IsPosInf()) ||
    203      (IsNegInf() && !aOther.IsNegInf())) {
    204    return false;
    205  }
    206  Int96 lhs = MultS64xU32(mTicks, aOther.mBase);
    207  Int96 rhs = MultS64xU32(aOther.mTicks, mBase);
    208  return lhs == rhs;
    209 }
    210 bool TimeUnit::operator!=(const TimeUnit& aOther) const {
    211  MOZ_ASSERT(IsValid() && aOther.IsValid());
    212  return !(aOther == *this);
    213 }
    214 bool TimeUnit::operator>=(const TimeUnit& aOther) const {
    215  MOZ_ASSERT(IsValid() && aOther.IsValid());
    216  if (aOther.mBase == mBase) {
    217    return mTicks.value() >= aOther.mTicks.value();
    218  }
    219  if ((!IsPosInf() && aOther.IsPosInf()) ||
    220      (IsNegInf() && !aOther.IsNegInf())) {
    221    return false;
    222  }
    223  if ((IsPosInf() && !aOther.IsPosInf()) ||
    224      (!IsNegInf() && aOther.IsNegInf())) {
    225    return true;
    226  }
    227  Int96 lhs = MultS64xU32(mTicks, aOther.mBase);
    228  Int96 rhs = MultS64xU32(aOther.mTicks, mBase);
    229  return lhs >= rhs;
    230 }
    231 bool TimeUnit::operator>(const TimeUnit& aOther) const {
    232  return !(*this <= aOther);
    233 }
    234 bool TimeUnit::operator<=(const TimeUnit& aOther) const {
    235  MOZ_ASSERT(IsValid() && aOther.IsValid());
    236  if (aOther.mBase == mBase) {
    237    return mTicks.value() <= aOther.mTicks.value();
    238  }
    239  if ((!IsPosInf() && aOther.IsPosInf()) ||
    240      (IsNegInf() && !aOther.IsNegInf())) {
    241    return true;
    242  }
    243  if ((IsPosInf() && !aOther.IsPosInf()) ||
    244      (!IsNegInf() && aOther.IsNegInf())) {
    245    return false;
    246  }
    247  Int96 lhs = MultS64xU32(mTicks, aOther.mBase);
    248  Int96 rhs = MultS64xU32(aOther.mTicks, mBase);
    249  return lhs <= rhs;
    250 }
    251 bool TimeUnit::operator<(const TimeUnit& aOther) const {
    252  return !(*this >= aOther);
    253 }
    254 
    255 TimeUnit TimeUnit::operator%(const TimeUnit& aOther) const {
    256  MOZ_ASSERT(IsValid() && aOther.IsValid());
    257  if (aOther.mBase == mBase) {
    258    return TimeUnit(mTicks % aOther.mTicks, mBase);
    259  }
    260  // This path can be made better if need be.
    261  double a = ToSeconds();
    262  double b = aOther.ToSeconds();
    263  return TimeUnit::FromSeconds(fmod(a, b), mBase);
    264 }
    265 
    266 TimeUnit TimeUnit::operator+(const TimeUnit& aOther) const {
    267  if (IsInfinite() || aOther.IsInfinite()) {
    268    // When adding at least one infinite value, the result is either
    269    // +/-Inf, or NaN. So do the calculation in floating point for
    270    // simplicity.
    271    double result = ToSeconds() + aOther.ToSeconds();
    272    return std::isnan(result) ? TimeUnit::Invalid() : FromSeconds(result);
    273  }
    274  if (aOther.mBase == mBase) {
    275    return TimeUnit(mTicks + aOther.mTicks, mBase);
    276  }
    277  if (aOther.IsZero()) {
    278    return *this;
    279  }
    280  if (IsZero()) {
    281    return aOther;
    282  }
    283 
    284  double error;
    285  TimeUnit inBase = aOther.ToBase(mBase, error);
    286  if (error == 0.0) {
    287    return *this + inBase;
    288  }
    289 
    290  // Last ditch: not exact
    291  double a = ToSeconds();
    292  double b = aOther.ToSeconds();
    293  return TimeUnit::FromSeconds(a + b, mBase);
    294 }
    295 
    296 TimeUnit TimeUnit::operator-(const TimeUnit& aOther) const {
    297  if (IsInfinite() || aOther.IsInfinite()) {
    298    // When subtracting at least one infinite value, the result is either
    299    // +/-Inf, or NaN. So do the calculation in floating point for
    300    // simplicity.
    301    double result = ToSeconds() - aOther.ToSeconds();
    302    return std::isnan(result) ? TimeUnit::Invalid() : FromSeconds(result);
    303  }
    304  if (aOther.mBase == mBase) {
    305    return TimeUnit(mTicks - aOther.mTicks, mBase);
    306  }
    307  if (aOther.IsZero()) {
    308    return *this;
    309  }
    310 
    311  if (IsZero()) {
    312    return TimeUnit(-aOther.mTicks, aOther.mBase);
    313  }
    314 
    315  double error = 0.0;
    316  TimeUnit inBase = aOther.ToBase(mBase, error);
    317  if (error == 0) {
    318    return *this - inBase;
    319  }
    320 
    321  // Last ditch: not exact
    322  double a = ToSeconds();
    323  double b = aOther.ToSeconds();
    324  return TimeUnit::FromSeconds(a - b, mBase);
    325 }
    326 TimeUnit& TimeUnit::operator+=(const TimeUnit& aOther) {
    327  if (aOther.mBase == mBase) {
    328    mTicks += aOther.mTicks;
    329    return *this;
    330  }
    331  *this = *this + aOther;
    332  return *this;
    333 }
    334 TimeUnit& TimeUnit::operator-=(const TimeUnit& aOther) {
    335  if (aOther.mBase == mBase) {
    336    mTicks -= aOther.mTicks;
    337    return *this;
    338  }
    339  *this = *this - aOther;
    340  return *this;
    341 }
    342 
    343 TimeUnit TimeUnit::MultDouble(double aVal) const {
    344  double multiplied = AssertedCast<double>(mTicks.value()) * aVal;
    345  // Check is the result of the multiplication can be represented exactly as
    346  // an integer, in a double.
    347  if (multiplied > std::pow(2, std::numeric_limits<double>::digits) - 1) {
    348    printf_stderr("TimeUnit tick count after multiplication %" PRId64
    349                  " * %lf is too"
    350                  " high for the result to be exact",
    351                  mTicks.value(), aVal);
    352    MOZ_CRASH();
    353  }
    354  // static_cast is ok, the magnitude of the number has been checked just above.
    355  return TimeUnit(static_cast<int64_t>(multiplied), mBase);
    356 }
    357 
    358 bool TimeUnit::IsValid() const { return mTicks.isValid(); }
    359 
    360 bool TimeUnit::IsPosInf() const {
    361  return mTicks.isValid() && mTicks.value() == INT64_MAX;
    362 }
    363 bool TimeUnit::IsNegInf() const {
    364  return mTicks.isValid() && mTicks.value() == INT64_MIN;
    365 }
    366 
    367 int64_t TimeUnit::ToCommonUnit(int64_t aRatio) const {
    368  CheckedInt<int64_t> rv = mTicks;
    369  // Avoid the risk overflowing in common cases, e.g. converting a TimeUnit
    370  // with a base of 1e9 back to nanoseconds.
    371  if (mBase == aRatio) {
    372    return rv.value();
    373  }
    374  // Avoid overflowing in other common cases, e.g. converting a TimeUnit with
    375  // a base of 1e9 to microseconds: the denominator is divisible by the target
    376  // unit so we can reorder the computation and keep the number within int64_t
    377  // range.
    378  if (aRatio < mBase && (mBase % aRatio) == 0) {
    379    int64_t exactDivisor = mBase / aRatio;
    380    rv /= exactDivisor;
    381    return rv.value();
    382  }
    383  rv *= aRatio;
    384  rv /= mBase;
    385  if (rv.isValid()) {
    386    return rv.value();
    387  }
    388  // Last ditch, perform the computation in floating point.
    389  double ratioFloating = AssertedCast<double>(aRatio);
    390  double baseFloating = AssertedCast<double>(mBase);
    391  double ticksFloating = static_cast<double>(mTicks.value());
    392  double approx = ticksFloating * (ratioFloating / baseFloating);
    393  // Clamp to a valid range. If this is clamped it's outside any usable time
    394  // value even in nanoseconds (thousands of years).
    395  if (approx > static_cast<double>(std::numeric_limits<int64_t>::max())) {
    396    return std::numeric_limits<int64_t>::max();
    397  }
    398  if (approx < static_cast<double>(std::numeric_limits<int64_t>::lowest())) {
    399    return std::numeric_limits<int64_t>::lowest();
    400  }
    401  return static_cast<int64_t>(approx);
    402 }
    403 
    404 // Reduce a TimeUnit to the smallest possible ticks and base. This is useful
    405 // to comparison with big time values that can otherwise overflow.
    406 TimeUnit TimeUnit::Reduced() const {
    407  int64_t posTicks = abs(mTicks.value());
    408  bool wasNeg = mTicks.value() < 0;
    409  int64_t gcd = GCD(posTicks, mBase);
    410  int64_t signedTicks = wasNeg ? -posTicks : posTicks;
    411  signedTicks /= gcd;
    412  return TimeUnit(signedTicks, mBase / gcd);
    413 }
    414 
    415 double RoundToMicrosecondResolution(double aSeconds) {
    416  return std::round(aSeconds * USECS_PER_S) / USECS_PER_S;
    417 }
    418 
    419 TimeRanges TimeRanges::ToMicrosecondResolution() const {
    420  TimeRanges output;
    421 
    422  for (const auto& interval : mIntervals) {
    423    TimeRange reducedPrecision{RoundToMicrosecondResolution(interval.mStart),
    424                               RoundToMicrosecondResolution(interval.mEnd),
    425                               RoundToMicrosecondResolution(interval.mFuzz)};
    426    output += reducedPrecision;
    427  }
    428  return output;
    429 }
    430 
    431 };  // namespace media
    432 
    433 }  // namespace mozilla