Custom ratio type implemented

This commit is contained in:
Mateusz Pusz
2019-05-28 16:17:48 +02:00
parent 7bcc5c7b0b
commit 9c0f2c6878
11 changed files with 205 additions and 176 deletions

View File

@@ -1,87 +0,0 @@
// The MIT License (MIT)
//
// Copyright (c) 2018 Mateusz Pusz
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include <ratio>
#include <type_traits>
namespace units {
// static_sign
template<std::intmax_t Pn>
struct static_sign : std::integral_constant<std::intmax_t, (Pn < 0) ? -1 : 1> {
};
// static_abs
template<std::intmax_t Pn>
struct static_abs : std::integral_constant<std::intmax_t, Pn * static_sign<Pn>::value> {
};
// static_gcd
template<std::intmax_t Pn, std::intmax_t Qn>
struct static_gcd : static_gcd<Qn, (Pn % Qn)> {
};
template<std::intmax_t Pn>
struct static_gcd<Pn, 0> : std::integral_constant<std::intmax_t, static_abs<Pn>::value> {
};
template<std::intmax_t Qn>
struct static_gcd<0, Qn> : std::integral_constant<std::intmax_t, static_abs<Qn>::value> {
};
// is_ratio
namespace detail {
template<typename T>
inline constexpr bool is_ratio = false;
template<intmax_t Num, intmax_t Den>
inline constexpr bool is_ratio<std::ratio<Num, Den>> = true;
} // namespace detail
template<typename T>
concept bool Ratio = detail::is_ratio<T>;
// common_ratio
namespace detail {
// todo: simplified
template<typename R1, typename R2>
struct common_ratio_impl {
using gcd_num = static_gcd<R1::num, R2::num>;
using gcd_den = static_gcd<R1::den, R2::den>;
using type = std::ratio<gcd_num::value, (R1::den / gcd_den::value) * R2::den>;
};
}
template<Ratio R1, Ratio R2>
using common_ratio = detail::common_ratio_impl<R1, R2>::type;
} // namespace units

View File

