xo-ratio: initial commit

This commit is contained in:
Roland Conybeare 2024-04-17 21:23:20 -04:00
commit 3cc6ba9a66
12 changed files with 1013 additions and 0 deletions

57
CMakeLists.txt Normal file
View file

@ -0,0 +1,57 @@
# xo-ratio/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(xo_ratio VERSION 1.0)
enable_language(CXX)
# common XO cmake macros (see proj/xo-cmake)
include(cmake/xo-bootstrap-macros.cmake)
# ----------------------------------------------------------------
# unit test setup
enable_testing()
# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON)
add_code_coverage()
# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc.
# we're not interested in code coverage for these sources.
# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves;
# rather, want coverage on the code that the unit tests exercise.
#
# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target
#
add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*)
# ----------------------------------------------------------------
# c++ settings
# one-time project-specific c++ flags. usually empty
set(PROJECT_CXX_FLAGS "")
#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2")
add_definitions(${PROJECT_CXX_FLAGS})
xo_toplevel_compile_options()
# ----------------------------------------------------------------
add_subdirectory(example)
#add_subdirectory(utest)
#add_subdirectory(docs)
# ----------------------------------------------------------------
# provide find_package() support for projects using this library
set(SELF_LIB xo_ratio)
xo_add_headeronly_library(${SELF_LIB})
xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets)
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
# ----------------------------------------------------------------
# dependencies
#xo_headeronly_dependency(${SELF_LIB} randomgen)
# etc..
# end CMakeLists.txt

29
LICENSE Normal file
View file

@ -0,0 +1,29 @@
Copyright (c) 2024 Roland Conybeare <git3ub@nym.hush.com>, All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of
external contributions to this project including patches, pull requests, etc.

26
README.md Normal file
View file

@ -0,0 +1,26 @@
# ratio library
Header-only, constexpr library providing exact representation for rational numbers.
Relative to `std::ratio`:
1. Uses `constexpr` instead of creating new types.
This means it can be used seamlessly at runtime.
2. Supports a few more arithmetic operations,
for example exponentiation to integer powers.
3. Provides concept support (with c++20)
4. Requires modern (c++17) support to achieve this
## Getting Started
### install dependencies
- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros
### build + install
```
$ cd xo-ratio
$ PREFIX=/usr/local # for example
$ BUILDDIR=.build # for example
$ make ${BUILDDIR}
$ cmake -DCMAKE_PREFIX_PATH=${PREFIX} -B ${BUILDDIR}
```

View file

@ -0,0 +1,15 @@
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix"))
# default to typical install location for xo-project-macros
set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake)
endif()
if (NOT XO_SUBMODULE_BUILD)
message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}")
message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
endif()
# needs to have been installed somewhere on CMAKE_MODULE_PATH,
# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX)
#
include(xo_macros/xo-project-macros)

View file

@ -0,0 +1,17 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
# note: changes to find_dependency() calls here
# must coordinate with xo_dependency() calls
# in xo-reactor/src/reactor/CMakeLists.txt
#
#find_dependency(reflect)
#find_dependency(subsys)
#find_dependency(Eigen3)
#find_dependency(webutil)
#find_dependency(printjson)
#find_dependency(callback)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
check_required_components("@PROJECT_NAME@")

3
example/CMakeLists.txt Normal file
View file

@ -0,0 +1,3 @@
# xo-ratio/example/CMakeLists.txt
add_subdirectory(ex1)

View file

@ -0,0 +1,15 @@
# xo-ratio/example/ex1/CMakeLists.txt
set(SELF_EXE xo_ratio_ex1)
set(SELF_SRCS ex1.cpp)
add_executable(${SELF_EXE} ${SELF_SRCS})
xo_include_options2(${SELF_EXE})
# ----------------------------------------------------------------
# dependencies..
xo_self_dependency(${SELF_EXE} xo_ratio)
xo_dependency(${SELF_EXE} reflect)
# end CMakeLists.txt

194
example/ex1/ex1.cpp Normal file
View file

@ -0,0 +1,194 @@
/** @file ex1.cpp **/
#include "xo/ratio/ratio_iostream.hpp"
#include <iostream>
int
main() {
using xo::ratio::make_ratio;
using xo::ratio::ratio;
using xo::ratio::ratio_concept;
using namespace std;
constexpr auto r1 = make_ratio(2, 3);
cerr << "r1=make_ratio(2,3): " << r1 << endl; // output <ratio 2/3>
static_assert(r1.num() == 2);
static_assert(r1.den() == 3);
static_assert(r1.compare(r1, r1) == 0);
static_assert(xo::ratio::detail::op_aux_type<decltype(r1), decltype(r1)>::compare(r1,r1) == 0);
static_assert(r1 == r1);
static_assert(!(r1 != r1));
static_assert(r1 != 0);
static_assert(r1 != 1);
static_assert(r1 != 2);
static_assert(r1 != 3);
static_assert(r1 >= r1);
static_assert(r1 <= r1);
static_assert(r1 > 0);
static_assert(r1 >= 0);
static_assert(r1 < 1);
static_assert(r1 <= 1);
constexpr auto r2 = make_ratio(2, 4);
cerr << "r2=make_ratio(2,4): " << r2 << endl; // output <ratio 1/2>
static_assert(r2.num() == 1);
static_assert(r2.den() == 2);
static_assert(r2 == r2);
static_assert(r2 != r1);
static_assert(!(r2 > r1));
static_assert(!(r2 >= r1));
static_assert(r2 <= r1);
static_assert(r2 < r1);
static_assert(r1 > r2);
static_assert(r1 >= r2);
static_assert(!(r1 < r2));
static_assert(!(r1 <= r2));
constexpr auto r3 = make_ratio(2, 3) - make_ratio(1, 2);
cerr << "r3=r1-r2: " << r1 - r2 << endl; // output <ratio 1/6>
static_assert(r3.num() == 1);
static_assert(r3.den() == 6);
static_assert(r3 == r3);
static_assert(r3 != 0);
static_assert(r3 != 1);
static_assert(r3 < r2);
static_assert(r3 <= r2);
static_assert(r3 < r1);
static_assert(r3 <= r1);
constexpr auto r4 = r1 + r2;
cerr << "r4=r1+r2: " << r1 + r2 << endl; // output <ratio 7/6>
static_assert(r4.num() == 7);
static_assert(r4.den() == 6);
static_assert(r4 > 1);
static_assert(r4 < 2);
constexpr auto r5 = r1 + 3;
cerr << "r5=r1+3: " << r5 << endl; // output <ratio 11/3>
static_assert(r5.num() == 11);
static_assert(r5.den() == 3);
constexpr auto r6 = 3 + r1;
cerr << "r5=3+r1: " << r6 << endl; // output <ratio 11/3>
static_assert(r6.num() == 11);
static_assert(r6.den() == 3);
static_assert(r5 == r6);
static_assert(r5 > 3);
static_assert(r5 < 4);
static_assert(r5 > r1);
constexpr auto r7 = r6 - 3;
cerr << "r7=r6-3: " << r7 << endl; // output <ratio 2/3>
static_assert(r7 == r1);
static_assert(r7 >= r1);
static_assert(r7 <= r1);
constexpr auto r8 = 3 - r6;
cerr << "r8=3-r6: " << r8 << endl; // output <ratio -2/3>
static_assert(r8 == r8);
static_assert(r8 > -1);
static_assert(r8 < 0);
static_assert(-1 < r8);
static_assert(-1 <= r8);
static_assert(0 >= r8);
constexpr auto r9 = r8 * r8;
cerr << "r9=r8*r8: " << r9 << endl; // output <ratio 4/9>
static_assert(r9 == make_ratio(4, 9));
constexpr auto r10 = r9 * 9;
cerr << "r10=r9*9: " << r10 << endl; // output <ratio 4/1>
static_assert(r10 == 4);
static_assert(r10.to<int>() == 4);
constexpr auto r11 = r9 * 3;
cerr << "r11=r9*3: " << r11 << endl; // output <ratio 4/3>
static_assert(r11 == make_ratio(4, 3));
static_assert(r11.to<int>() == 1);
constexpr auto r12 = 9 * r9;
cerr << "r12=9*r9: " << r12 << endl;
static_assert(r12 == r10);
static_assert(r12 == make_ratio(4, 1));
static_assert(r12.to<int>() == 4);
constexpr auto r13 = 3 * r9;
cerr << "r13=3*r9: " << r13 << endl; // output <ratio 4/3>
static_assert(r13 == make_ratio(4, 3));
static_assert(r13 == make_ratio(-4, -3));
static_assert(r13 == r11);
static_assert(r13.to<int>() == 1);
constexpr auto r14 = r9 / r9;
cerr << "r14=r9/r9: " << r14 << endl; // output <ratio 4/3>
static_assert(r14 == 1);
static_assert(r14.to<int>() == 1);
constexpr auto r15 = r9 / r8;
cerr << "r15=r9/r8: " << r15 << endl; // (4/9) / (-2/3) = (4/9) * (3/-2) = 12/-18 = -2/3
static_assert(r15 == make_ratio(-2, 3));
static_assert(r15 == make_ratio(2, -3));
constexpr auto r16 = r9 / 2;
cerr << "r16=r9/2: " << r16 << endl;
static_assert(r16 == ratio(2, 9));
static_assert(!r16.is_integer());
constexpr auto r17 = 2 / r9;
cerr << "r17=2/r9: " << r17 << endl;
static_assert(r17 == ratio(9, 2));
static_assert(!r17.is_integer());
constexpr auto r18 = r12 / r8;
cerr << "r18=r12/r8: " << r12/r8 << endl;
static_assert(r18.is_integer());
constexpr auto r19 = r18.power(2);
cerr << "r19=r18^2: " << r19 << endl;
static_assert(r19.is_integer());
static_assert(r19 == ratio(36, 1));
constexpr auto r20 = r17.power(-3);
cerr << "r20=r17^-3: " << r20 << endl;
static_assert(!r20.is_integer());
static_assert(r20 == ratio(8, 729));
/* verify constexpr working */
static_assert(ratio<int>(2,3).num() == 2);
static_assert(ratio<int>(2,3).den() == 3);
static_assert(make_ratio(-1,2).num() == -1);
static_assert(make_ratio(-1,2).den() == 2);
static_assert(make_ratio(-2,4).num() == -1);
static_assert(make_ratio(-2,4).den() == 2);
static_assert(make_ratio(1,-2).num() == -1);
static_assert(make_ratio(1,-2).den() == 2);
static_assert(make_ratio(-4,-6).num() == 2);
static_assert(make_ratio(-4,-6).den() == 3);
}

