diff --git a/AampTime.h b/AampTime.h index 5d8844dab..0c98941f2 100644 --- a/AampTime.h +++ b/AampTime.h @@ -29,12 +29,10 @@ struct AampTicks { int64_t ticks; uint32_t timescale; - /// @brief Constructor /// @param ticks /// @param timescale AampTicks(int64_t ticks, uint32_t timescale) : ticks(ticks), timescale(timescale) {} - /// @brief Get time in milliseconds int64_t inMilli() { return (ticks * 1000) / (int64_t)timescale; } }; @@ -42,177 +40,189 @@ struct AampTicks /// @brief time class to work around the use of doubles within Aamp // While operators are overloaded for comparisons, the underlying data type is integer // But the code is tolerant of being treated as a double +#include class AampTime { - public: - typedef enum { milli = 1000, micro = 1000000, nano = 1000000000 } TimeScale; - - private: - static const uint64_t baseTimescale = nano; - int64_t baseTime; - - public: - /// @brief Constructor - /// @param seconds time in seconds, as a double - constexpr AampTime(double seconds = 0.0) : baseTime(int64_t(seconds * baseTimescale)){} - - /// @brief Copy constructor - /// @param rhs AampTime object to copy - constexpr AampTime(const AampTime& rhs) : baseTime(rhs.baseTime){} - - /// @brief Constructor - /// @param time struct containing time in ticks and timescale - /// @note This is used to convert from AampTicks to AampTime; it is lossy and cannot be converted back - constexpr AampTime(AampTicks &time) : baseTime((time.ticks * (int64_t)baseTimescale) / (int64_t)time.timescale) {} - - /// @brief Get the stored time - /// @return Time in seconds (double) - inline double inSeconds() const { return (baseTime / double(baseTimescale)); } - - /// @brief Get the stored time in seconds - /// @return Time in seconds (integer) - inline int64_t seconds() const { return (baseTime / baseTimescale); } - - /// @brief Get the stored time in milliseconds - /// @return Time in milliseconds (integer) - inline int64_t milliseconds() const { return (baseTime / (baseTimescale / milli)); } - - // Equivalent to round() but in integer domain - inline int64_t nearestSecond() const - { - int64_t retval = this->seconds(); - - // Fractional part - int64_t tempval = baseTime - retval * baseTimescale; - - if (tempval >= ((5 * baseTimescale)/10)) - { - retval += 1; - } - - return retval; - } - - // Overloads for comparison operators to check AampTime : AampTime and AampTime : double - // Converting (and truncating) the double to the timescale should avoid the issues around epsilon for floating point - inline bool operator==(const AampTime &rhs) const - { - if (this == &rhs) - return true; - else - return (baseTime == rhs.baseTime); - } - - inline bool operator==(const double &rhs) const { return (baseTime == int64_t(rhs * baseTimescale)); } - - inline AampTime& operator=(const AampTime &rhs) - { - if (this == &rhs) - return *this; - - baseTime = rhs.baseTime; - return *this; - } - - inline AampTime& operator=(const double &rhs) - { - baseTime = int64_t(rhs * baseTimescale); - return *this; - } - - inline AampTime operator-() const - { - AampTime temp(*this); - temp.baseTime = -baseTime; - return temp; - } - - inline bool operator!=(const AampTime &rhs) const { return !(*this == rhs); } - inline bool operator!=(double &rhs) const { return !(*this == rhs); } - - inline bool operator>(const AampTime &rhs) const { return (baseTime > rhs.baseTime); } - inline bool operator>(const double &rhs) const { return (baseTime > int64_t(rhs * baseTimescale)); } - - inline bool operator<(const AampTime &rhs) const { return ((*this != rhs) && (!(*this > rhs))); } - inline bool operator<(const double &rhs) const { return ((*this != rhs) && (!(*this > rhs))); } - - inline bool operator>=(const AampTime &rhs) const { return ((*this > rhs) || (*this == rhs)); } - inline bool operator>=(double rhs) const { return ((*this > rhs) || (*this == rhs)); } - - inline bool operator<=(const AampTime &rhs) const { return ((*this < rhs) || (*this == rhs)); } - inline bool operator<=(double rhs) const { return ((*this < rhs) || (*this == rhs)); } - - inline AampTime operator+(const AampTime &t) const - { - AampTime temp(*this); - - temp.baseTime = baseTime + t.baseTime; - return temp; - } - inline AampTime operator+(const double &t) const - { - AampTime temp(*this); - - temp.baseTime = baseTime + int64_t(t * baseTimescale); - return std::move(temp); - } - - inline const AampTime &operator+=(const AampTime &t) - { - *this = *this + t; - return *this; - } - inline const AampTime &operator+=(const double &t) - { - *this = *this + t; - return *this; - } - - inline AampTime operator-(const AampTime &t) const - { - AampTime temp(*this); - - temp.baseTime = baseTime - t.baseTime; - return std::move(temp); +public: + typedef enum { milli = 1000, micro = 1000000, nano = 1000000000 } TimeScale; +private: + static const uint64_t baseTimescale = nano; + int64_t baseTime; + +public: + /// @brief Constructor + /// @param seconds time in seconds, as a double + constexpr AampTime(double seconds = 0.0) : baseTime(int64_t(seconds * baseTimescale)){} + + /// @brief Copy constructor + /// @param rhs AampTime object to copy + constexpr AampTime(const AampTime& rhs) : baseTime(rhs.baseTime){} + + /// @brief Constructor + /// @param time struct containing time in ticks and timescale + /// @note This is used to convert from AampTicks to AampTime; it is lossy and cannot be converted back + AampTime(const AampTicks& time) : baseTime(time.ticks) + { + int64_t threshold = INT64_MAX / baseTimescale; + if( baseTime < threshold && baseTime > -threshold ) + { // no overflow - use integer math + baseTime *= baseTimescale; + baseTime /= (int64_t)time.timescale; } - - inline AampTime operator-(const double &t) const - { - AampTime temp(*this); - temp.baseTime = baseTime - int64_t(t * baseTimescale); - return std::move(temp); + else + { // overflow - fallback to floating point math + baseTime *= (baseTimescale / (double)time.timescale); } - - inline const AampTime &operator-=(const AampTime &t) + } + + /// @brief Get the stored time + /// @return Time in seconds (double) + inline double inSeconds() const { return (baseTime / double(baseTimescale)); } + + /// @brief Get the stored time in seconds + /// @return Time in seconds (integer) + inline int64_t seconds() const { return (baseTime / baseTimescale); } + + /// @brief Get the stored time in milliseconds + /// @return Time in milliseconds (integer) + inline int64_t milliseconds() const { return (baseTime / (baseTimescale / milli)); } + + // Equivalent to round() but in integer domain + inline int64_t nearestSecond() const + { + int64_t retval = this->seconds(); + + // Fractional part + int64_t tempval = baseTime - retval * baseTimescale; + + if (tempval >= ((5 * baseTimescale)/10)) { - *this = *this - t; - return *this; + retval += 1; } - inline const AampTime &operator-=(const double &t) - { - *this = *this - t; + + return retval; + } + + // Overloads for comparison operators to check AampTime : AampTime and AampTime : double + // Converting (and truncating) the double to the timescale should avoid the issues around epsilon for floating point + inline bool operator==(const AampTime &rhs) const + { + if (this == &rhs) + return true; + else + return (baseTime == rhs.baseTime); + } + + inline bool operator==(const double &rhs) const { return (baseTime == int64_t(rhs * baseTimescale)); } + + inline AampTime& operator=(const AampTime &rhs) + { + if (this == &rhs) return *this; - } - - inline AampTime operator/(const double &t) const - { - AampTime temp(*this); - - temp.baseTime = (int64_t)((double)baseTime/t); - return std::move(temp); - } - - inline AampTime operator*(const double &t) const - { - AampTime temp(*this); - - temp.baseTime = (int64_t)((double)baseTime * t); - return std::move(temp); - } - - explicit operator double() const { return this->inSeconds(); } - explicit operator int64_t() const { return this->seconds(); } + + baseTime = rhs.baseTime; + return *this; + } + + inline AampTime& operator=(const double &rhs) + { + baseTime = int64_t(rhs * baseTimescale); + return *this; + } + + inline AampTime operator-() const + { + AampTime temp(*this); + temp.baseTime = -baseTime; + return temp; + } + + inline bool operator!=(const AampTime &rhs) const { return !(*this == rhs); } + inline bool operator!=(double &rhs) const { return !(*this == rhs); } + + inline bool operator>(const AampTime &rhs) const { return (baseTime > rhs.baseTime); } + inline bool operator>(const double &rhs) const { return (baseTime > int64_t(rhs * baseTimescale)); } + + inline bool operator<(const AampTime &rhs) const { return ((*this != rhs) && (!(*this > rhs))); } + inline bool operator<(const double &rhs) const { return ((*this != rhs) && (!(*this > rhs))); } + + inline bool operator>=(const AampTime &rhs) const { return ((*this > rhs) || (*this == rhs)); } + inline bool operator>=(double rhs) const { return ((*this > rhs) || (*this == rhs)); } + + inline bool operator<=(const AampTime &rhs) const { return ((*this < rhs) || (*this == rhs)); } + inline bool operator<=(double rhs) const { return ((*this < rhs) || (*this == rhs)); } + + inline AampTime operator+(const AampTime &t) const + { + AampTime temp(*this); + + temp.baseTime = baseTime + t.baseTime; + return temp; + } + inline AampTime operator+(const double &t) const + { + AampTime temp(*this); + + temp.baseTime = baseTime + int64_t(t * baseTimescale); + return std::move(temp); + } + + inline const AampTime &operator+=(const AampTime &t) + { + *this = *this + t; + return *this; + } + inline const AampTime &operator+=(const double &t) + { + *this = *this + t; + return *this; + } + + inline AampTime operator-(const AampTime &t) const + { + AampTime temp(*this); + + temp.baseTime = baseTime - t.baseTime; + return std::move(temp); + } + + inline AampTime operator-(const double &t) const + { + AampTime temp(*this); + temp.baseTime = baseTime - int64_t(t * baseTimescale); + return std::move(temp); + } + + inline const AampTime &operator-=(const AampTime &t) + { + *this = *this - t; + return *this; + } + inline const AampTime &operator-=(const double &t) + { + *this = *this - t; + return *this; + } + + inline AampTime operator/(const double &t) const + { + AampTime temp(*this); + + temp.baseTime = (int64_t)((double)baseTime/t); + return std::move(temp); + } + + inline AampTime operator*(const double &t) const + { + AampTime temp(*this); + + temp.baseTime = (int64_t)((double)baseTime * t); + return std::move(temp); + } + + explicit operator double() const { return this->inSeconds(); } + explicit operator int64_t() const { return this->seconds(); } }; // For those who like if (0.0 == b) diff --git a/test/utests/tests/AampTimeTests/validateAampTimeOverloads.cpp b/test/utests/tests/AampTimeTests/validateAampTimeOverloads.cpp index 2ea67939f..9bf4bbd0b 100644 --- a/test/utests/tests/AampTimeTests/validateAampTimeOverloads.cpp +++ b/test/utests/tests/AampTimeTests/validateAampTimeOverloads.cpp @@ -359,3 +359,40 @@ TEST_F(validateAampTimeOverloads, AampTicksInMilli) AampTicks ticks(5000, 1000); // 5000 ticks with a timescale of 1000 EXPECT_EQ(ticks.inMilli(), 5000); // 5000 milliseconds } + +TEST_F(validateAampTimeOverloads, testTimescaleConversionNoOverflow) +{ // Small values that won't overflow + { + int64_t rawTicks = 1000; + uint32_t scale = 1000; + AampTicks ticks(rawTicks, scale); + AampTime t(ticks); + EXPECT_NEAR(t.inSeconds(), 1.0, 0.000001); + } + { + int64_t rawTicks = -1000; + uint32_t scale = 1000; + AampTicks ticks(rawTicks, scale); + AampTime t(ticks); + EXPECT_NEAR(t.inSeconds(), -1.0, 0.000001); + } +} + +TEST_F(validateAampTimeOverloads, testTimescaleConversionWithOverflow) +{ // Large values that will overflow int64_t * 1000000000 + { + int64_t rawTicks = 927996007213; + uint32_t scale = 240000; + AampTicks ticks(rawTicks, scale); + AampTime t(ticks); + EXPECT_NEAR(t.inSeconds(), 3866650.03005416, 0.001); + } + + { + int64_t rawTicks = -927996007213; + uint32_t scale = 240000; + AampTicks ticks(rawTicks, scale); + AampTime t(ticks); + EXPECT_NEAR(t.inSeconds(), -3866650.03005416, 0.001); + } +}