@@ -70,16 +70,16 @@ namespace units {
} // namespace literals
// US customary units
struct yard : unit<dimension_length, std::ratio<9'144, 10'000>> {};
struct yard : unit<dimension_length, ratio<9'144, 10'000>> {};
template<> struct upcasting_traits<upcast_from<yard>> : upcast_to<yard> {};
struct foot : unit<dimension_length, std::ratio_multiply<std::ratio<1, 3>, yard::ratio>> {};
struct foot : unit<dimension_length, ratio_multiply<ratio<1, 3>, yard::ratio>> {};
template<> struct upcasting_traits<upcast_from<foot>> : upcast_to<foot> {};
struct inch : unit<dimension_length, std::ratio_multiply<std::ratio<1, 12>, foot::ratio>> {};
struct inch : unit<dimension_length, ratio_multiply<ratio<1, 12>, foot::ratio>> {};
template<> struct upcasting_traits<upcast_from<inch>> : upcast_to<inch> {};
struct mile : unit<dimension_length, std::ratio_multiply<std::ratio<1'760>, yard::ratio>> {};
struct mile : unit<dimension_length, ratio_multiply<ratio<1'760>, yard::ratio>> {};
template<> struct upcasting_traits<upcast_from<mile>> : upcast_to<mile> {};
inline namespace literals {

View File

@@ -36,7 +36,7 @@ namespace units {
template<Unit U = class kilogram, Number Rep = double>
using mass = quantity<dimension_mass, U, Rep>;
struct gram : unit<dimension_mass, std::milli> {};
struct gram : unit<dimension_mass, ratio<1, 1000>> {};
template<> struct upcasting_traits<upcast_from<gram>> : upcast_to<gram> {};
struct kilogram : kilo<gram> {};

View File

@@ -125,7 +125,7 @@ namespace units {
requires std::Same<typename To::dimension, D>
constexpr To quantity_cast(const quantity<D, U, Rep>& q)
{
using c_ratio = std::ratio_divide<typename U::ratio, typename To::unit::ratio>;
using c_ratio = ratio_divide<typename U::ratio, typename To::unit::ratio>;
using c_rep = std::common_type_t<typename To::rep, Rep, intmax_t>;
using cast = detail::quantity_cast_impl<To, c_ratio, c_rep, c_ratio::num == 1, c_ratio::den == 1>;
return cast::cast(q);
@@ -284,7 +284,7 @@ namespace units {
{
using dim = dimension_multiply_t<D1, D2>;
using common_rep = decltype(lhs.count() * rhs.count());
using ret = quantity<dim, upcasting_traits_t<unit<dim, std::ratio_multiply<typename U1::ratio, typename U2::ratio>>>, common_rep>;
using ret = quantity<dim, upcasting_traits_t<unit<dim, ratio_multiply<typename U1::ratio, typename U2::ratio>>>, common_rep>;
return ret(lhs.count() * rhs.count());
}
@@ -298,7 +298,7 @@ namespace units {
using dim = dim_invert_t<D>;
using common_rep = decltype(v / q.count());
using ret = quantity<dim, upcasting_traits_t<unit<dim, std::ratio<U::ratio::den, U::ratio::num>>>, common_rep>;
using ret = quantity<dim, upcasting_traits_t<unit<dim, ratio<U::ratio::den, U::ratio::num>>>, common_rep>;
using den = quantity<D, U, common_rep>;
return ret(v / den(q).count());
}
@@ -331,13 +331,13 @@ namespace units {
[[nodiscard]] constexpr Quantity operator/(const quantity<D1, U1, Rep1>& lhs,
const quantity<D2, U2, Rep2>& rhs)
requires treat_as_floating_point<decltype(lhs.count() / rhs.count())> ||
(std::ratio_divide<typename U1::ratio, typename U2::ratio>::den == 1)
(ratio_divide<typename U1::ratio, typename U2::ratio>::den == 1)
{
Expects(rhs != std::remove_cvref_t<decltype(rhs)>(0));
using common_rep = decltype(lhs.count() / rhs.count());
using dim = dimension_divide_t<D1, D2>;
using ret = quantity<dim, upcasting_traits_t<unit<dim, std::ratio_divide<typename U1::ratio, typename U2::ratio>>>, common_rep>;
using ret = quantity<dim, upcasting_traits_t<unit<dim, ratio_divide<typename U1::ratio, typename U2::ratio>>>, common_rep>;
return ret(lhs.count() / rhs.count());
}

138
src/include/units/ratio.h Normal file
View File

@@ -0,0 +1,138 @@
// The MIT License (MIT)
//
// Copyright (c) 2018 Mateusz Pusz
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include <type_traits>
#include <numeric>
#include <cstdint>
#include <gsl/gsl-lite.hpp>
namespace units {
namespace detail {
template<typename T>
[[nodiscard]] constexpr T abs(T v) noexcept { return v < 0 ? -v : v; }
}
template<std::intmax_t Num, std::intmax_t Den = 1>
struct ratio {
static_assert(Den != 0, "zero denominator");
static_assert(-INTMAX_MAX <= Num, "numerator too negative");
static_assert(-INTMAX_MAX <= Den, "denominator too negative");
static constexpr std::intmax_t num = Num * (Den < 0 ? -1 : 1) / std::gcd(Num, Den);
static constexpr std::intmax_t den = detail::abs(Den) / std::gcd(Num, Den);
using type = ratio<num, den>;
};
// is_ratio
namespace detail {
template<typename T>
inline constexpr bool is_ratio = false;
template<intmax_t Num, intmax_t Den>
inline constexpr bool is_ratio<ratio<Num, Den>> = true;
} // namespace detail
template<typename T>
concept bool Ratio = detail::is_ratio<T>;
// ratio_multiply
namespace detail {
static constexpr std::intmax_t safe_multiply(std::intmax_t lhs, std::intmax_t rhs)
{
constexpr std::uintmax_t c = std::uintmax_t(1) << (sizeof(std::intmax_t) * 4);
const std::uintmax_t a0 = detail::abs(lhs) % c;
const std::uintmax_t a1 = detail::abs(lhs) / c;
const std::uintmax_t b0 = detail::abs(rhs) % c;
const std::uintmax_t b1 = detail::abs(rhs) / c;
Expects(a1 == 0 || b1 == 0); // overflow in multiplication
Expects(a0 * b1 + b0 * a1 < (c >> 1)); // overflow in multiplication
Expects(b0 * a0 <= INTMAX_MAX); // overflow in multiplication
Expects((a0 * b1 + b0 * a1) * c <= INTMAX_MAX - b0 * a0); // overflow in multiplication
return lhs * rhs;
}
template<typename R1, typename R2>
struct ratio_multiply_impl {
private:
static constexpr std::intmax_t gcd1 = std::gcd(R1::num, R2::den);
static constexpr std::intmax_t gcd2 = std::gcd(R2::num, R1::den);
public:
using type = ratio<safe_multiply(R1::num / gcd1, R2::num / gcd2), safe_multiply(R1::den / gcd2, R2::den / gcd1)>;
static constexpr std::intmax_t num = type::num;
static constexpr std::intmax_t den = type::den;
};
}
template<Ratio R1, Ratio R2>
using ratio_multiply = typename detail::ratio_multiply_impl<R1, R2>::type;
// ratio_divide
namespace detail {
template<typename R1, typename R2>
struct ratio_divide_impl {
static_assert(R2::num != 0, "division by 0");
using type = ratio_multiply<R1, ratio<R2::den, R2::num>>;
static constexpr std::intmax_t num = type::num;
static constexpr std::intmax_t den = type::den;
};
}
template<Ratio R1, Ratio R2>
using ratio_divide = typename detail::ratio_divide_impl<R1, R2>::type;
// common_ratio
namespace detail {
// todo: simplified
template<typename R1, typename R2>
struct common_ratio_impl {
static constexpr std::intmax_t gcd_num = std::gcd(R1::num, R2::num);
static constexpr std::intmax_t gcd_den = std::gcd(R1::den, R2::den);
using type = ratio<gcd_num, (R1::den / gcd_den) * R2::den>;
};
}
template<Ratio R1, Ratio R2>
using common_ratio = typename detail::common_ratio_impl<R1, R2>::type;
} // namespace units

View File

@@ -48,10 +48,10 @@ namespace units {
struct millisecond : milli<second> {};
template<> struct upcasting_traits<upcast_from<millisecond>> : upcast_to<millisecond> {};
struct minute : unit<dimension_time, std::ratio<60>> {};
struct minute : unit<dimension_time, ratio<60>> {};
template<> struct upcasting_traits<upcast_from<minute>> : upcast_to<minute> {};
struct hour : unit<dimension_time, std::ratio<3600>> {};
struct hour : unit<dimension_time, ratio<3600>> {};
template<> struct upcasting_traits<upcast_from<hour>> : upcast_to<hour> {};
inline namespace literals {

View File

@@ -23,11 +23,12 @@
#pragma once
#include <units/dimension.h>
#include <units/bits/ratio_tools.h>
#include <units/ratio.h>
#include <ratio>
namespace units {
template<Dimension D, Ratio R = std::ratio<1>>
template<Dimension D, Ratio R = ratio<1>>
requires (R::num > 0)
struct unit : upcast_base<unit<D, R>> {
using dimension = D;
@@ -67,7 +68,7 @@ namespace units {
template<typename BaseDimension, Unit... Us>
struct get_ratio {
using ratio = std::ratio<1>;
using ratio = ::units::ratio<1>;
};
template<typename BaseDimension, Unit U, Unit... Rest>
@@ -87,8 +88,8 @@ namespace units {
template<Ratio Result, int UnitExpValue, Ratio UnitRatio>
struct ratio_op {
using calc_ratio = std::conditional_t<(UnitExpValue > 0), std::ratio_multiply<Result, UnitRatio>,
std::ratio_divide<Result, UnitRatio>>;
using calc_ratio = std::conditional_t<(UnitExpValue > 0), ratio_multiply<Result, UnitRatio>,
ratio_divide<Result, UnitRatio>>;
static constexpr int value = UnitExpValue > 0 ? UnitExpValue - 1 : UnitExpValue + 1;
using ratio = ratio_op<calc_ratio, value, UnitRatio>::ratio;
};
@@ -98,7 +99,7 @@ namespace units {
template<Unit... Us>
struct derived_ratio<dimension<>, Us...> {
using ratio = std::ratio<1>;
using ratio = ::units::ratio<1>;
};
template<Exponent E, Exponent... Rest, Unit... Us>
@@ -114,20 +115,20 @@ namespace units {
using derived_unit = unit<D, typename detail::derived_ratio<typename D::base_type, Us...>::ratio>;
// prefixes
template<Unit U> using atto = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::atto>>;
template<Unit U> using femto = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::femto>>;
template<Unit U> using pico = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::pico>>;
template<Unit U> using nano = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::nano>>;
template<Unit U> using micro = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::micro>>;
template<Unit U> using milli = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::milli>>;
template<Unit U> using centi = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::centi>>;
template<Unit U> using deca = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::deca>>;
template<Unit U> using hecto = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::hecto>>;
template<Unit U> using kilo = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::kilo>>;
template<Unit U> using mega = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::mega>>;
template<Unit U> using giga = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::giga>>;
template<Unit U> using tera = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::tera>>;
template<Unit U> using peta = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::peta>>;
template<Unit U> using exa = unit<typename U::dimension, std::ratio_multiply<typename U::ratio, std::exa>>;
template<Unit U> using atto = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<1, std::atto::den>>>;
template<Unit U> using femto = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<1, std::femto::den>>>;
template<Unit U> using pico = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<1, std::pico::den>>>;
template<Unit U> using nano = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<1, std::nano::den>>>;
template<Unit U> using micro = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<1, std::micro::den>>>;
template<Unit U> using milli = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<1, std::milli::den>>>;
template<Unit U> using centi = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<1, std::centi::den>>>;
template<Unit U> using deca = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<std::deca::num>>>;
template<Unit U> using hecto = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<std::hecto::num>>>;
template<Unit U> using kilo = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<std::kilo::num>>>;
template<Unit U> using mega = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<std::mega::num>>>;
template<Unit U> using giga = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<std::giga::num>>>;
template<Unit U> using tera = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<std::tera::num>>>;
template<Unit U> using peta = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<std::peta::num>>>;
template<Unit U> using exa = unit<typename U::dimension, ratio_multiply<typename U::ratio, ratio<std::exa::num>>>;
} // namespace units

View File

@@ -48,21 +48,6 @@ namespace units {
using type = ratio<num, den>;
};
// is_ratio
namespace detail {
template<typename T>
inline constexpr bool is_ratio = false;
template<intmax_t Num, intmax_t Den>
inline constexpr bool is_ratio<ratio<Num, Den>> = true;
} // namespace detail
template<typename T>
concept bool Ratio = detail::is_ratio<T>;
// ratio_multiply
namespace detail {
@@ -120,15 +105,19 @@ namespace units {
// common_ratio
// todo: simplified
template<Ratio R1, Ratio R2>
struct common_ratio {
static constexpr std::intmax_t gcd_num = std::gcd(R1::num, R2::num);
static constexpr std::intmax_t gcd_den = std::gcd(R1::den, R2::den);
using type = ratio<gcd_num, (R1::den / gcd_den) * R2::den>;
};
namespace detail {
template<Ratio R1, Ratio R2>
using common_ratio_t = typename common_ratio<R1, R2>::type;
// todo: simplified
template<typename R1, typename R2>
struct common_ratio_impl {
static constexpr std::intmax_t gcd_num = std::gcd(R1::num, R2::num);
static constexpr std::intmax_t gcd_den = std::gcd(R1::den, R2::den);
using type = ratio<gcd_num, (R1::den / gcd_den) * R2::den>;
};
}
template<typename R1, typename R2>
using common_ratio_t = typename detail::common_ratio_impl<R1, R2>::type;
} // namespace units

View File

@@ -53,32 +53,17 @@ namespace units {
struct static_gcd<0, Qn> : std::integral_constant<std::intmax_t, static_abs<Qn>::value> {
};
// is_ratio
namespace detail {
template<typename T>
inline constexpr bool is_ratio = false;
template<intmax_t Num, intmax_t Den>
inline constexpr bool is_ratio<std::ratio<Num, Den>> = true;
} // namespace detail
template<typename T>
concept bool Ratio = detail::is_ratio<T>;
// common_ratio
// todo: simplified
template<Ratio R1, Ratio R2>
template<typename R1, typename R2>
struct common_ratio {
using gcd_num = static_gcd<R1::num, R2::num>;
using gcd_den = static_gcd<R1::den, R2::den>;
using type = std::ratio<gcd_num::value, (R1::den / gcd_den::value) * R2::den>;
};
template<Ratio R1, Ratio R2>
template<typename R1, typename R2>
using common_ratio_t = typename common_ratio<R1, R2>::type;
} // namespace units

View File

@@ -20,29 +20,32 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "units/bits/ratio_tools.h"
#include "units/ratio.h"
namespace {
using namespace units;
// static_sign
static_assert(static_sign<2>::value == 1);
static_assert(static_sign<-3>::value == -1);
static_assert(static_sign<0>::value == 1);
template<Ratio R1, Ratio R2>
inline constexpr bool same = R1::num == R2::num && R1::den == R2::den;
// static_abs
static_assert(same<ratio<2, 4>, ratio<1, 2>>);
static_assert(static_abs<2>::value == 2);
static_assert(static_abs<-3>::value == 3);
static_assert(static_abs<0>::value == 0);
static_assert(std::is_same_v<ratio_multiply<ratio<4>, ratio<1, 8>>, ratio<1, 2>>);
static_assert(std::is_same_v<ratio_multiply<ratio<4>, ratio<1, 2>>, ratio<2>>);
static_assert(std::is_same_v<ratio_multiply<ratio<1, 8>, ratio<2>>, ratio<1, 4>>);
static_assert(std::is_same_v<ratio_multiply<ratio<1, 2>, ratio<8>>, ratio<4>>);
static_assert(std::is_same_v<ratio_divide<ratio<4>, ratio<2>>, ratio<2>>);
static_assert(std::is_same_v<ratio_divide<ratio<2>, ratio<8>>, ratio<1, 4>>);
static_assert(std::is_same_v<ratio_divide<ratio<1, 8>, ratio<2>>, ratio<1, 16>>);
static_assert(std::is_same_v<ratio_divide<ratio<6>, ratio<3>>, ratio<2>>);
// common_ratio
static_assert(std::is_same_v<common_ratio<std::ratio<1>, std::kilo>, std::ratio<1>>);
static_assert(std::is_same_v<common_ratio<std::kilo, std::ratio<1>>, std::ratio<1>>);
static_assert(std::is_same_v<common_ratio<std::ratio<1>, std::milli>, std::milli>);
static_assert(std::is_same_v<common_ratio<std::milli, std::ratio<1>>, std::milli>);
static_assert(std::is_same_v<common_ratio<ratio<1>, ratio<1000>>, ratio<1>>);
static_assert(std::is_same_v<common_ratio<ratio<1000>, ratio<1>>, ratio<1>>);
static_assert(std::is_same_v<common_ratio<ratio<1>, ratio<1, 1000>>, ratio<1, 1000>>);
static_assert(std::is_same_v<common_ratio<ratio<1, 1000>, ratio<1>>, ratio<1, 1000>>);
} // namespace

View File

@@ -74,7 +74,7 @@ namespace {
// velocity
static_assert(std::is_same_v<decltype(1_km / 1_s), velocity<unit<dimension_velocity, std::ratio<1000, 1>>, std::int64_t>>);
static_assert(std::is_same_v<decltype(1_km / 1_s), velocity<unit<dimension_velocity, ratio<1000, 1>>, std::int64_t>>);
static_assert(10_m / 5_s == 2_mps);
static_assert(10 / 5_s * 1_m == 2_mps);