mirror of
https://gitlab.freedesktop.org/monado/monado.git
synced 2025-01-16 03:45:24 +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.c
|
||||||
math/m_predict.h
|
math/m_predict.h
|
||||||
math/m_quatexpmap.cpp
|
math/m_quatexpmap.cpp
|
||||||
math/m_relation_history.h
|
math/m_rational.hpp
|
||||||
math/m_relation_history.cpp
|
math/m_relation_history.cpp
|
||||||
|
math/m_relation_history.h
|
||||||
math/m_space.cpp
|
math/m_space.cpp
|
||||||
math/m_space.h
|
math/m_space.h
|
||||||
math/m_vec2.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_json
|
||||||
tests_pacing
|
tests_pacing
|
||||||
tests_quatexpmap
|
tests_quatexpmap
|
||||||
|
tests_rational
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(${testname} ${testname}.cpp)
|
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_history_buf PRIVATE aux_math)
|
||||||
target_link_libraries(tests_input_transform PRIVATE st_oxr xrt-interfaces xrt-external-openxr)
|
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_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