xo-expression2/include/xo/unit/quantity.hpp

519 lines
20 KiB
C++

/* @file quantity.hpp */
#pragma once
#include "quantity_concept.hpp"
#include "unit.hpp"
//#include "xo/reflect/Reflect.hpp"
//#include "xo/indentlog/scope.hpp"
namespace xo {
namespace unit {
/** @class promoter
*
* Aux class assister for quantity::promote()
**/
template <typename Unit, typename Repr, bool Dimensionless = dimensionless_v<Unit> >
struct promoter;
// ----- quantity -----
/** @class quantity
*
* @brief represets a scalar quantity; enforces dimensional consistency at compile time
*
* Repr representation.
* Unit unit
* Assert use to specify required unit dimension
*
* Require:
* - Repr copyable, assignable
* - Repr = 0
* - Repr = 1
* - Repr + Repr -> Repr
* - Repr - Repr -> Repr
* - Repr * Repr -> Repr
* - Repr / Repr -> Repr
**/
template <typename Unit, typename Repr = double>
class quantity {
public:
using unit_type = Unit;
using repr_type = Repr;
/* non-unity compile-time scale factors can arise during unit conversion;
* for example see method quantity::in_units_of()
*/
static_assert(std::same_as< typename Unit::scalefactor_type, std::ratio<1> >);
static_assert(std::same_as< typename Unit::canon_type, typename Unit::canon_type >);
public:
constexpr quantity() = default;
constexpr quantity(quantity const & x) = default;
constexpr quantity(quantity && x) = default;
template <dim BasisDim>
using find_bpu_t = unit_find_bpu_t<unit_type, BasisDim>;
/**
* For example:
* auto q = qty::milliseconds(5) * qty::seconds(1);
* then
* q.basis_power<dim::time> -> 2
* q.basis_power<dim::mass> -> 0
**/
template <dim BasisDim, typename PowerRepr = int>
static constexpr PowerRepr basis_power = from_ratio<PowerRepr, typename find_bpu_t<BasisDim>::power_type>();
/** @brief get scale value (relative to unit) (@ref scale_) **/
constexpr Repr scale() const { return scale_; }
/** @brief abbreviation for this quantity's units **/
static constexpr char const * unit_cstr() { return unit_abbrev_v<unit_type>.c_str(); }
/** @brief return unit quantity -- amount with this Unit that has representation = 1 **/
static constexpr quantity unit_quantity() { return quantity(1); }
/** @brief promote representation to quantity. Same as multiplying by Unit **/
static constexpr auto promote(Repr x) {
//std::cerr << "quantity<U,R>::promote: x=" << x << ", R=" << reflect::Reflect::require<Repr>()->canonical_name() << std::endl;
return promoter<Unit, Repr>::promote(x);
}
template <typename Unit2>
constexpr quantity<Unit2, Repr> with_unit() const { return *this; }
template <typename Repr2>
constexpr quantity<unit_type, Repr2> with_repr() const { return quantity<unit_type, Repr2>::promote(scale_); }
template <typename Quantity2>
auto multiply(Quantity2 y) const {
//constexpr bool c_debug_flag = false;
//using Reflect = xo::reflect::Reflect;
//scope log(XO_DEBUG(c_debug_flag));
static_assert(quantity_concept<Quantity2>);
/* unit: may have non-unit scalefactor_type */
using unit_product_type = unit_cartesian_product<Unit, typename Quantity2::unit_type>;
using exact_unit_type = unit_product_type::exact_unit_type;
using norm_unit_type = normalize_unit_t<exact_unit_type>;
using exact_scalefactor_type = exact_unit_type::scalefactor_type;
constexpr double c_scalefactor_inexact = unit_product_type::c_scalefactor_inexact;
using repr_type = std::common_type_t<repr_type, typename Quantity2::repr_type>;
repr_type r_scale = ((scale() * y.scale() * c_scalefactor_inexact * exact_scalefactor_type::num)
/ exact_scalefactor_type::den);
# ifdef NOT_USING_DEBUG
log && log(xtag("unit_product_type", Reflect::require<unit_product_type>()->canonical_name()));
log && log(xtag("exact_unit_type", Reflect::require<exact_unit_type>()->canonical_name()));
log && log(xtag("norm_unit_type", Reflect::require<norm_unit_type>()->canonical_name()));
log && log(xtag("exact_scalefactor_type", Reflect::require<exact_scalefactor_type>()->canonical_name()));
log && log(xtag("c_scalefactor_inexact", c_scalefactor_inexact));
log && log(xtag("repr_type", Reflect::require<repr_type>()->canonical_name()));
log && log(xtag("repr_type", Reflect::require<repr_type>()->canonical_name()));
# endif
return quantity<norm_unit_type, repr_type>::promote(r_scale);
}
template <typename Quantity2>
auto divide(Quantity2 y) const {
using unit_divide_type = unit_divide<Unit, typename Quantity2::unit_type>;
using exact_unit_type = unit_divide_type::exact_unit_type;
using norm_unit_type = normalize_unit_t<exact_unit_type>;
using exact_scalefactor_type = exact_unit_type::scalefactor_type;
constexpr double c_scalefactor_inexact = unit_divide_type::c_scalefactor_inexact;
using repr_type = std::common_type_t<repr_type, typename Quantity2::repr_type>;
repr_type r_scale = ((scale() * c_scalefactor_inexact * exact_scalefactor_type::num)
/ (y.scale() * exact_scalefactor_type::den));
# ifdef NOT_USING_DEBUG
using xo::reflect::Reflect;
scope log(XO_DEBUG(true /*c_debug_flag*/));
log && log(xtag("unit_divide_type", Reflect::require<unit_divide_type>()->canonical_name()));
log && log(xtag("exact_unit_type", Reflect::require<exact_unit_type>()->canonical_name()));
log && log(xtag("norm_unit_type", Reflect::require<norm_unit_type>()->canonical_name()));
log && log(xtag("exact_scalefactor_type", Reflect::require<exact_scalefactor_type>()->canonical_name()));
log && log(xtag("c_scalefactor_inexact", c_scalefactor_inexact));
log && log(xtag("r_scale", r_scale));
log && log(xtag("repr_type", Reflect::require<repr_type>()->canonical_name()));
# endif
return quantity<norm_unit_type, repr_type>::promote(r_scale);
}
// quantity operator*=()
// quantity operator/=()
/**
* scale by dimensionless number
**/
template <typename Repr2>
auto scale_by(Repr2 x) const {
static_assert(!quantity_concept<Repr2>);
using r_repr_type = std::common_type_t<repr_type, Repr2>;
r_repr_type r_scale = this->scale_ * x;
//std::cerr << "quantity::scale_by: scale=" << scale << ", repr_type=" << reflect::Reflect::require<repr_type>()->canonical_name() << std::endl;
return quantity<unit_type, r_repr_type>::promote(r_scale);
}
/**
* divide by dimensionless number
**/
template <typename Repr2>
auto divide_by(Repr2 x) const {
using r_repr_type = std::common_type_t<repr_type, Repr2>;
r_repr_type r_scale = this->scale_ / x;
return quantity<unit_type, r_repr_type>::promote(r_scale);
}
/**
* divide dimensionless number by this quantity
**/
template <typename Repr2>
auto divide_into(Repr2 x) const {
using r_unit_type = unit_invert_t<Unit>;
using r_repr_type = std::common_type_t<repr_type, Repr2>;
r_repr_type r_scale = ((x * r_unit_type::scalefactor_type::num)
/ (this->scale_ * r_unit_type::scalefactor_type::den));
return quantity<r_unit_type, r_repr_type>::promote(r_scale);
}
template <typename Unit2, typename Repr2>
Repr2 in_units_of() const {
// static_assert(dimension_of<Unit> == dimension_of<Unit2>); // discard all the scaling values
static_assert(same_dimension_v<Unit, Unit2>);
using _convert_to_u2_type = unit_cartesian_product<Unit, unit_invert_t<Unit2>>;
using exact_scalefactor_type = _convert_to_u2_type::exact_unit_type::scalefactor_type;
constexpr double c_scalefactor_inexact = _convert_to_u2_type::c_scalefactor_inexact;
// _convert_u2_type
// - scalefactor_type
// - dim_type
// - canon_type
/* if _convert_u2_type isn't dimensionless, then {Unit2, Unit} have different dimensions */
return ((this->scale_ * c_scalefactor_inexact * exact_scalefactor_type::num) / exact_scalefactor_type::den);
}
template <typename Quantity2>
quantity operator+=(Quantity2 y) {
static_assert(std::same_as<
typename unit_type::canon_type,
typename Quantity2::unit_type::canon_type >);
/* relying on assignment that correctly converts-to-lhs-units */
quantity y2 = y;
this->scale_ += y2.scale();
return *this;
}
template <typename Quantity2>
quantity operator-=(Quantity2 y) {
static_assert(std::same_as<
typename unit_type::canon_type,
typename Quantity2::unit_type::canon_type >);
quantity y2 = y;
/* relying on assignment that correctly converts-to-lhs-units */
this->scale_ -= y2.scale();
return *this;
}
/* convert to quantity with same dimension, different {unit_type, repr_type} */
template <typename Quantity2>
operator Quantity2 () const {
/* avoid truncating precision when converting:
* use best available representation
*/
using tmp_repr_type = std::common_type_t<repr_type, typename Quantity2::repr_type>;
return Quantity2::promote(this->in_units_of<typename Quantity2::unit_type, tmp_repr_type>());
}
void display(std::ostream & os) const {
os << this->scale() << unit_cstr();
}
quantity & operator=(quantity const & x) = default;
quantity & operator=(quantity && x) = default;
private:
explicit constexpr quantity(Repr x) : scale_{x} {}
friend class promoter<Unit, Repr, true>;
friend class promoter<Unit, Repr, false>;
private:
/** @brief quantity represents this multiple of a unit (that has compile-time outer-scalefactor of 1) **/
Repr scale_ = 0;
}; /*quantity*/
// ----- promoter -----
/* collapse dimensionless quantity to its repr_type> */
template <typename Unit, typename Repr>
struct promoter<Unit, Repr, /*Dimensionless*/ true> {
static constexpr Repr promote(Repr x) { return x; };
};
template <typename Unit, typename Repr>
struct promoter<Unit, Repr, /*Dimensionless*/ false> {
static constexpr quantity<Unit, Repr> promote(Repr x) { return quantity<Unit, Repr>(x); }
};
// ----- operator+ -----
template <typename Quantity1, typename Quantity2>
inline Quantity1 operator+ (Quantity1 x, Quantity2 y) {
static_assert(same_dimension_v<typename Quantity1::unit_type, typename Quantity2::unit_type>);
/* convert y to match units used by x;
* would fail at compile time if this isn't well-defined
*/
Quantity1 y2 = y;
return Quantity1::promote(x.scale() + y2.scale());
}
template <typename Quantity1, typename Quantity2>
inline Quantity1 operator- (Quantity1 x, Quantity2 y) {
static_assert(std::same_as
< typename Quantity1::unit_type::dimension_type::canon_type,
typename Quantity2::unit_type::dimension_type::canon_type >);
/* convert y to match units used by x */
Quantity1 y2 = y;
return Quantity1::promote(x.scale() - y2.scale());
}
template <typename Quantity>
inline Quantity operator- (Quantity x) {
return Quantity::promote(- x.scale());
}
template <typename Quantity1, typename Quantity2>
inline auto operator* (Quantity1 x, Quantity2 y) {
static_assert(quantity_concept<Quantity1>);
static_assert(quantity_concept<Quantity2>);
return x.multiply(y);
}
/** e.g. DECLARE_LH_MULT(int32_t) **/
# define DECLARE_LH_MULT(lhtype) \
template <typename Quantity> \
inline auto \
operator* (lhtype x, Quantity y) { \
static_assert(quantity_concept<Quantity>); \
return y.scale_by(x); \
}
DECLARE_LH_MULT(int8_t);
DECLARE_LH_MULT(uint8_t);
DECLARE_LH_MULT(int16_t);
DECLARE_LH_MULT(uint16_t);
DECLARE_LH_MULT(int32_t);
DECLARE_LH_MULT(uint32_t);
DECLARE_LH_MULT(int64_t);
DECLARE_LH_MULT(uint64_t);
DECLARE_LH_MULT(float);
DECLARE_LH_MULT(double);
# undef DECLARE_LH_MULT
/** e.g. DECLARE_RH_MULT(int32_t) **/
# define DECLARE_RH_MULT(rhtype) \
template <typename Quantity> \
inline auto \
operator* (Quantity x, rhtype y) { \
static_assert(quantity_concept<Quantity>); \
return x.scale_by(y); \
}
DECLARE_RH_MULT(int8_t);
DECLARE_RH_MULT(uint8_t);
DECLARE_RH_MULT(int16_t);
DECLARE_RH_MULT(uint16_t);
DECLARE_RH_MULT(int32_t);
DECLARE_RH_MULT(uint32_t);
DECLARE_RH_MULT(int64_t);
DECLARE_RH_MULT(uint64_t);
DECLARE_RH_MULT(float);
DECLARE_RH_MULT(double);
# undef DECLARE_LH_MULT
template <typename Quantity1, typename Quantity2>
inline auto operator/ (Quantity1 x, Quantity2 y) {
static_assert(quantity_concept<Quantity1>);
static_assert(quantity_concept<Quantity2>);
return x.divide(y);
}
# define DECLARE_LH_DIV(lhtype) \
template <typename Quantity> \
inline auto \
operator/ (lhtype x, Quantity y) { \
static_assert(quantity_concept<Quantity>); \
return y.divide_into(x); \
}
DECLARE_LH_DIV(int8_t);
DECLARE_LH_DIV(uint8_t);
DECLARE_LH_DIV(int16_t);
DECLARE_LH_DIV(uint16_t);
DECLARE_LH_DIV(int32_t);
DECLARE_LH_DIV(uint32_t);
DECLARE_LH_DIV(int64_t);
DECLARE_LH_DIV(uint64_t);
DECLARE_LH_DIV(float);
DECLARE_LH_DIV(double);
# undef DECLARE_LH_DIV
# define DECLARE_RH_DIV(rhtype) \
template <typename Quantity> \
inline auto \
operator/ (Quantity x, rhtype y) { \
static_assert(quantity_concept<Quantity>); \
return x.divide_by(y); \
}
DECLARE_RH_DIV(int8_t)
DECLARE_RH_DIV(uint8_t)
DECLARE_RH_DIV(int16_t)
DECLARE_RH_DIV(uint16_t)
DECLARE_RH_DIV(int32_t)
DECLARE_RH_DIV(uint32_t)
DECLARE_RH_DIV(int64_t)
DECLARE_RH_DIV(uint64_t)
DECLARE_RH_DIV(float)
DECLARE_RH_DIV(double)
# undef DECLARE_RH_DIV
template <typename Unit, typename Repr>
inline std::ostream &
operator<< (std::ostream & os, quantity<Unit, Repr> const & x) {
x.display(os);
return os;
}
namespace qty {
// ----- mass -----
template <typename Repr = double>
inline auto milligrams(Repr x) -> quantity<units::milligram, Repr> {
return quantity<units::milligram, Repr>::promote(x);
};
template <typename Repr = double>
inline auto grams(Repr x) -> quantity<units::gram, Repr> {
return quantity<units::gram, Repr>::promote(x);
};
template <typename Repr = double>
inline auto kilograms(Repr x) -> quantity<units::kilogram, Repr> {
return quantity<units::kilogram, Repr>::promote(x);
};
// ----- distance -----
template <typename Repr = double>
inline auto millimeters(Repr x) -> quantity<units::millimeter, Repr> {
return quantity<units::millimeter, Repr>::promote(x);
}
template <typename Repr = double>
inline auto meters(Repr x) -> quantity<units::meter, Repr> {
return quantity<units::meter, Repr>::promote(x);
}
template <typename Repr = double>
inline auto kilometers(Repr x) -> quantity<units::kilometer, Repr> {
return quantity<units::kilometer, Repr>::promote(x);
}
// ----- time -----
template <typename Repr = double>
inline auto nanoseconds(Repr x) -> quantity<units::nanosecond, Repr> {
return quantity<units::nanosecond, Repr>::promote(x);
}
template <typename Repr = double>
inline auto microseconds(Repr x) -> quantity<units::microsecond, Repr> {
return quantity<units::microsecond, Repr>::promote(x);
}
template <typename Repr = double>
inline auto milliseconds(Repr x) -> quantity<units::millisecond, Repr> {
return quantity<units::millisecond, Repr>::promote(x);
}
template <typename Repr = double>
inline auto seconds(Repr x) -> quantity<units::second, Repr> {
return quantity<units::second, Repr>::promote(x);
}
template <typename Repr = double>
inline auto minutes(Repr x) -> quantity<units::minute, Repr> {
return quantity<units::minute, Repr>::promote(x);
}
template <typename Repr = double>
inline auto hours(Repr x) -> quantity<units::hour, Repr> {
return quantity<units::hour, Repr>::promote(x);
}
template <typename Repr = double>
inline auto days(Repr x) -> quantity<units::day, Repr> {
return quantity<units::day, Repr>::promote(x);
}
// ----- time/volatility -----
/** quantity in units of 1/sqrt(1dy) **/
template <typename Repr = double>
inline auto volatility1d(Repr x) -> quantity<units::volatility_1d, Repr> {
return quantity<units::volatility_1d, Repr>::promote(x);
}
/** quantity in units of 1/sqrt(30days)
**/
template <typename Repr = double>
inline auto volatility30d(Repr x) -> quantity<units::volatility_30d, Repr> {
return quantity<units::volatility_30d, Repr>::promote(x);
}
/** quantity in units of 1/sqrt(250days)
**/
template <typename Repr = double>
inline auto volatility250d(Repr x) -> quantity<units::volatility_250d, Repr> {
return quantity<units::volatility_250d, Repr>::promote(x);
}
} /*namespace qty*/
} /*namespace unit*/
} /*namespace xo*/
/* end quantity.hpp */