From 38a5c36a35534340c68abeda647d931189d86b1a Mon Sep 17 00:00:00 2001
From: itsmattkc <34096995+itsmattkc@users.noreply.github.com>
Date: Mon, 2 May 2022 10:31:59 -0700
Subject: [PATCH] rational: rewrite
Our rational is now a much thinner wrapper around AVRational. Their code is better than ours and using it greatly reduces our own maintenance cost
---
app/common/rational.cpp | 338 ++++++++++------------------------------
app/common/rational.h | 109 +++++++------
2 files changed, 135 insertions(+), 312 deletions(-)
diff --git a/app/common/rational.cpp b/app/common/rational.cpp
index d7d374230..2efad3f1f 100644
--- a/app/common/rational.cpp
+++ b/app/common/rational.cpp
@@ -1,5 +1,22 @@
-//Copyright 2015 Adam Quintero
-//This program is distributed under the terms of the GNU General Public License.
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 Olive Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+***/
#include "rational.h"
@@ -16,7 +33,7 @@ rational rational::fromDouble(const double &flt, bool* ok)
}
// Use FFmpeg function for the time being
- AVRational r = av_d2q(flt, 65535);
+ AVRational r = av_d2q(flt, INT_MAX);
if (r.den == 0) {
// If den == 0, we were unable to convert to a rational
@@ -39,9 +56,9 @@ rational rational::fromString(const QString &str, bool* ok)
switch (elements.size()) {
case 1:
- return rational(elements.first().toLongLong(ok));
+ return rational(elements.first().toInt(ok));
case 2:
- return rational(elements.at(0).toLongLong(ok), elements.at(1).toLongLong(ok));
+ return rational(elements.at(0).toInt(ok), elements.at(1).toInt(ok));
default:
// Returns NaN with ok set to false
if (ok) {
@@ -51,63 +68,12 @@ rational rational::fromString(const QString &str, bool* ok)
}
}
-//Function: ensures denom >= 0
-
-void rational::fix_signs()
-{
- // Normalize so that denominator is always positive and only numerator is positive
- if (denom_ < 0) {
- denom_ = -denom_;
- numer_ = -numer_;
- } else if (denom_ == intType(0)) {
- // Normalize to 0/0 (aka NaN) if denominator is zero
- numer_ = intType(0);
- } else if (numer_ == intType(0)) {
- // Normalize to 0/1 if numerator is zero
- denom_ = intType(1);
- }
-}
-
-//Function: ensures lowest form
-
-void rational::reduce()
-{
- if (!isNull() && denom_ != 1) {
- // Euclidean often fails if numbers are negative, we abs it and re-neg it later if necessary
- bool neg = numer_ < 0;
-
- numer_ = qAbs(numer_);
-
- intType d = gcd(numer_, denom_);
-
- if (d > 1) {
- numer_ /= d;
- denom_ /= d;
- }
-
- if (neg) {
- numer_ = -numer_;
- }
- }
-}
-
-//Function: finds greatest common denominator
-
-intType rational::gcd(const intType &x, const intType &y)
-{
- if (y == 0) {
- return x;
- } else {
- return gcd(y, x % y);
- }
-}
-
//Function: convert to double
double rational::toDouble() const
{
- if (denom_ != 0) {
- return static_cast(numer_) / static_cast(denom_);
+ if (r_.den != 0) {
+ return av_q2d(r_);
} else {
return qSNaN();
}
@@ -115,12 +81,7 @@ double rational::toDouble() const
AVRational rational::toAVRational() const
{
- AVRational r;
-
- r.num = static_cast(numer_);
- r.den = static_cast(denom_);
-
- return r;
+ return r_;
}
#ifdef USE_OTIO
@@ -143,44 +104,41 @@ rational rational::flipped() const
void rational::flip()
{
if (!isNull()) {
- std::swap(denom_, numer_);
+ std::swap(r_.den, r_.num);
+ FixSigns();
}
}
-bool rational::isNull() const
-{
- return numerator() == 0;
-}
-
-bool rational::isNaN() const
-{
- return denominator() == 0;
-}
-
-const intType &rational::numerator() const
-{
- return numer_;
-}
-
-const intType &rational::denominator() const
-{
- return denom_;
-}
-
QString rational::toString() const
{
- return QStringLiteral("%1/%2").arg(QString::number(numer_), QString::number(denom_));
+ return QStringLiteral("%1/%2").arg(QString::number(r_.num), QString::number(r_.den));
+}
+
+void rational::FixSigns()
+{
+ if (r_.den < 0) {
+ // Normalize so that denominator is always positive
+ r_.den = -r_.den;
+ r_.num = -r_.num;
+ } else if (r_.den == 0) {
+ // Normalize to 0/0 (aka NaN) if denominator is zero
+ r_.num = 0;
+ } else if (r_.num == 0) {
+ // Normalize to 0/1 if numerator is zero
+ r_.den = 1;
+ }
+}
+
+void rational::Reduce()
+{
+ av_reduce(&r_.num, &r_.den, r_.num, r_.den, INT_MAX);
}
//Assignment Operators
const rational& rational::operator=(const rational &rhs)
{
- if (this != &rhs) {
- numer_ = rhs.numer_;
- denom_ = rhs.denom_;
- }
-
+ r_ = rhs.r_;
return *this;
}
@@ -190,19 +148,10 @@ const rational& rational::operator+=(const rational &rhs)
if (!isNaN()) {
if (rhs.isNaN()) {
- // Set to NaN
- denom_ = 0;
- fix_signs();
- } else if (!rhs.isNull()) {
- if (isNull()) {
- numer_ = rhs.numer_;
- denom_ = rhs.denom_;
- } else {
- numer_ = (numer_ * rhs.denom_) + (rhs.numer_ * denom_);
- denom_ = denom_ * rhs.denom_;
- fix_signs();
- reduce();
- }
+ *this = NaN;
+ } else {
+ r_ = av_add_q(r_, rhs.r_);
+ FixSigns();
}
}
@@ -215,39 +164,10 @@ const rational& rational::operator-=(const rational &rhs)
if (!isNaN()) {
if (rhs.isNaN()) {
- // Set to NaN
- denom_ = 0;
- fix_signs();
- } else if (!rhs.isNull()) {
- if (isNull()) {
- numer_ = -rhs.numer_;
- denom_ = rhs.denom_;
- } else {
- numer_ = (numer_ * rhs.denom_) - (rhs.numer_ * denom_);
- denom_ = denom_ * rhs.denom_;
- fix_signs();
- reduce();
- }
- }
- }
-
- return *this;
-}
-
-const rational& rational::operator/=(const rational &rhs)
-{
- Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
-
- if (!isNaN()) {
- if (rhs.isNaN()) {
- // Set to NaN
- denom_ = 0;
- fix_signs();
+ *this = NaN;
} else {
- numer_ = numer_ * rhs.denom_;
- denom_ = denom_ * rhs.numer_;
- fix_signs();
- reduce();
+ r_ = av_sub_q(r_, rhs.r_);
+ FixSigns();
}
}
@@ -260,13 +180,26 @@ const rational& rational::operator*=(const rational &rhs)
if (!isNaN()) {
if (rhs.isNaN()) {
- denom_ = 0;
- fix_signs();
+ *this = NaN;
} else {
- numer_ = numer_ * rhs.numer_;
- denom_ = denom_ * rhs.denom_;
- fix_signs();
- reduce();
+ r_ = av_mul_q(r_, rhs.r_);
+ FixSigns();
+ }
+ }
+
+ return *this;
+}
+
+const rational& rational::operator/=(const rational &rhs)
+{
+ Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
+
+ if (!isNaN()) {
+ if (rhs.isNaN()) {
+ *this = NaN;
+ } else {
+ r_ = av_div_q(r_, rhs.r_);
+ FixSigns();
}
}
@@ -307,87 +240,29 @@ rational rational::operator*(const rational &rhs) const
bool rational::operator<(const rational &rhs) const
{
- if (isNaN() || rhs.isNaN()) {
- return false;
- }
-
- if (isNull() && rhs.isNull()) {
- return false;
- }
-
- if (rhs == RATIONAL_MAX
- || *this == RATIONAL_MIN) {
- // We will always either be LESS THAN (true) or EQUAL (false)
- return (*this != rhs);
- }
-
- if (*this == RATIONAL_MAX
- || rhs == RATIONAL_MIN) {
- // We will always be GREATER THAN (false) or EQUAL (false)
- return false;
- }
-
- if (!isNull() && rhs.isNull()) {
- return (numer_ * denom_ < intType(0));
- }
-
- if (isNull() && !rhs.isNull()) {
- return !(rhs.numer_ * rhs.denom_ < intType(0));
- }
-
- return ((numer_ * rhs.denom_) < (denom_ * rhs.numer_));
+ return av_cmp_q(r_, rhs.r_) == -1;
}
bool rational::operator<=(const rational &rhs) const
{
- if (isNaN() || rhs.isNaN()) {
- return false;
- }
-
- if (isNull() && rhs.isNull()) {
- return true;
- }
-
- if (rhs == RATIONAL_MAX
- || *this == RATIONAL_MIN) {
- // We will always either be LESS THAN (true) or EQUAL (true)
- return true;
- }
-
- if (*this == RATIONAL_MAX
- || rhs == RATIONAL_MIN) {
- // We will always be GREATER THAN (false) or EQUAL (true)
- return rhs == *this;
- }
-
- if (!isNull() && rhs.isNull()) {
- return (numer_ * denom_ < intType(0));
- }
-
- if (isNull() && !rhs.isNull()) {
- return !(rhs.numer_ * rhs.denom_ < intType(0));
- }
-
- return ((numer_ * rhs.denom_) <= (denom_ * rhs.numer_));
+ int cmp = av_cmp_q(r_, rhs.r_);
+ return cmp == 0 || cmp == -1;
}
bool rational::operator>(const rational &rhs) const
{
- return rhs < *this;
+ return av_cmp_q(r_, rhs.r_) == 1;
}
bool rational::operator>=(const rational &rhs) const
{
- return rhs <= *this;
+ int cmp = av_cmp_q(r_, rhs.r_);
+ return cmp == 0 || cmp == 1;
}
bool rational::operator==(const rational &rhs) const
{
- if (isNaN() || rhs.isNaN()) {
- return false;
- }
-
- return (numer_ == rhs.numer_ && denom_ == rhs.denom_);
+ return av_cmp_q(r_, rhs.r_) == 0;
}
bool rational::operator!=(const rational &rhs) const
@@ -395,55 +270,6 @@ bool rational::operator!=(const rational &rhs) const
return !(*this == rhs);
}
-const rational& rational::operator+() const
-{
- return *this;
-}
-
-rational rational::operator-() const
-{
- return rational(numer_, -denom_);
-}
-
-bool rational::operator!() const
-{
- return !numer_;
-}
-
-//IO
-
-std::ostream& operator<<(std::ostream &out, const rational &value)
-{
- out << value.numer_;
-
- if (value.denom_ != 1) {
- out << '/' << value.denom_;
- return out;
- }
-
- return out;
-}
-
-std::istream& operator>>(std::istream &in, rational &value)
-{
- in >> value.numer_;
- value.denom_ = 1;
-
- char ch;
- in.get(ch);
-
- if(!in.eof()) {
- if(ch == '/') {
- in >> value.denom_;
- value.fix_signs();
- value.reduce();
- } else {
- in.putback(ch);
- }
- }
- return in;
-}
-
uint qHash(const rational &r, uint seed)
{
return ::qHash(r.toDouble(), seed);
diff --git a/app/common/rational.h b/app/common/rational.h
index 26f625fc6..73c839c81 100644
--- a/app/common/rational.h
+++ b/app/common/rational.h
@@ -1,60 +1,66 @@
-//Copyright 2015 Adam Quintero
-//This program is distributed under the terms of the GNU General Public License.
+/***
-// Adapted by MattKC for the Olive Video Editor (2019-2022)
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 Olive Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+***/
#ifndef RATIONAL_H
#define RATIONAL_H
-#include
+
+extern "C" {
+#include
+}
+
+#include
+#include
#ifdef USE_OTIO
#include
#endif
-#include
-#include
-
-extern "C" {
-#include
-}
-
#include "common/define.h"
namespace olive {
-typedef int64_t intType;
-/*
- * Zero Handling
- * 0/0 = 0
- * 0/non-zero = 0
- * non-zero/0 = 0
-*/
class rational
{
public:
- //constructors
- rational(const intType &numerator = 0) :
- numer_(numerator),
- denom_(1)
+ rational(const int &numerator = 0)
{
+ r_.num = numerator;
+ r_.den = 1;
}
- rational(const intType &numerator, const intType &denominator) :
- numer_(numerator),
- denom_(denominator)
+ rational(const int &numerator, const int &denominator)
{
- fix_signs();
- reduce();
+ r_.num = numerator;
+ r_.den = denominator;
+
+ FixSigns();
+ Reduce();
}
rational(const rational &rhs) = default;
- rational(const AVRational& r) :
- numer_(r.num),
- denom_(r.den)
+ rational(const AVRational& r)
{
- fix_signs();
- reduce();
+ r_ = r;
+
+ FixSigns();
}
static rational fromDouble(const double& flt, bool *ok = nullptr);
@@ -84,9 +90,9 @@ public:
bool operator!=(const rational &rhs) const;
//Unary operators
- const rational& operator+() const;
- rational operator-() const;
- bool operator!() const;
+ const rational& operator+() const { return *this; }
+ rational operator-() const { return rational(r_.num, -r_.den); }
+ bool operator!() const { return !r_.num; }
//Function: convert to double
double toDouble() const;
@@ -100,7 +106,7 @@ public:
return fromDouble(t.to_seconds());
}
- // Convert Olive ratioanls to opentime rationals with the given framerate (defaults to 24)
+ // Convert Olive rationals to opentime rationals with the given framerate (defaults to 24)
opentime::RationalTime toRationalTime(double framerate = 24) const;
#endif
@@ -111,35 +117,26 @@ public:
// Returns whether the rational is valid but equal to zero or not
//
// A NaN is always a null, but a null is not always a NaN
- bool isNull() const;
+ bool isNull() const { return r_.num == 0; }
- // Returns whether this rational is not a valid number
- bool isNaN() const;
+ // Returns whether this rational is not a valid number (denominator == 0)
+ bool isNaN() const { return r_.den == 0; }
- //IO
- friend std::ostream& operator<<(std::ostream &out, const rational &value);
- friend std::istream& operator>>(std::istream &in, rational &value);
-
- const intType& numerator() const;
- const intType& denominator() const;
+ const int& numerator() const { return r_.num; }
+ const int& denominator() const { return r_.den; }
QString toString() const;
private:
- //numerator and denominator
- intType numer_;
- intType denom_;
+ void FixSigns();
+ void Reduce();
+
+ AVRational r_;
- //Function: ensures denom >= 0
- void fix_signs();
- //Function: ensures lowest form
- void reduce();
- //Function: finds greatest common denominator
- static intType gcd(const intType &x, const intType &y);
};
-#define RATIONAL_MIN rational(INT64_MIN, 1)
-#define RATIONAL_MAX rational(INT64_MAX, 1)
+#define RATIONAL_MIN rational(INT_MIN)
+#define RATIONAL_MAX rational(INT_MAX)
uint qHash(const rational& r, uint seed = 0);