Add 'xo-ratio/' from commit 'd18e9afc1d'

git-subtree-dir: xo-ratio
git-subtree-mainline: b4c2b98a88
git-subtree-split: d18e9afc1d
This commit is contained in:
Roland Conybeare 2025-05-10 21:26:39 -05:00
commit 92df7ca236
31 changed files with 5283 additions and 0 deletions

View file

@ -0,0 +1,221 @@
name: build xo-ratio + dependencies
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
# You can convert this to a matrix build if you need cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
steps:
- name: checkout source
uses: actions/checkout@v3
- name: Install dependencies
run: |
# install catch2, doxygen. see
# [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]]
echo "::group::install catch2"
sudo apt-get install -y catch2
echo "::endgroup"
echo "::group::install doxygen"
sudo apt-get install -y doxygen
echo "::endgroup"
echo "::group::install sphinx"
sudo apt-get install -y python3-sphinx
echo "::endgroup"
echo "::group::install sphinx readthedocs theme"
sudo apt-get install -y python3-sphinx-rtd-theme
echo "::endgroup"
#echo "::group::install pybind11"
#sudo apt-get install -y pybind11-dev
#echo "::endgroup"
# ----------------------------------------------------------------
- name: clone xo-cmake
uses: actions/checkout@v3
with:
repository: Rconybea/xo-cmake
path: repo/xo-cmake
- name: build xo-cmake
run: |
XONAME=xo-cmake
XOSRC=repo/${XONAME}
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}}
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: clone xo-indentlog
uses: actions/checkout@v3
with:
repository: Rconybea/indentlog
path: repo/xo-indentlog
- name: build xo-indentlog
run: |
XONAME=xo-indentlog
XOSRC=repo/${XONAME}
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::repo dir tree"
tree -L 2 repo
echo "::endgroup"
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree -L 3 ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: clone xo-randomgen
uses: actions/checkout@v3
with:
repository: Rconybea/randomgen
path: repo/xo-randomgen
- name: build xo-randomgen
run: |
XONAME=xo-randomgen
XOSRC=repo/${XONAME}
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::repo dir tree"
tree -L 2 repo
echo "::endgroup"
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree -L 3 ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: clone xo-flatstring
uses: actions/checkout@v3
with:
repository: Rconybea/xo-flatstring
path: repo/xo-flatstring
- name: build xo-flatstring
run: |
XONAME=xo-flatstring
XOSRC=repo/${XONAME}
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::repo dir tree"
tree -L 2 repo
echo "::endgroup"
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree -L 3 ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: build self (xo-ratio)
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: |
XONAME=xo-ratio
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::repo dir tree"
tree -L 2 repo
echo "::endgroup"
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}}
echo "::endgroup"
echo "::group::run unit tests ${XONAME}"
cmake --build ${BUILDDIR} -- test
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree -L 3 ${PREFIX}
echo "::endgroup"
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
(cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}})

View file

@ -0,0 +1,212 @@
name: XO ratio builder
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
# You can convert this to a matrix build if you need cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
container:
# custom docker image. see github.com:rconybea/docker-xo-builder for definition
image: ghcr.io/rconybea/docker-xo-builder:v1
steps:
- name: xo-cmake
run: |
# treat github.com as known host to prevent shtoopid SSL errors
mkdir -p ~/.ssh
ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
cat ~/.ssh/known_hosts
git config --global http.sslVerify false
XO_NAME=xo-cmake
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/xo-cmake.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: xo-indentlog
run: |
XO_NAME=xo-indentlog
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/indentlog.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: xo-flatstring
run: |
XO_NAME=xo-flatstring
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/${XO_NAME}.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: xo-randomgen
run: |
XO_NAME=xo-randomgen
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/randomgen.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: xo-ratio
run: |
XO_NAME=xo-ratio
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/${XO_NAME}.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::test ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -- test
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"

8
xo-ratio/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# emacs project control file
.projectile
# clangd working space (see emacs+lsp)
.cache
# typical cmake build directory (source-tree-nephew)
.build*
# symlink to builddir/compile_commands.json; should be set manually in dev sandbox
compile_commands.json

47
xo-ratio/CMakeLists.txt Normal file
View file

@ -0,0 +1,47 @@
# xo-ratio/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(xo_ratio VERSION 1.0)
enable_language(CXX)
include(GNUInstallDirs)
include(cmake/xo-bootstrap-macros.cmake)
xo_cxx_toplevel_options3()
# ----------------------------------------------------------------
# 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})
# ----------------------------------------------------------------
add_subdirectory(example)
add_subdirectory(utest)
# ----------------------------------------------------------------
# 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)
# ----------------------------------------------------------------
# docs targets depend on all the other library/utest targets
#
add_subdirectory(docs)
# ----------------------------------------------------------------
# dependencies
xo_headeronly_dependency(${SELF_LIB} xo_reflectutil)
xo_headeronly_dependency(${SELF_LIB} xo_flatstring)
#xo_headeronly_dependency(${SELF_LIB} randomgen)
# etc..
# end CMakeLists.txt

