mirror of
https://gitlab.freedesktop.org/monado/monado.git
synced 2024-12-28 02:26:16 +00:00
a/math: Add a rational number struct template.
This commit is contained in:
parent
4b94d83c11
commit
928254ffed
|
@ -50,8 +50,9 @@ add_library(
|
|||
math/m_predict.c
|
||||
math/m_predict.h
|
||||
math/m_quatexpmap.cpp
|
||||
math/m_relation_history.h
|
||||
math/m_rational.hpp
|
||||
math/m_relation_history.cpp
|
||||
math/m_relation_history.h
|
||||
math/m_space.cpp
|
||||
math/m_space.h
|
||||
math/m_vec2.h
|
||||
|
|
209
src/xrt/auxiliary/math/m_rational.hpp
Normal file
209
src/xrt/auxiliary/math/m_rational.hpp
Normal file
|
@ -0,0 +1,209 @@
|
|||
// Copyright 2022, Collabora, Ltd.
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
/*!
|
||||
* @file
|
||||
* @brief A very simple rational number type.
|
||||
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
|
||||
* @ingroup aux_math
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __cplusplus
|
||||
#error "This header is C++-only."
|
||||
#endif
|
||||
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrt::auxiliary::math {
|
||||
|
||||
/*!
|
||||
* A rational (fractional) number type.
|
||||
*/
|
||||
template <typename Scalar> struct Rational
|
||||
{
|
||||
static_assert(std::is_integral_v<Scalar>, "This type is only for use with integer components.");
|
||||
|
||||
using value_type = Scalar;
|
||||
value_type numerator;
|
||||
value_type denominator;
|
||||
|
||||
/*!
|
||||
* Return the rational value 1/1, the simplest unity (== 1) value.
|
||||
*/
|
||||
static constexpr Rational<Scalar>
|
||||
simplestUnity() noexcept
|
||||
{
|
||||
return {1, 1};
|
||||
}
|
||||
|
||||
/*!
|
||||
* Return the reciprocal of this value.
|
||||
*
|
||||
* Result will have a non-negative denominator.
|
||||
*/
|
||||
constexpr Rational
|
||||
reciprocal() const noexcept
|
||||
{
|
||||
return Rational{denominator, numerator}.withNonNegativeDenominator();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Return this value, with the denominator non-negative (0 or positive).
|
||||
*/
|
||||
constexpr Rational
|
||||
withNonNegativeDenominator() const noexcept
|
||||
{
|
||||
if constexpr (std::is_unsigned_v<Scalar>) {
|
||||
// unsigned means always non-negative
|
||||
return *this;
|
||||
} else {
|
||||
return denominator < Scalar{0} ? Rational{-numerator, -denominator} : *this;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Does this rational number represent a value greater than 1, with a positive denominator?
|
||||
*
|
||||
*/
|
||||
constexpr bool
|
||||
isOverUnity() const noexcept
|
||||
{
|
||||
return numerator > denominator && denominator > Scalar{0};
|
||||
}
|
||||
|
||||
/*!
|
||||
* Does this rational number represent 1?
|
||||
*
|
||||
* @note false if denominator is 0, even if numerator is also 0.
|
||||
*/
|
||||
constexpr bool
|
||||
isUnity() const noexcept
|
||||
{
|
||||
return numerator == denominator && denominator != Scalar{0};
|
||||
}
|
||||
|
||||
/*!
|
||||
* Does this rational number represent 0?
|
||||
*
|
||||
* @note false if denominator is 0, even if numerator is also 0.
|
||||
*/
|
||||
constexpr bool
|
||||
isZero() const noexcept
|
||||
{
|
||||
return numerator == Scalar{0} && denominator != Scalar{0};
|
||||
}
|
||||
|
||||
/*!
|
||||
* Does this rational number represent a value between 0 and 1 (exclusive), and has a positive denominator?
|
||||
*
|
||||
* This is the most common useful range.
|
||||
*/
|
||||
constexpr bool
|
||||
isBetweenZeroAndOne() const noexcept
|
||||
{
|
||||
return denominator > Scalar{0} && numerator > Scalar{0} && numerator < denominator;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the complementary fraction.
|
||||
*
|
||||
* Only really makes sense if isBetweenZeroAndOne() is true
|
||||
*
|
||||
* Result will have a non-negative denominator.
|
||||
*/
|
||||
constexpr Rational
|
||||
complement() const noexcept
|
||||
{
|
||||
return Rational{denominator - numerator, denominator}.withNonNegativeDenominator();
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Multiplication operator. Warning: does no simplification!
|
||||
*
|
||||
* Result will have a non-negative denominator.
|
||||
*
|
||||
* @relates Rational
|
||||
*/
|
||||
template <typename Scalar>
|
||||
constexpr Rational<Scalar>
|
||||
operator*(const Rational<Scalar> &lhs, const Rational<Scalar> &rhs)
|
||||
{
|
||||
return Rational<Scalar>{lhs.numerator * rhs.numerator, lhs.denominator * rhs.denominator}
|
||||
.withNonNegativeDenominator();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Multiplication operator with a scalar. Warning: does no simplification!
|
||||
*
|
||||
* Result will have a non-negative denominator.
|
||||
*
|
||||
* @relates Rational
|
||||
*/
|
||||
template <typename Scalar>
|
||||
constexpr Rational<Scalar>
|
||||
operator*(const Rational<Scalar> &lhs, const Scalar &rhs)
|
||||
{
|
||||
return Rational<Scalar>{lhs.numerator * rhs, lhs.denominator}.withNonNegativeDenominator();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Multiplication operator with a scalar. Warning: does no simplification!
|
||||
*
|
||||
* Result will have a non-negative denominator.
|
||||
*
|
||||
* @relates Rational
|
||||
*/
|
||||
template <typename Scalar>
|
||||
constexpr Rational<Scalar>
|
||||
operator*(const Scalar &lhs, const Rational<Scalar> &rhs)
|
||||
{
|
||||
return (rhs * lhs).withNonNegativeDenominator();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Equality comparison operator. Warning: does no simplification, looks for exact equality!
|
||||
*
|
||||
* @relates Rational
|
||||
*/
|
||||
template <typename Scalar>
|
||||
constexpr bool
|
||||
operator==(const Rational<Scalar> &lhs, const Rational<Scalar> &rhs)
|
||||
{
|
||||
return rhs.numerator == lhs.numerator && rhs.denominator == lhs.denominator;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Division operator. Warning: does no simplification!
|
||||
*
|
||||
* Result will have a non-negative denominator.
|
||||
*
|
||||
* @relates Rational
|
||||
*/
|
||||
template <typename Scalar>
|
||||
constexpr Rational<Scalar>
|
||||
operator/(const Rational<Scalar> &lhs, const Rational<Scalar> &rhs)
|
||||
{
|
||||
return (lhs * rhs.reciprocal()).withNonNegativeDenominator();
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Division operator by a scalar. Warning: does no simplification!
|
||||
*
|
||||
* Result will have a non-negative denominator.
|
||||
*
|
||||
* @relates Rational
|
||||
*/
|
||||
template <typename Scalar>
|
||||
constexpr Rational<Scalar>
|
||||
operator/(const Rational<Scalar> &lhs, Scalar rhs)
|
||||
{
|
||||
return (lhs * Rational<Scalar>{1, rhs});
|
||||
}
|
||||
|
||||
|
||||
} // namespace xrt::auxiliary::math
|
|
@ -19,6 +19,7 @@ foreach(
|
|||
tests_json
|
||||
tests_pacing
|
||||
tests_quatexpmap
|
||||
tests_rational
|
||||
)
|
||||
|
||||
add_executable(${testname} ${testname}.cpp)
|
||||
|
@ -33,3 +34,4 @@ target_link_libraries(tests_cxx_wrappers PRIVATE xrt-interfaces)
|
|||
target_link_libraries(tests_history_buf PRIVATE aux_math)
|
||||
target_link_libraries(tests_input_transform PRIVATE st_oxr xrt-interfaces xrt-external-openxr)
|
||||
target_link_libraries(tests_quatexpmap PRIVATE aux_math)
|
||||
target_link_libraries(tests_rational PRIVATE aux_math)
|
||||
|
|
145
tests/tests_rational.cpp
Normal file
145
tests/tests_rational.cpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
// Copyright 2022, Collabora, Ltd.
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
/*!
|
||||
* @file
|
||||
* @brief Integer low pass filter tests.
|
||||
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
|
||||
*/
|
||||
|
||||
#include <math/m_rational.hpp>
|
||||
|
||||
#include "catch/catch.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <queue>
|
||||
|
||||
using xrt::auxiliary::math::Rational;
|
||||
namespace Catch {
|
||||
template <typename T> struct StringMaker<Rational<T>>
|
||||
{
|
||||
static std::string
|
||||
convert(Rational<T> const &value)
|
||||
{
|
||||
return std::to_string(value.numerator) + "/" + std::to_string(value.denominator);
|
||||
}
|
||||
};
|
||||
} // namespace Catch
|
||||
|
||||
TEMPLATE_TEST_CASE("Rational", "", int32_t, uint32_t)
|
||||
{
|
||||
using R = Rational<TestType>;
|
||||
using T = TestType;
|
||||
CHECK(R{1, 1} == R::simplestUnity());
|
||||
CHECK((R::simplestUnity() * T{1}) == R::simplestUnity());
|
||||
CHECK((T{1} * R::simplestUnity()) == R::simplestUnity());
|
||||
|
||||
CHECK(R{5, 8}.reciprocal() == R{8, 5});
|
||||
|
||||
CHECK(R{5, 8}.complement() == R{3, 8});
|
||||
CHECK(R{8, 8}.complement() == R{0, 8});
|
||||
|
||||
if constexpr (std::is_signed<TestType>::value) {
|
||||
}
|
||||
|
||||
CHECK(R{5, 8}.withNonNegativeDenominator() == R{5, 8});
|
||||
|
||||
if constexpr (std::is_signed<TestType>::value) {
|
||||
CHECK(R{5, -8}.withNonNegativeDenominator() == R{-5, 8});
|
||||
CHECK(R{-5, 8}.withNonNegativeDenominator() == R{-5, 8});
|
||||
|
||||
CHECK(R{-5, 8}.reciprocal() == R{-8, 5});
|
||||
CHECK(R{5, -8}.complement() == R{8 + 5, 8});
|
||||
}
|
||||
|
||||
{
|
||||
R val{5, 8};
|
||||
CAPTURE(val);
|
||||
CHECK((R::simplestUnity() * val) == val);
|
||||
CHECK((val * R::simplestUnity()) == val);
|
||||
CHECK((val * T{1}) == val);
|
||||
CHECK((T{1} * val) == val);
|
||||
|
||||
CHECK((val * val.reciprocal()).numerator == (val * val.reciprocal()).denominator);
|
||||
CHECK((val * val.reciprocal()).isUnity());
|
||||
|
||||
CHECK((val / val).numerator == (val / val).denominator);
|
||||
CHECK((val / val).isUnity());
|
||||
|
||||
CHECK((val / T{1}) == val);
|
||||
}
|
||||
|
||||
if constexpr (std::is_signed<TestType>::value) {
|
||||
R val{5, -8};
|
||||
R valNonNegativeDenominator = val.withNonNegativeDenominator();
|
||||
|
||||
CAPTURE(val);
|
||||
CHECK((R::simplestUnity() * val) == valNonNegativeDenominator);
|
||||
CHECK((val * R::simplestUnity()) == valNonNegativeDenominator);
|
||||
CHECK((val * T{1}) == valNonNegativeDenominator);
|
||||
CHECK((T{1} * val) == valNonNegativeDenominator);
|
||||
|
||||
CHECK((val * val.reciprocal()).numerator == (val * val.reciprocal()).denominator);
|
||||
CHECK((val * val.reciprocal()).isUnity());
|
||||
|
||||
CHECK((val / val).numerator == (val / val).denominator);
|
||||
CHECK((val / val).isUnity());
|
||||
|
||||
CHECK((val / T{1}) == valNonNegativeDenominator);
|
||||
}
|
||||
|
||||
// Check all our predicates
|
||||
{
|
||||
// This is divide by zero error, all should be false
|
||||
R val{0, 0};
|
||||
CAPTURE(val);
|
||||
CHECK_FALSE(val.isZero());
|
||||
CHECK_FALSE(val.isBetweenZeroAndOne());
|
||||
CHECK_FALSE(val.isUnity());
|
||||
CHECK_FALSE(val.isOverUnity());
|
||||
}
|
||||
{
|
||||
R val{0, 8};
|
||||
CAPTURE(val);
|
||||
CHECK(val.isZero());
|
||||
CHECK_FALSE(val.isBetweenZeroAndOne());
|
||||
CHECK_FALSE(val.isUnity());
|
||||
CHECK_FALSE(val.isOverUnity());
|
||||
}
|
||||
|
||||
{
|
||||
R val{5, 8};
|
||||
CAPTURE(val);
|
||||
CHECK_FALSE(val.isZero());
|
||||
CHECK(val.isBetweenZeroAndOne());
|
||||
CHECK_FALSE(val.isUnity());
|
||||
CHECK_FALSE(val.isOverUnity());
|
||||
}
|
||||
|
||||
{
|
||||
R val{8, 8};
|
||||
CAPTURE(val);
|
||||
CHECK_FALSE(val.isZero());
|
||||
CHECK_FALSE(val.isBetweenZeroAndOne());
|
||||
CHECK(val.isUnity());
|
||||
CHECK_FALSE(val.isOverUnity());
|
||||
}
|
||||
{
|
||||
R val = R::simplestUnity();
|
||||
CAPTURE(val);
|
||||
CHECK_FALSE(val.isZero());
|
||||
CHECK_FALSE(val.isBetweenZeroAndOne());
|
||||
CHECK(val.isUnity());
|
||||
CHECK_FALSE(val.isOverUnity());
|
||||
}
|
||||
{
|
||||
R val{8, 5};
|
||||
CAPTURE(val);
|
||||
CHECK_FALSE(val.isZero());
|
||||
CHECK_FALSE(val.isBetweenZeroAndOne());
|
||||
CHECK_FALSE(val.isUnity());
|
||||
CHECK(val.isOverUnity());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue