xo-ratio: initial commit
This commit is contained in:
commit
3cc6ba9a66
12 changed files with 1013 additions and 0 deletions
57
CMakeLists.txt
Normal file
57
CMakeLists.txt
Normal 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
29
LICENSE
Normal 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
26
README.md
Normal 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}
|
||||
```
|
||||
15
cmake/xo-bootstrap-macros.cmake
Normal file
15
cmake/xo-bootstrap-macros.cmake
Normal 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)
|
||||
17
cmake/xo_ratioConfig.cmake.in
Normal file
17
cmake/xo_ratioConfig.cmake.in
Normal 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
3
example/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# xo-ratio/example/CMakeLists.txt
|
||||
|
||||
add_subdirectory(ex1)
|
||||
15
example/ex1/CMakeLists.txt
Normal file
15
example/ex1/CMakeLists.txt
Normal 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
194
example/ex1/ex1.cpp
Normal 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);
|
||||
}
|
||||
39
include/xo/ratio/numeric_concept.hpp
Normal file
39
include/xo/ratio/numeric_concept.hpp
Normal 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
547
include/xo/ratio/ratio.hpp
Normal 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 **/
|
||||
29
include/xo/ratio/ratio_concept.hpp
Normal file
29
include/xo/ratio/ratio_concept.hpp
Normal 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 **/
|
||||
42
include/xo/ratio/ratio_iostream.hpp
Normal file
42
include/xo/ratio/ratio_iostream.hpp
Normal 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 **/
|
||||
Loading…
Add table
Add a link
Reference in a new issue