29
xo-ratio/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.

85
xo-ratio/README.md Normal file
View file

@ -0,0 +1,85 @@
# 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 constexpr conversion to fixed-capacity strings (using xo-flatstring)
3. Provides concept support (with c++20)
4. Requires modern (c++17) support to achieve this
Relative to `boost::ratio`:
1. Streamlined, assumes modern compiler support
## Documentation
- xo-ratio documentation [under construction]: [documentation](https://rconybea.github.io/web/xo-ratio/html/index.html)
- unit test coverage here: [coverage](https://rconybea.github.io/web/xo-ratio/ccov/html/index.html)
## Getting Started
### install dependencies
- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros
- [github/Rconybea/xo-flatstring](https://github.com/Rconybea/xo-flatstring) fixed-capacity strings
- [github/rconybea/xo-indentlog](https://github.com/Rconybea/xo-indentlog) logging (used by unit tests)
- [github/rconybea/xo-randomgen](https://github.com/Rconybea/xo-randomgen) rng (used by unit tests)
### copy repository locally
Using `xo-build` (provided by `xo-cmake`):
```
$ xo-build --clone xo-ratio`
```
or equivalently:
```
$ cd ~/proj # for example
$ git clone https://github.com/Rconybea/xo-ratio
```
### build + install
Using `xo-build`:
```
$ xo-build --configure --build --install xo-ratio
```
or equivalently:
```
$ PREFIX=/usr/local # for example
$ BUILDDIR=.build # for example
$ mkdir xo-ratio/${BUILDDIR}
$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-ratio -B xo-ratio/${BUILDDIR}
$ cmake --build xo-ratio/${BUILDDIR}
$ cmake --install xo-ratio/${BUILDDIR}
```
### build with unit test coverage
```
$ cd xo-ratio
$ mkdir .build-ccov
$ cmake -DCMAKE_BUILD_TYPE=coverage -B .build-ccov
$ cmake --build .build-ccov
```
run coverage-enabled unit tests
```
$ cmake --build .build-ccov -- test
```
generate html+text coverage report
```
$ cmake --build .build-ccov -- ccov
```
browse to `.build-ccov/ccov/html/index.html`
### LSP support
```
$ cd xo-ratio
$ ln -s .build/compile_commands.json
```

View file

@ -0,0 +1,33 @@
# ----------------------------------------------------------------
# for example:
# $ PREFIX=/usr/local # for example
# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build
#
# will get
# CMAKE_MODULE_PATH
# from xo-cmake-config --cmake-module-path
#
# and expect .cmake macros in
# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake
# ----------------------------------------------------------------
find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED)
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix"))
message(FATAL "could not find xo-cmake-config executable")
endif()
if (NOT XO_SUBMODULE_BUILD)
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix))
# default to typical install location for xo-project-macros
execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH)
message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
endif()
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_cxx)
xo_cxx_bootstrap_message()

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(xo_flatstring)
#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@")

View file

@ -0,0 +1,5 @@
# xo-flatstring/docs/CMakeLists.txt
xo_doxygen_collect_deps()
xo_docdir_doxygen_config()
xo_docdir_sphinx_config(index.rst install.rst ratio-reference.rst ratio-class.rst ratio-functions.rst)

2816
xo-ratio/docs/Doxyfile.in Normal file

File diff suppressed because it is too large Load diff

BIN
xo-ratio/docs/_static/img/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

77
xo-ratio/docs/_static/img/icon.svg vendored Normal file
View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="108.32474mm"
height="108.39381mm"
viewBox="0 0 108.32474 108.39382"
version="1.1"
id="svg5"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="1"
inkscape:cx="157"
inkscape:cy="322.5"
inkscape:window-width="1607"
inkscape:window-height="1085"
inkscape:window-x="26"
inkscape:window-y="23"
inkscape:window-maximized="0"
inkscape:current-layer="layer1"
fit-margin-top="9.8"
fit-margin-left="8"
fit-margin-right="10"
fit-margin-bottom="9.8" />
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-47.453609,-27.112369)">
<circle
style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.264583"
id="path31"
cx="68.211342"
cy="87.603088"
r="12.757732" />
<circle
style="fill:#ffcf00;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
id="path31-3"
cx="122.47422"
cy="49.670101"
r="12.757732" />
<circle
style="fill:#6b6bff;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
id="path31-3-6"
cx="133.02061"
cy="112.94845"
r="12.757732" />
<path
style="fill:none;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 73.144329,75.525772 C 85.051544,48.309278 109.54639,49.670102 109.54639,49.670102"
id="path589" />
<path
style="fill:none;stroke:#000000;stroke-width:2.44548;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 76.544292,96.598799 C 95.429921,116.11457 120.4897,113.37017 120.4897,113.37017"
id="path593" />
<path
style="fill:none;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 132,58.175257 c 17.01031,17.350515 7.14433,43.886603 7.14433,43.886603"
id="path788" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

77
xo-ratio/docs/_static/img/xo-icon.svg vendored Normal file
View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="108.32474mm"
height="108.39381mm"
viewBox="0 0 108.32474 108.39382"
version="1.1"
id="svg5"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="xo-icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="1"
inkscape:cx="157"
inkscape:cy="322.5"
inkscape:window-width="1607"
inkscape:window-height="1085"
inkscape:window-x="26"
inkscape:window-y="23"
inkscape:window-maximized="0"
inkscape:current-layer="layer1"
fit-margin-top="9.8"
fit-margin-left="8"
fit-margin-right="10"
fit-margin-bottom="9.8" />
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-47.453609,-27.112369)">
<circle
style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.264583"
id="path31"
cx="68.211342"
cy="87.603088"
r="12.757732" />
<circle
style="fill:#ffcf00;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
id="path31-3"
cx="122.47422"
cy="49.670101"
r="12.757732" />
<circle
style="fill:#6b6bff;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
id="path31-3-6"
cx="133.02061"
cy="112.94845"
r="12.757732" />
<path
style="fill:none;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 73.144329,75.525772 C 85.051544,48.309278 109.54639,49.670102 109.54639,49.670102"
id="path589" />
<path
style="fill:none;stroke:#000000;stroke-width:2.44548;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 76.544292,96.598799 C 95.429921,116.11457 120.4897,113.37017 120.4897,113.37017"
id="path593" />
<path
style="fill:none;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 132,58.175257 c 17.01031,17.350515 7.14433,43.886603 7.14433,43.886603"
id="path788" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

36
xo-ratio/docs/conf.py Normal file
View file

@ -0,0 +1,36 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'xo ratio documentation'
copyright = '2024, Roland Conybeare'
author = 'Roland Conybeare'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
#extensions = []
extensions = [ "breathe",
"sphinx.ext.autodoc" # generate info from docstrings
]
# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in
# match project name in Doxyfile.in
breathe_default_project = "xodoxxml"
templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
pygments_style = 'sphinx'
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
#html_theme = 'alabaster'
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
html_favicon = '_static/img/favicon.ico'

35
xo-ratio/docs/index.rst Normal file
View file

@ -0,0 +1,35 @@
xo-ratio documentation
======================
xo-ratio is a lightweight header-only library that provides a constexpr
fixed-size no-allocation ratio implementation.
Why ``ratio``?
1. ``ratio`` instances can be used as template arguments. [1]_
2. ``ratio`` operations (arithmetic, integer powers, string conversion, ...) are ``constexpr``, so can be done at compile time. [2]_
3. a ``ratio`` expression can occupy both compile-time and runtime roles. [3]_
.. [1] verified with gcc 13.2.0; clang 18 does not recognize ratio<T> as a structured type for some reason.
.. [2] Although allocation is permitted in constexpr code, it's subject to several restrictions.
it's not yet possible (as of c++23) to use ``std::string`` at compile time.
Using ``xo::flatstring`` instead.
.. [3] contrast with a solution relying on template arguments, which must then be compile-time-only.
.. toctree::
:maxdepth: 2
:caption: xo-ratio contents:
install
ratio-reference
Indices and Tables
------------------
* :ref:`genindex`
* :ref:`search`

96
xo-ratio/docs/install.rst Normal file
View file

@ -0,0 +1,96 @@
.. _install:
.. toctree
:maxdepth: 2
Source
======
Source code lives on github `here`_
.. _here: https://github.com/rconybea/xo-ratio
To clone from git:
.. code-block:: bash
git clone https://github.com/rconybea/xo-ratio
Implementation relies on c++20 features (expanded use of constexpr; class-instances as template arguments).
Tested with gcc 13.2
Install
=======
Since xo-ratio is header-only, can incorporate into another project just by copying the include directories
to somewhere convenient.
Copy includes
-------------
.. code-block:: bash
# For example..
cd myproject
mkdir -p ext/xo-ratio
rsync -a -v path/to/xo-ratio/include/ ext/xo-ratio/
Include as git submodule
------------------------
.. code-block:: bash
cd myproject
git submodule add -b main https://github.com/rconybea/xo-ratio ext/xo-ratio
git submodule update --init
This assumes you organize directly-incorporated dependencies under directory ``myproject/ext``.
You would then add ``myproject/ext/xo-ratio/include`` to your compiler's include path,
and from c++ do something like
.. code-block:: c++
#include <xo/ratio/ratio.hpp>
in c++ source files that rely on xo-ratio
Supported compilers
-------------------
* developed with gcc 13.2.0; github CI using gcc 11.4.0 (asof April 2024)
Building from source
--------------------
Although the xo-ratio library is header-only, unit tests have some dependencies.
Example instructions (github CI) for build starting from stock ubuntu are in `ubuntu-main.yml`_
.. _ubuntu-main.yml: https://github.com/Rconybea/xo-ratio/blob/main/.github/workflows/ubuntu-main.yml
Unit test dependencies:
* `catch2`_ header-only unit-test framework
* `xo-cmake`_ cmake macros
* `xo-indentlog`_ logging with call-structure indenting
* `xo-randomgen`_ fast random-number generator (xoshiro256ss)
.. _catch2: https://github.com/catchorg/Catch2
.. _xo-cmake: https://github.com/rconybea/xo-cmake
.. _xo-indentlog: https://github.com/rconybea/indentlog
.. _xo-randomgen: https://github.com/rconybea/randomgen
To build documentation, will also need:
* `doxygen`_ c++ documentation
* `graphviz`_ graph drawing software, used by doxygen
* `sphinx`_ documentation system, ReST (`.rst`) format
* `breathe`_ use `doxygen`-generated ingredients in sphinx `.rst` files
* `sphinx_rtd_theme`_ css for sphinx html output
Can install these
.. _doxygen: https://www.doxygen.nl
.. _graphviz: https://graphviz.org
.. _sphinx: https://www.sphinx-doc.org/en/master
.. _breathe: https://breathe.readthedocs.io/en/latest
.. _sphinx_rtd_theme: https://github.com/readthedocs/sphinx_rtd_theme

View file

@ -0,0 +1,47 @@
.. _ratio-class:
Ratio
=====
.. code-block:: cpp
#include <xo/ratio/ratio.hpp>
A ``ratio`` represents an exact fraction of two integers.
.. doxygenclass:: xo::ratio::ratio
Instance Variables
------------------
.. doxygengroup:: ratio-instance-variables
Types
-----
.. doxygengroup:: ratio-types
Constructors
------------
.. doxygengroup:: ratio-ctor
Static Methods
--------------
.. doxygengroup:: ratio-static-methods
Access Methods
--------------
.. doxygengroup:: ratio-access
General Methods
---------------
.. doxygengroup:: ratio-methods
Conversion
----------
.. doxygengroup:: ratio-conversion

View file

@ -0,0 +1,17 @@
.. _ratio_functions:
.. toctree::
:maxdepth: 2
Ratio Functions
===============
.. code-block:: cpp
#include <xo/ratio/ratio.hpp>
.. doxygenfunction:: xo::ratio::make_ratio
.. doxygengroup:: ratio-arithmetic
.. doxygengroup:: ratio-3way-compare

View file

@ -0,0 +1,11 @@
.. _ratio-reference:
Ratio Reference
===============
.. toctree::
:maxdepth: 2
:caption: Ratio Reference
ratio-class
ratio-functions

View file

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

View file

@ -0,0 +1,12 @@
# xo-ratio/example/ex1/CMakeLists.txt
set(SELF_EXE xo_ratio_ex1)
set(SELF_SRCS ex1.cpp)
if (XO_ENABLE_EXAMPLES)
add_executable(${SELF_EXE} ${SELF_SRCS})
xo_include_options2(${SELF_EXE})
xo_self_dependency(${SELF_EXE} xo_ratio)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,287 @@
/** @file ex1.cpp **/
#include "xo/ratio/ratio_iostream.hpp"
#include "xo/indentlog/scope.hpp"
#include "xo/indentlog/print/hex.hpp"
#include <iostream>
namespace {
using xo::xtag;
using xo::hex_view;
#ifdef NOT_USING
template <std::size_t N>
xo::flatstring<N>
flatstring_from_int(int x)
{
XO_SCOPE(log, always);
constexpr size_t buf_z = 20;
bool negative_flag = (x < 0);
std::size_t i = buf_z;
char buf[buf_z];
std::fill_n(buf, N, '\0');
if (negative_flag)
x = -x;
buf[--i] = '\0';
while ((i > 0) && (x != 0)) {
buf[--i] = ('0' + x % 10);
x = x / 10;
}
if ((i > 0) && negative_flag)
buf[--i] = '-';
char retv[N];
std::fill_n(retv, N, '\0');
std::copy_n(buf + i, buf_z - i, retv);
log && log(xtag("i",i), xtag("buf[i..]", hex_view(buf+i, buf+buf_z, true)));
return retv;
}
#endif
template <typename Ratio, std::size_t N>
constexpr xo::flatstring<N>
ratio_to_str(Ratio x) noexcept
{
if (x.is_integer()) {
return xo::flatstring<N>::from_int(x.num());
} else {
constexpr auto num_str = xo::flatstring<N>::from_int(x.num());
constexpr auto den_str = xo::flatstring<N>::from_int(x.den());
constexpr auto tmp = flatstring_concat("(", num_str, "/", den_str, ")");
return tmp;
}
}
/* verify can use ratio instance as template argument */
using ratio64_t = xo::ratio::ratio<int64_t>;
template<ratio64_t Ratio>
constexpr double my_ratio_approx = Ratio.num_ / static_cast<double>(Ratio.den_);
}
int
main() {
using xo::ratio::make_ratio;
using xo::ratio::ratio;
using xo::ratio::ratio_concept;
using xo::flatstring;
using namespace std;
static_assert(my_ratio_approx<ratio64_t(1, 2)> == 0.5);
static_assert(my_ratio_approx<ratio64_t{}> == 0.0);
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.convert_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.convert_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.convert_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.convert_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.convert_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; // 9/2
static_assert(r17 == ratio(9, 2));
static_assert(!r17.is_integer());
constexpr auto s17_num_str = flatstring<20>::from_int(r17.num());
static_assert(s17_num_str == flatstring("9"));
constexpr auto s17_den_str = flatstring<20>::from_int(r17.den());
static_assert(s17_den_str == flatstring("2"));
constexpr auto s17_str = flatstring_concat(flatstring_concat(flatstring("("),
s17_num_str),
flatstring_concat(flatstring("/"),
s17_den_str),
flatstring(")"));
cerr << "s17_str=" << s17_str << endl;
//constexpr auto s17 = ratio_to_str<decltype(r17), 20>(r17);
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));
//cerr << flatstring_from_int<10>(1) << endl;
constexpr auto s20 = flatstring<10>::from_int(-123);
cerr << "s20=" << s20 << endl;
static_assert(s20.size() > 0);
static_assert(s20 == flatstring("-123"));
/* 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 */

View file

@ -0,0 +1,674 @@
/** @file ratio.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "ratio_concept.hpp"
#include "xo/flatstring/flatstring.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 convert value to different numeric (or ratio) representation **/
template <typename Ratio, typename ToType, bool ToRatioFlag = ratio_concept<ToType>>
struct converter_type;
}
/** @class ratio
* @brief represent a ratio of two Int values.
**/
template <typename Int>
requires std::totally_ordered<Int>
struct ratio
{
public:
/** @defgroup ratio-types **/
///@{
/** @brief representation for (numerator, denominator) **/
using component_type = Int;
///@}
public:
/** @defgroup ratio-ctor **/
///@{
/** @brief construct ratio 0/1 **/
constexpr ratio() = default;
/** @brief construct ratio with numerator @p n and denominator @p d.
*
* Ratio need not be normalized
**/
constexpr ratio(Int n, Int d = 1) : num_{n}, den_{d} {}
///@}
/** @defgroup ratio-static-methods **/
///@{
/** @brief ratio in lowest commono terms
*
**/
static constexpr ratio reduce(Int n, Int d) {
return ratio(n, d).normalize();
}
/** @brief add ratios @p x and @p y
*
* @post result ratio is normalized
**/
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 ratio @p y from ratio @p x
*
* @post result ratio is normalized
**/
static constexpr ratio subtract(const ratio & x,
const ratio & y) {
return add(x, y.negate());
}
/** @brief multiply ratios @p x and @p y
*
* @post result ratio is normalized
**/
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 ratio @p y into ratio @p x
*
* @post result ratio is normalized
**/
static constexpr ratio divide(const ratio & x,
const ratio & y)
{
return multiply(x, y.reciprocal());
}
/** @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);
}
///@}
/** @defgroup ratio-access **/
///@{
/** @brief fetch ratio's numerator **/
constexpr Int num() const noexcept { return num_; }
/** @brief fetch ratio's denominator **/
constexpr Int den() const noexcept { return den_; }
/** @brief true if and only if ratio is equal to zero **/
constexpr bool is_zero() const noexcept { return (num_ == 0) && (den_ != 0); }
/** @brief true if and only if ratio is equal to one **/
constexpr bool is_unity() const noexcept { return (num_ != 0) && (num_ == den_); }
/** @brief true if and only if ratio represents an integer
*
* (denominator is +/- 1)
**/
constexpr bool is_integer() const noexcept { return den_ == 1 || den_ == -1; }
///@}
/** @defgroup ratio-methods **/
///@{
/** @brief r.negate() is the exact ratio representing @c -r **/
constexpr ratio negate() const { return ratio(-num_, den_); }
/** @brief r.reciprocal() is the eact ratio representing @c 1/r **/
constexpr ratio reciprocal() const { return ratio(den_, num_); }
/** @brief r.floor() is the largest integer x : x <= r **/
constexpr Int floor() const { return (num_ / den_); }
/** @brief r.ceil() is the smallest integer x : r < x. **/
constexpr Int ceil() const { return floor() + 1; }
/** @brief reduce to lowest terms
*
* @pre @c Int type must be totally ordered
**/
constexpr ratio normalize() const requires std::totally_ordered<Int> {
if (den_ < 0)
return ratio(-num_, -den_).normalize();
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, ratio(this->floor(), 1));
}
/** @brief compute integer exponent @p p of this ratio
*
* @note time complexity is O(log p)
**/
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 negate operator **/
constexpr ratio operator-() const { return ratio(-num_, den_); }
///@}
/** @defgroup ratio-conversion **/
///@{
/** @brief convert to non-ratio representation
*
* For example: to int or double
**/
template <typename Repr>
constexpr Repr convert_to() const noexcept {
return detail::converter_type<ratio, Repr>::convert(*this);
}
/** @brief convert to short human-friendly flatstring representation
*
* Example:
* @code
* ratio(7,1).to_str<5>(); // "7"
* ratio(1,7).to_str<5>(); // "(1/7)"
* ratio(-1,7).to_str<10>(); // "(-1/7)"
* ratio(-1,7).to_str<5>(); // "(-1/"
* @endcode
**/
template <std::size_t N>
constexpr flatstring<N> to_str() const noexcept {
if (this->is_integer()) {
return flatstring<N>::from_int(num_);
} else {
auto num_str = flatstring<N>::from_int(num_);
auto den_str = flatstring<N>::from_int(den_);
/* tmp capacity will be about 2N+3 */
auto tmp = flatstring_concat(flatstring("("),
num_str,
flatstring("/"),
den_str,
flatstring(")"));
flatstring<N> retval;
retval.assign(tmp);
return retval;
}
}
/** @brief convert to representation using different integer types **/
template <typename Ratio2>
constexpr operator Ratio2 () const noexcept 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) noexcept {
/* 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;
}
public: /* need public members so that a ratio instance can be a non-type template parameter (a structural type) */
/** @defgroup ratio-instance-variables **/
///@{
/** @brief numerator **/
Int num_ = 0;
/** @brief denominator **/
Int den_ = 1;
///@}
};
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.normalize(); }
};
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); }
};
}
namespace detail {
template <typename Ratio, typename ToType, bool ToRatioFlag>
struct converter_type;
template <typename Ratio, typename ToType>
struct converter_type<Ratio, ToType, true /*ToRatioFlag*/> {
/* to convert to a ratio, can just use built-in conversion */
static constexpr ToType convert(Ratio x) { return ToType(x.num(), x.den()); }
};
template <typename Ratio, typename ToType>
struct converter_type<Ratio, ToType, false /*!ToRatioFlag*/> {
/* to convert to non-ratio, do division */
static constexpr ToType convert(Ratio x) { return x.num() / static_cast<ToType>(x.den()); }
};
}
/** @brief create a ratio in lowest terms from two integers **/
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*/
/** @defgroup ratio-arithmetic **/
///@{
/** @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);
}
///@}
/** @defgroup ratio-3way-compare 3way comparison **/
///@{
/** @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 Int>
void
print_ratio (std::ostream & os, const ratio<Int> & 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 Int>
inline std::ostream &
operator<< (std::ostream & os, const ratio<Int> & x) {
print_ratio(os, x);
return os;
}
}
}
/** end ratio_iostream.hpp **/

View file

@ -0,0 +1,25 @@
/** @file ratio_reflect.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "xo/reflectutil/reflect_struct_info.hpp"
#include "ratio.hpp"
//#include <cstdint>
namespace xo {
namespace reflect {
template <typename Int>
REFLECT_BASE_STRUCT_INFO_TBODY(xo::ratio::ratio<Int>, 2);
template <typename Int>
REFLECT_STRUCT_MEMBER_INFO_TBODY(xo::ratio::ratio<Int>, 0, num);
template <typename Int>
REFLECT_STRUCT_MEMBER_INFO_TBODY(xo::ratio::ratio<Int>, 1, den);
}
}
/** end ratio_reflect.hpp **/

View file

@ -0,0 +1,24 @@
# xo-ratio/utest/CMakeLists.txt
set(SELF_EXE utest.ratio)
set(SELF_SRCS
ratio_utest_main.cpp
ratio_reflect.test.cpp
ratio.test.cpp)
if (ENABLE_TESTING)
xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS})
xo_utest_coverage_config2()
# ----------------------------------------------------------------
# dependencies..
xo_self_headeronly_dependency(${SELF_EXE} xo_ratio)
xo_dependency(${SELF_EXE} reflect)
xo_dependency(${SELF_EXE} randomgen)
xo_dependency(${SELF_EXE} indentlog)
xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,241 @@
/** @file ratio.utest.cpp **/
#include "xo/ratio/ratio.hpp"
#include "xo/ratio/ratio_iostream.hpp"
#include "xo/randomgen/random_seed.hpp"
#include "xo/randomgen/xoshiro256.hpp"
#include "xo/indentlog/scope.hpp"
#include "xo/indentlog/print/vector.hpp"
#include "xo/indentlog/print/array.hpp"
#include "xo/indentlog/print/tag.hpp"
//#include "xo/indentlog/print/hex.hpp"
#include <catch2/catch.hpp>
#include <random>
#include <numeric>
namespace xo {
using std::exponential_distribution;
using std::bernoulli_distribution;
namespace ut {
template <typename Int>
struct ratio_distribution {
ratio_distribution(double sign_prob, double int_lambda)
: sign_dist_{sign_prob}, int_dist_{int_lambda} {}
template <typename Rng>
xo::ratio::ratio<Int>
random_ratio(Rng & rng) {
Int num_sign = sign_dist_(rng) ? -1 : +1;
Int num = num_sign * (1 + int_dist_(rng));
Int den_sign = sign_dist_(rng) ? -1 : +1;
Int den = den_sign * (1 + int_dist_(rng));
return xo::ratio::ratio(num, den).normalize();
}
template <typename Rng>
xo::ratio::ratio<Int> operator()(Rng & rng) {
return random_ratio(rng);
}
/* generate negative numbers some of the time */
bernoulli_distribution sign_dist_;
/* create ratios involving integers, but don't need integers to be too large */
exponential_distribution<double> int_dist_;
};
template <typename Rng>
void
ratio_tests(Rng & rng)
{
constexpr bool debug_flag = false;
std::size_t n_ratio = 25;
std::size_t n_experiment = n_ratio * n_ratio / 4;
/* want to avoid integer overflow when exponentiating */
constexpr int max_pwr = 5;
scope log(XO_DEBUG2(debug_flag, "ratio_tests"));
log && log(xtag("n_ratio", n_ratio));
ratio_distribution<int> ratio_dist(0.25 /*sign_prob*/,
0.05 /*lambda */);
bernoulli_distribution sign_dist(0.5);
exponential_distribution<double> power_dist(0.2 /*lambda*/);
std::vector<xo::ratio::ratio<int>> ratio_v;
/* ensure 0, 1, -1 all present */
ratio_v.push_back(xo::ratio::ratio<int>(0,1));
ratio_v.push_back(xo::ratio::ratio<int>(1,1));
ratio_v.push_back(xo::ratio::ratio<int>(-1,1));
for (std::uint32_t i=0, n=n_ratio - ratio_v.size(); i<n; ++i) {
ratio_v.push_back(ratio_dist(rng));
REQUIRE(std::gcd(ratio_v[i].num(), ratio_v[i].den()) == 1);
}
INFO(XTAG(ratio_v));
for (std::uint32_t i=0; i<n_experiment; ++i) {
INFO(tostr(XTAG(i), XTAG(n_experiment)));
/* choose a couple of ratios at random */
auto ratio1 = ratio_v[rng() % n_ratio];
auto ratio2 = ratio_v[rng() % n_ratio];
double ratio1_approx = ratio1.num() / static_cast<double>(ratio1.den());
double ratio2_approx = ratio2.num() / static_cast<double>(ratio2.den());
{
auto sum = ratio1 + ratio2;
double sum_approx = sum.num() / static_cast<double>(sum.den());
log && log(XTAG(ratio1), XTAG(ratio2), XTAG(sum));
REQUIRE(sum_approx == Approx(ratio1_approx + ratio2_approx).epsilon(1e-6));
REQUIRE(std::gcd(sum.num(), sum.den()) == 1);
REQUIRE(sum.den() > 0);
/* comparison tests. piggyback on sum */
{
auto cmp_approx = (sum_approx <=> ratio1_approx);
REQUIRE(cmp_approx == (sum <=> ratio1));
}
{
bool eq = (sum == ratio1);
bool eq_approx = (sum_approx == ratio1_approx);
REQUIRE(eq == eq_approx);
}
{
bool ne = (sum != ratio1);
bool ne_approx = (sum_approx != ratio1_approx);
REQUIRE(ne == ne_approx);
}
{
bool gt = (sum > ratio1);
bool gt_approx = (sum_approx > ratio1_approx);
REQUIRE(gt == gt_approx);
}
{
bool ge = (sum >= ratio1);
bool ge_approx = (sum_approx >= ratio1_approx);
REQUIRE(ge == ge_approx);
}
{
bool lt = (sum > ratio1);
bool lt_approx = (sum_approx > ratio1_approx);
REQUIRE(lt == lt_approx);
}
{
bool le = (sum >= ratio1);
bool le_approx = (sum_approx >= ratio1_approx);
REQUIRE(le == le_approx);
}
}
{
auto neg = -ratio1;
double neg_approx = neg.num() / static_cast<double>(neg.den());
log && log(XTAG(ratio1), XTAG(neg));
REQUIRE(neg_approx == Approx(-ratio1_approx).epsilon(1e-06));
REQUIRE(std::gcd(neg.num(), neg.den()) == 1);
REQUIRE(neg.den() > 0);
}
{
auto diff = ratio1 - ratio2;
double diff_approx = diff.num() / static_cast<double>(diff.den());
log && log(XTAG(ratio1), XTAG(ratio2), XTAG(diff));
REQUIRE(diff_approx == Approx(ratio1_approx - ratio2_approx).epsilon(1e-6));
REQUIRE(std::gcd(diff.num(), diff.den()) == 1);
REQUIRE(diff.den() > 0);
}
{
auto prod = ratio1 * ratio2;
double prod_approx = prod.num() / static_cast<double>(prod.den());
log && log(XTAG(ratio1), XTAG(ratio2), XTAG(prod));
REQUIRE(prod_approx == Approx(ratio1_approx * ratio2_approx).epsilon(1e-6));
REQUIRE(std::gcd(prod.num(), prod.den()) == 1);
REQUIRE(prod.den() > 0);
}
{
auto div = ratio1 * ratio2;
double div_approx = div.num() / static_cast<double>(div.den());
log && log(XTAG(ratio1), XTAG(ratio2), XTAG(div));
REQUIRE(div_approx == Approx(ratio1_approx * ratio2_approx).epsilon(1e-6));
REQUIRE(std::gcd(div.num(), div.den()) == 1);
REQUIRE(div.den() > 0);
}
{
int exp = (sign_dist(rng) ? -1 : +1) * power_dist(rng);
if (std::abs(exp) >= max_pwr) {
exp = (std::signbit(exp) ? -1 : +1) * max_pwr;
}
auto pwr = ratio1.power(exp);
double pwr_approx = pwr.num() / static_cast<double>(pwr.den());
log && log(XTAG(ratio1), XTAG(exp), XTAG(pwr));
REQUIRE(pwr_approx == Approx(::pow(ratio1_approx, exp)).epsilon(1e-6));
REQUIRE(std::gcd(pwr.num(), pwr.den()) == 1);
REQUIRE(pwr.den() >= 0);
}
{
auto ratio1_str = ratio1.template to_str<20>();
log && log(XTAG(ratio1_str));
REQUIRE(!ratio1_str.empty());
/* TODO: more tests here --
* if we write parser, verify can recover original ratio
*/
}
}
}
TEST_CASE("ratio", "[ratio]") {
//constexpr bool c_debug_flag = false;
// can get bits from /dev/random by uncommenting the 2nd line below
uint64_t seed = 5521646833469436535ul;
//rng::Seed<rng::xoshiro256ss> seed;
//std::cerr << "ratio: seed=" << seed << std::endl;
auto rng = rng::xoshiro256ss(seed);
ratio_tests(rng);
} /*TEST_CASE(ratio)*/
} /*namespace ut*/
} /*namespace xo*/
/** end ratio.utest.cpp **/

View file

@ -0,0 +1,32 @@
/* @file ratio_reflect.test.cpp */
#include "xo/ratio/ratio_reflect.hpp"
#include "xo/reflect/reflect_struct.hpp"
#include <catch2/catch.hpp>
namespace xo {
namespace ut {
TEST_CASE("ratio_reflect", "[ratio][reflect]") {
using xo::reflect::reflect_struct;
using xo::reflect::TypeDescrBase;
using xo::reflect::TypeDescr;
using xo::ratio::ratio;
/* verify ratio reflection */
TypeDescr td = reflect_struct<ratio<std::int64_t>>();
REQUIRE(td->is_struct());
REQUIRE(td->metatype() == xo::reflect::Metatype::mt_struct);
REQUIRE(td->n_child(nullptr) == 2);
REQUIRE(td->struct_member(0).member_name() == "num");
REQUIRE(td->struct_member(0).get_member_td()->short_name() == "long int");
REQUIRE(td->struct_member(1).member_name() == "den");
REQUIRE(td->struct_member(1).get_member_td()->short_name() == "long int");
TypeDescrBase::print_reflected_types(std::cerr);
}
} /*namespace ut*/
} /*namespace xo*/
/* end ratio_reflect.test.cpp */

View file

@ -0,0 +1,6 @@
/** @file ratio_utest_main.cpp **/
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
/** end ratio_utest_main.cpp **/