View file

@ -0,0 +1,39 @@
/** @file numeric_concept.hpp **/
#pragma once
#include <concepts>
namespace xo {
namespace ratio {
/** @concept numeric_concept
* @brief Concept for values that participate in arithmetic operations (+,-,*,/) and comparisons
*
* Intended to include at least:
* - built-in integral and floating-point types
* - big_int<N> from ctbignum
* - boost::rational<U>
* - std::complex<U>
* - xo::unit::quantity<U,R>
*
* Accepting complex numbers --> we don't require T to be totally ordered,
* and don't require (<,<=,>=,>) operators.
*
* Intend numeric_concept to apply to types T suitable for
* xo::ratio::ratio<T>
**/
template <typename T, typename U = T>
concept numeric_concept = requires(T x, U y)
{
{ -x };
{ x - y };
{ x + y };
{ x * y };
{ x / y };
{ x == y };
{ x != y };
};
} /*namespace ratio*/
} /*namespace xo*/
/* end numeric_concept.hpp */

547
include/xo/ratio/ratio.hpp Normal file
View file

@ -0,0 +1,547 @@
/** @file ratio.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "ratio_concept.hpp"
#include <numeric>
#include <compare>
//#include <type_traits>
namespace xo {
namespace ratio {
namespace detail {
/** @brief converts ratio to lowest terms when feasible
*
* Falls back to identity function for non-totally-ordered Ratio::component_type
*/
template <typename Ratio, bool EnabledFlag = std::totally_ordered<typename Ratio::component_type>>
struct reducer_type;
/** @brief promote value to ratio type **/
template <typename Ratio, typename FromType, bool FromRatioFlag = ratio_concept<FromType>>
struct promoter_type;
}
/** @brief represent a ratio of two Int values. **/
template <typename Int>
requires std::totally_ordered<Int>
struct ratio
{
public:
using component_type = Int;
public:
constexpr ratio(Int n, Int d) : num_{n}, den_{d} {}
/** @brief ratio in lowest commono terms
*
**/
static constexpr ratio reduce(Int n, Int d) {
return ratio(n, d).reduce();
}
/** @brief add two ratios **/
static constexpr ratio add(const ratio & x,
const ratio & y) {
/* (a/b) + (c/d)
* = a.d / (b.d) + b.c / (b.d)
* = (a.d + b.c) / (b.d)
*/
auto a = x.num();
auto b = x.den();
auto c = y.num();
auto d = y.den();
auto num = a*d + b*c;
auto den = b*d;
return ratio(num, den).maybe_reduce();
}
/** @brief subtract two ratios **/
static constexpr ratio subtract(const ratio & x,
const ratio & y) {
return add(x, y.negate());
}
/** @brief multiply two ratios **/
static constexpr ratio multiply(const ratio & x,
const ratio & y) {
/* (a/b) * (c/d) = a.c / b.d */
/* if x,y normalized,
* opportunity to cancel common factor between (a, d) or (c, b)
*
* want to do this before multiplying to avoid overflow involving intermediate terms
*/
auto a1 = x.num();
auto b1 = x.den();
auto c1 = y.num();
auto d1 = y.den();
auto ad_gcf = std::gcd(a1, d1);
auto bc_gcf = std::gcd(b1, c1);
auto a = a1 / ad_gcf;
auto b = b1 / bc_gcf;
auto c = c1 / bc_gcf;
auto d = d1 / ad_gcf;
auto num = a*c;
auto den = b*d;
return ratio(num, den).maybe_reduce();
}
/** @brief divide two ratios **/
static constexpr ratio divide(const ratio & x,
const ratio & y) {
return multiply(x, y.reciprocal());
}
/** @brief compute integer power of a ratio **/
constexpr ratio power(int p) const {
constexpr ratio retval = ratio(1, 1);
if (p == 0)
return ratio(1, 1);
if (p < 0)
return this->reciprocal().power(-p);
/* inv: x^p = aj.xj^pj */
ratio aj = ratio(1, 1);
ratio xj = *this;
int pj = p;
while (pj > 0) {
if (pj % 2 == 0) {
/* a.x^(2q) = a.(x^2)^q */
xj = xj * xj;
pj = pj / 2;
} else {
/* a.x^(2q+1) = (a.x).x^(2q) */
aj = aj * xj;
pj = (pj - 1);
}
}
/* pj = 0, so: x^p = aj.xj^pj = aj.xj^0 = aj */
return aj;
}
/** @brief 3-way compare two ratios **/
static constexpr auto compare(ratio x, ratio y) {
/* ensure minus signs in numerators only */
if (x.den() < 0)
return compare_aux(ratio(-x.num(), -x.den()), y);
if (y.den() < 0)
return compare_aux(x, ratio(-y.num(), -y.den()));
return compare_aux(x, y);
}
constexpr Int num() const { return num_; }
constexpr Int den() const { return den_; }
constexpr bool is_integer() const { return den_ == 1 || den_ == -1; }
constexpr ratio negate() const { return ratio(-num_, den_); }
constexpr ratio reciprocal() const { return ratio(den_, num_); }
/** @brief requires component_type is totally ordered **/
constexpr Int floor() const { return (num_ / den_); }
/** @brief requires component_type is totally ordered **/
constexpr Int ceil() const { return floor() + 1; }
/** @brief reduce to lowest terms
*
* @pre @c Int type must be totally ordered
**/
constexpr ratio reduce() const requires std::totally_ordered<Int> {
if (den_ < 0)
return ratio(-num_, -den_).reduce();
auto factor = std::gcd(num_, den_);
return ratio(num_ / factor,
den_ / factor);
}
/** @brief reduce to lowest terms, if Int representation admits
*
* Otherwise fallback to identity function
**/
constexpr ratio maybe_reduce() const {
return detail::reducer_type<ratio>::attempt_reduce(*this);
}
/** @brief return fractional part of this ratio
*
* @pre @c Int type must be totally ordered
**/
constexpr ratio frac() const requires std::totally_ordered<Int> {
return ratio::subtract(*this, this->floor());
}
/** @brief convert to non-ratio representation
*
* For example: to int or double
**/
template <typename Repr>
constexpr Repr to() const { return num_ / static_cast<Repr>(den_); }
/** @brief convert to representation using different integer types **/
template <typename Ratio2>
constexpr operator Ratio2 () const requires ratio_concept<Ratio2> {
return Ratio2(num_, den_);
}
private:
/** @brief 3-way compare auxiliary function.
*
* @pre @p x, @p y have non-negative denominator
**/
static constexpr auto compare_aux(ratio x, ratio y) {
/* control here: b>=0, d>=0 */
/* (a/b) <=> (c/d)
* (a.d/b) <=> c no sign change, since d >= 0
* (a.d) <=> (b.c) no sign change, since b >= 0
*/
auto a = x.num();
auto b = x.den();
auto c = y.num();
auto d = y.den();
auto lhs = a*d;
auto rhs = b*c;
return lhs <=> rhs;
}
private:
/** @brief numerator **/
Int num_;
/** @brief denominator **/
Int den_;
};
namespace detail {
template <typename Ratio, bool EnabledFlag>
struct reducer_type {};
template <typename Ratio>
struct reducer_type<Ratio, true /*EnabledFlag*/> {
static constexpr Ratio attempt_reduce(Ratio x) { return x.reduce(); }
};
template <typename Ratio>
struct reducer_type<Ratio, false /*!EnabledFlag*/> {
static constexpr Ratio attempt_reduce(Ratio x) { return x; }
};
}
namespace detail {
template <typename Ratio, typename FromType, bool FromRatioFlag>
struct promoter_type;
template <typename Ratio, typename FromType>
struct promoter_type<Ratio, FromType, true /*FromRatioFlag*/> {
/* to 'promote' a ratio, rely on its conversion operator */
static constexpr Ratio promote(FromType x) { return x; }
};
template <typename Ratio, typename FromType>
struct promoter_type<Ratio, FromType, false /*!FromRatioFlag*/> {
/* to 'promote' a non-ratio, use denominator=1 */
static constexpr Ratio promote(FromType x) { return Ratio(x, 1); }
};
}
template <typename Int1, typename Int2>
constexpr auto
make_ratio (Int1 n, Int2 d = 1) -> ratio<std::common_type_t<Int1, Int2>>
{
return ratio<std::common_type_t<Int1, Int2>>(n, d).maybe_reduce();
}
namespace detail {
/** @brief auxiliary function for binary ratio operations
*
* Support binary ratio operations on combinations:
* - (ratio<T>, ratio<U>)
* - (ratio<T>, U) // where U is not a ratio
* - (T, ratio(U)) // where T is not a ratio
*
* Goals:
*
* 1. Support expressions like
*
* @code
* auto x = 1 + make_ratio(2,3);
* @endcode
*
* 2. promote to wider types as needed
*
* @code
* auto x = make_ratio(2,3) + make_ratio(1ul,2ul);
* static_assert(std::same_as<x::component_type, unsigned long>);
* @endcode
*
* 3. avoid interfering with other templates that may overload operator+
*
* @pre at least one of (Left,Right) must be known to be a ratio
**/
template <typename Left,
typename Right,
bool LeftIsRatio = ratio_concept<Left>,
bool RightIsRatio = ratio_concept<Right>>
struct op_aux_type;
/** @brief specialization for two ratio types **/
template <typename LeftRatio,
typename RightRatio>
requires (ratio_concept<LeftRatio> && ratio_concept<RightRatio>)
struct op_aux_type<LeftRatio, RightRatio, true /*LeftIsRatio*/, true /*RightIsRatio*/> {
using component_type = std::common_type_t<typename LeftRatio::component_type,
typename RightRatio::component_type>;
using ratio_type = ratio<component_type>;
static constexpr ratio_type add (const LeftRatio & x,
const RightRatio & y)
{
return ratio_type::add(x, y);
}
static constexpr ratio_type subtract (const LeftRatio & x,
const RightRatio & y)
{
return ratio_type::subtract(x, y);
}
static constexpr ratio_type multiply (const LeftRatio & x,
const RightRatio & y)
{
return ratio_type::multiply(x, y);
}
static constexpr ratio_type divide (const LeftRatio & x,
const RightRatio & y)
{
return ratio_type::divide(x, y);
}
static constexpr auto compare (const LeftRatio & x,
const RightRatio & y)
{
return ratio_type::compare(x, y);
}
};
/** @brief specialization for left-hand ratio and right-hand integer value **/
template <typename LeftRatio,
typename Right>
requires (ratio_concept<LeftRatio> && !ratio_concept<Right>)
struct op_aux_type<LeftRatio, Right, true /*LeftIsRatio*/, false /*RightIsRatio*/> {
using component_type = std::common_type_t<typename LeftRatio::component_type, Right>;
using ratio_type = ratio<component_type>;
static constexpr ratio_type add (const LeftRatio & x,
const Right & y)
{
/* reminder: adding an integer can't introduce reduced terms */
return ratio_type(x.num() + x.den() * y, x.den());
}
static constexpr ratio_type subtract (const LeftRatio & x,
const Right & y)
{
/* reminder: subtracting an integer can't introduce reduced terms */
return ratio_type(x.num() - x.den() * y, x.den());
}
static constexpr ratio_type multiply (const LeftRatio & x,
const Right & yp)
{
auto gcf = std::gcd(x.den(), yp);
auto a = x.num();
auto b = x.den() / gcf;
auto y = yp / gcf;
return ratio_type(a*y, b);
}
static constexpr ratio_type divide (const LeftRatio & x,
const Right & yp)
{
auto gcf = std::gcd(x.num(), yp);
auto a = x.num() / gcf;
auto b = x.den();
auto y = yp / gcf;
return ratio_type(a*y, b);
}
static constexpr auto compare (const LeftRatio & x,
const Right & y)
{
/* note: in c++26 std::signof is constexpr, usable here */
if (x.den() >= 0)
return compare_aux(x, y);
else
return compare_aux(LeftRatio(-x.num(), -x.den()), y);
}
private:
static constexpr auto compare_aux (const LeftRatio & x, const Right & y) {
return (x.num() <=> x.den() * y);
}
};
/** @brief specialization for left-hand integer value and right-hand ratio **/
template <typename Left,
typename RightRatio>
requires (!ratio_concept<Left> && ratio_concept<RightRatio>)
struct op_aux_type<Left, RightRatio, false /*LeftIsRatio*/, true /*RightIsRatio*/> {
using component_type = std::common_type_t<Left, typename RightRatio::component_type>;
using ratio_type = ratio<component_type>;
static constexpr ratio_type add(const Left & x,
const RightRatio & y)
{
/* reminder: adding an integer can't introduce reduced terms */
return ratio_type(x * y.den() + y.num(), y.den());
}
static constexpr ratio_type subtract(const Left & x,
const RightRatio & y)
{
/* reminder: subtracting an integer can't introduce reduced terms */
return ratio_type(x * y.den() - y.num(), y.den());
}
static constexpr ratio_type multiply (const Left & xp,
const RightRatio & y)
{
auto gcf = std::gcd(xp, y.den());
auto x = xp / gcf;
auto c = y.num();
auto d = y.den() / gcf;
return ratio_type(x*c, d);
}
static constexpr ratio_type divide (const Left & x,
const RightRatio & y)
{
return multiply(x, y.reciprocal());
}
static constexpr auto compare(const Left & x,
const RightRatio & y)
{
if (y.den() >= 0)
return compare_aux(x, y);
else
return compare_aux(x, RightRatio(-y.num(), -y.den()));
}
private:
static constexpr auto compare_aux (const Left & x,
const RightRatio & y)
{
return (x * y.den() <=> y.num());
};
};
} /*namespace detail*/
/** @brief add two ratios.
*
* One argument may be a non-ratio type if it can be promoted to a ratio
**/
template <typename Ratio1, typename Ratio2>
inline constexpr auto
operator+ (const Ratio1 & x, const Ratio2 & y)
requires (ratio_concept<Ratio1> || ratio_concept<Ratio2>)
{
return detail::op_aux_type<Ratio1, Ratio2>::add(x, y);
}
/** @brief subtract two ratios.
*
* One argument may be a non-ratio type if it can be promoted to a ratio
**/
template <typename Ratio1, typename Ratio2>
inline constexpr auto
operator- (const Ratio1 & x, const Ratio2 & y)
requires (ratio_concept<Ratio1> || ratio_concept<Ratio2>)
{
return detail::op_aux_type<Ratio1, Ratio2>::subtract(x, y);
}
/** @brief multiply two ratios
*
* One argument may be a non-ratio type if it can be promoted to a ratio
**/
template <typename Ratio1, typename Ratio2>
inline constexpr auto
operator* (const Ratio1 & x, const Ratio2 & y)
requires (ratio_concept<Ratio1> || ratio_concept<Ratio2>)
{
return detail::op_aux_type<Ratio1, Ratio2>::multiply(x, y);
}
/** @brief divide two ratios
*
* One argument may be a non-ratio type if it can be promoted to a ratio
**/
template <typename Ratio1, typename Ratio2>
inline constexpr auto
operator/ (const Ratio1 & x, const Ratio2 & y)
requires (ratio_concept<Ratio1> || ratio_concept<Ratio2>)
{
return detail::op_aux_type<Ratio1, Ratio2>::divide(x, y);
}
/** @brief compare two ratios for equality
*
* One argument may be a non-ratio type if it can be promoted to a ratio
**/
template <typename Ratio1, typename Ratio2>
inline constexpr bool
operator== (const Ratio1 & x, const Ratio2 & y)
requires (ratio_concept<Ratio1> || ratio_concept<Ratio2>)
{
return (detail::op_aux_type<Ratio1, Ratio2>::compare(x, y) == 0);
}
/** @brief compare two ratios
*
* One argument may be a non-ratio type if it can be promoted to a ratio
**/
template <typename Ratio1, typename Ratio2>
inline constexpr auto
operator<=> (const Ratio1 & x, const Ratio2 & y)
requires (ratio_concept<Ratio1> || ratio_concept<Ratio2>)
{
return detail::op_aux_type<Ratio1, Ratio2>::compare(x, y);
}
} /*namespace ratio*/
} /*namespace xo*/
/** end ratio.hpp **/

