/* @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 > 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 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 using find_bpu_t = unit_find_bpu_t; /** * For example: * auto q = qty::milliseconds(5) * qty::seconds(1); * then * q.basis_power -> 2 * q.basis_power -> 0 **/ template static constexpr PowerRepr c_basis_power = from_ratio::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.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::promote: x=" << x << ", R=" << reflect::Reflect::require()->canonical_name() << std::endl; return promoter::promote(x); } template 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); /* unit: may have non-unit scalefactor_type */ using unit_product_type = unit_cartesian_product; using exact_unit_type = unit_product_type::exact_unit_type; using norm_unit_type = normalize_unit_t; 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 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()->canonical_name())); log && log(xtag("exact_unit_type", Reflect::require()->canonical_name())); log && log(xtag("norm_unit_type", Reflect::require()->canonical_name())); log && log(xtag("exact_scalefactor_type", Reflect::require()->canonical_name())); log && log(xtag("c_scalefactor_inexact", c_scalefactor_inexact)); log && log(xtag("repr_type", Reflect::require()->canonical_name())); log && log(xtag("repr_type", Reflect::require()->canonical_name())); # endif return quantity::promote(r_scale); } template auto divide(Quantity2 y) const { using unit_divide_type = unit_divide; using exact_unit_type = unit_divide_type::exact_unit_type; using norm_unit_type = normalize_unit_t; 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 r_scale = ((scale() * c_scalefactor_inexact * unit_type::scalefactor_type::num) / (y.scale() * unit_type::scalefactor_type::den)); return quantity::promote(r_scale); } // quantity operator*=() // quantity operator/=() /** * scale by dimensionless number **/ template auto scale_by(Repr2 x) const { static_assert(!quantity_concept); using r_repr_type = std::common_type_t; r_repr_type r_scale = this->scale_ * x; //std::cerr << "quantity::scale_by: scale=" << scale << ", repr_type=" << reflect::Reflect::require()->canonical_name() << std::endl; return quantity::promote(r_scale); } /** * divide by dimensionless number **/ template auto divide_by(Repr2 x) const { using r_repr_type = std::common_type_t; r_repr_type r_scale = this->scale_ / x; return quantity::promote(r_scale); } /** * divide dimensionless number by this quantity **/ template auto divide_into(Repr2 x) const { using r_unit_type = unit_invert_t; using r_repr_type = std::common_type_t; r_repr_type r_scale = ((x * r_unit_type::scalefactor_type::num) / (this->scale_ * r_unit_type::scalefactor_type::den)); return quantity::promote(r_scale); } template Repr2 in_units_of() const { // static_assert(dimension_of == dimension_of); // discard all the scaling values static_assert(same_dimension_v); using _convert_to_u2_type = unit_cartesian_product>; 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 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 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 operator Quantity2 () const { /* avoid truncating precision when converting: * use best available representation */ using tmp_repr_type = std::common_type_t; return Quantity2::promote(this->in_units_of()); } 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; friend class promoter; 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 struct promoter { static constexpr Repr promote(Repr x) { return x; }; }; template struct promoter { static constexpr quantity promote(Repr x) { return quantity(x); } }; // ----- operator+ ----- template inline Quantity1 operator+ (Quantity1 x, Quantity2 y) { static_assert(same_dimension_v); /* 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 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 inline Quantity operator- (Quantity x) { return Quantity::promote(- x.scale()); } template inline auto operator* (Quantity1 x, Quantity2 y) { static_assert(quantity_concept); static_assert(quantity_concept); return x.multiply(y); } /** e.g. DECLARE_LH_MULT(int32_t) **/ # define DECLARE_LH_MULT(lhtype) \ template \ inline auto \ operator* (lhtype x, Quantity y) { \ static_assert(quantity_concept); \ 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 \ inline auto \ operator* (Quantity x, rhtype y) { \ static_assert(quantity_concept); \ 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 inline auto operator/ (Quantity1 x, Quantity2 y) { static_assert(quantity_concept); static_assert(quantity_concept); return x.divide(y); } # define DECLARE_LH_DIV(lhtype) \ template \ inline auto \ operator/ (lhtype x, Quantity y) { \ static_assert(quantity_concept); \ 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 \ inline auto \ operator/ (Quantity x, rhtype y) { \ static_assert(quantity_concept); \ 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 inline std::ostream & operator<< (std::ostream & os, quantity const & x) { x.display(os); return os; } namespace qty { // ----- mass ----- template inline auto milligrams(Repr x) -> quantity { return quantity::promote(x); }; template inline auto grams(Repr x) -> quantity { return quantity::promote(x); }; template inline auto kilograms(Repr x) -> quantity { return quantity::promote(x); }; // ----- distance ----- template inline auto millimeters(Repr x) -> quantity { return quantity::promote(x); } template inline auto meters(Repr x) -> quantity { return quantity::promote(x); } template inline auto kilometers(Repr x) -> quantity { return quantity::promote(x); } // ----- time ----- template inline auto nanoseconds(Repr x) -> quantity { return quantity::promote(x); } template inline auto microseconds(Repr x) -> quantity { return quantity::promote(x); } template inline auto milliseconds(Repr x) -> quantity { return quantity::promote(x); } template inline auto seconds(Repr x) -> quantity { return quantity::promote(x); } template inline auto minutes(Repr x) -> quantity { return quantity::promote(x); } template inline auto hours(Repr x) -> quantity { return quantity::promote(x); } template inline auto days(Repr x) -> quantity { return quantity::promote(x); } // ----- time/volatility ----- /** quantity in units of 1/sqrt(1dy) **/ template inline auto volatility1d(Repr x) -> quantity { return quantity::promote(x); } /** quantity in units of 1/sqrt(30days) **/ template inline auto volatility30d(Repr x) -> quantity { return quantity::promote(x); } /** quantity in units of 1/sqrt(250days) **/ template inline auto volatility250d(Repr x) -> quantity { return quantity::promote(x); } } /*namespace qty*/ } /*namespace unit*/ } /*namespace xo*/ /* end quantity.hpp */