View file

@ -0,0 +1,29 @@
/** @file ratio_concept.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "numeric_concept.hpp"
namespace xo {
namespace ratio {
/* also expect:
* Ratio::num_type / Ratio::den_type rounds towards -inf
*/
template <typename Ratio>
concept ratio_concept = requires(Ratio ratio)
{
typename Ratio::component_type;
typename Ratio::component_type;
{ ratio.num() } -> std::same_as<typename Ratio::component_type>;
{ ratio.den() } -> std::same_as<typename Ratio::component_type>;
} && numeric_concept<typename Ratio::component_type>;
} /*namespace ratio*/
} /*namespace xo*/
/** end ratio_concept.hpp **/

View file

@ -0,0 +1,42 @@
/** @file ratio_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "ratio.hpp"
#include <ostream>
namespace xo {
namespace ratio {
/** @brief print ratio x on stream os.
*
* Example:
* @code
* print_ratio(std::cerr, make_ratio(1,2); // outputs "<ratio 1/2>"
* @endcode
**/
template <typename Ratio>
void
print_ratio (std::ostream & os, const Ratio & x) {
os << "<ratio " << x.num() << "/" << x.den() << ">";
}
/** @brief print ratio x on stream os.
*
* Example:
* @code
* std::cout << make_ratio(2,3); // outputs "<ratio 2/3>"
* @endcode
**/
template <typename Ratio>
inline std::ostream &
operator<< (std::ostream & os, const Ratio & x) {
print_ratio(os, x);
return os;
}
}
}
/** end ratio_iostream.hpp **/