/* @file quantity.hpp */ #pragma once #include "quantity_concept.hpp" #include "unit.hpp" #include "detail/promoter.hpp" //#include "xo/reflect/Reflect.hpp" //#include "xo/indentlog/scope.hpp" namespace xo { namespace unit { // ----- quantity ----- /** @class quantity * * @brief represents a scalar quantity; enforces dimensional consistency at compile time. * * - @p Unit is a type identifying dimension and scale attaching to this quantity. * Unit must satisfy @c unit_concept * - @p Repr is a type used to represent quantity values, scaled by @p Unit. * Repr must satisfy @c numeric_concept * * A quantity's run-time state consists of exactly one @p Repr * instance: * @c sizeof(quantity) == sizeof(Repr) **/ template class quantity { public: /** @defgroup quantity-traits **/ ///@{ /** @brief type capturing the units (and dimension) of this quantity **/ using unit_type = Unit; /** @brief type used for representation of this quantity **/ using repr_type = Repr; ///@} static_assert(unit_concept); static_assert(numeric_concept); /* 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: /** @defgroup quantity-ctors constructors **/ ///@{ constexpr quantity() = default; constexpr quantity(quantity const & x) = default; constexpr quantity(quantity && x) = default; ///@} /** @defgroup quantity-named-ctors named constructors **/ ///@{ /** @brief construct a unit quantity using @c unit_type * * @code * auto q = qty::milliseconds(17) / qty::kilometers(23.0); * q::unit_quantity(); // 1ms.km^-1 * @endcode **/ static constexpr quantity unit_quantity() { return quantity(1); } /** @brief promote representation to quantity. Same as multiplying by Unit **/ static constexpr auto promote(Repr x); ///@} /** @addtogroup quantity-traits **/ ///@{ /** @brief report this quantity's basis-power-unit type for a given basis dimension * * Example: * @code * auto q = 1.0 / (qty::milliseconds(5) * qty::seconds(100.0)); * q.unit_cstr(); // "ms^-2" * * using tmp = q.find_bpu_t; * * tmp::c_native_dim; // dim::time * tmp::c_native_unit; // native_unit_id::second * tmp::scalefactor_type::num; // 1 * tmp::scalefactor_type::den; // 1000 * tmp::power_type::num; // -2 * tmp::pwoer_type::den; // 1 * @endcode **/ template using find_bpu_t = unit_find_bpu_t; /** @brief report this quantity's scalefactor type for given basis dimension **/ template using basis_scale_type = typename find_bpu_t::scalefactor_type::type; ///@} /** @defgroup quantity-access-methods **/ ///@{ /** @brief get scale value (relative to unit) (@ref scale_) **/ constexpr Repr scale() const { return scale_; } /** @brief abbreviation for this quantity's units * * This string literal is constructed at compile-time by concatenating * abbreviations for each basis-power-unit. * For implementation see: * * @c xo::unit::native_unit_abbrev_helper * (in xo/unit/basis_unit.hpp) for each native dimension * * @c xo::unit::scaled_native_unit_abbrev * (in xo/unit/basis_unit.hpp) last-resort handling for scaled native dimensions * * @c xo::unit::scaled_native_unit_abbrev * (in xo/unit/unit.hpp) specializations for scaled native dimensions **/ static constexpr char const * unit_cstr() { return unit_abbrev_v.c_str(); } ///@} /** @defgroup quantity-constants constants **/ ///@{ /** @brief report exponent of @p BasisDim in dimension of this quantity * * For example: * @code * auto q = qty::milliseconds(5) * qty::seconds(1); * int p1 = q.basis_power; // p1 == 2 * int p2 = q.basis_power; // p2 == 0 * @endcode **/ template static constexpr PowerRepr basis_power = from_ratio::power_type>(); ///@} /** @defgroup quantity-unit-conversion **/ ///@{ /** @brief convert to quantity representing the same amount, but changing units and perhaps representation. * * These two expressions are equivalent: * @code * q.with_unit(); * quantity(q); * @endcode * **/ template constexpr quantity with_unit() const { return *this; } /** * @brief produce quantity scaled according to @p BasisUnit2, representing the same value as @c *this. * * For example: * * @code{.cpp} * auto q1 = 1.0 / minutes(1) * kilograms(2.5); // q1 = 2.5kg.min^-1 * auto q2 = q1.with_basis_unit(); // q2 in kg.ms^-1 * @endcode * * Motivation is ability to chain rescaling to reach desired compound unit * * @code * auto q3 = q1.with_basis_unit() * .with_basis_unit(); // q3 in g.s^-1 * @endcode **/ template constexpr auto with_basis_unit() const { static_assert(basis_unit_concept); using new_bpu_type = BasisUnit2::dim_type::front_type; using old_bpulist_type = unit_type::dim_type; using new_bpulist_type = di_replace_basis_scale::type; using new_unit_type = wrap_unit, new_bpulist_type>; # ifdef NOT_USING_DEBUG using xo::reflect::Reflect; scope log(XO_DEBUG(true /*c_debug_flag*/)); log && log(xtag("old_unit_type", Reflect::require()->canonical_name())); log && log(xtag("new_unit_type", Reflect::require()->canonical_name())); # endif return this->with_unit(); } /** * @brief express this quantity in the same units as @p q * * @pre @c *this and @p q must have the same dimension * * @param q take units from @c q::unit_type, ignoring @c q.scale() * @return this amount, but expressed using the same units as @p q **/ template auto with_units_from(Quantity q) const { return this->with_units(); } /** * @brief express this quantity in units of @p Unit2. * * @p Unit2 specifies new units * @p Repr2 specifies representation * @return this amount, but expressed as a multiple of @p Unit2 **/ template auto with_units() const { Repr2 x = this->in_units_of(); return quantity::promote(x); } /** * @brief compute scale with respect to @p Unit2 * * @pre @c *this must have the same dimension as @p Unit2 * * @p Unit2 rescale in terms of this unit. * @p Repr2 compute scale in this representation * @return scale to use for @c quantity representing the same amount as @c *this. **/ 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); } /** * @brief convert to quantity with representation @p Repr2 * * @return a quantity representing the same amount as @c *this, but using representation @p Repr2 **/ template constexpr quantity with_repr() const { return quantity::promote(scale_); } ///@} /** @defgroup quantity-arithmeticsupport **/ ///@{ /** * @brief multiply this quantity *x* by another quantity *y*. * * Result will propagate dimension and units appropriately. * If *x* and *y* use conflicting scale factors for a dimension, * adopt scalefactor from *x*. * * note: result will be a dimensionless value (e.g. type @c double) * if units cancel. * * @pre @p Quantity2 must satisfy @c quantity_concept * * @param y multiply by this amount * @return x.multiply(y) returns amount representing x*y **/ 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); } /** * @brief multiply this quantity *x* by another quantity *y* * * Result will propagate dimension and units appropriately. * If *x* and *y* use conflicting scale factors for a dimension, * adopt scalefactor from *x*. * * note: result will be a dimensionless value (e.g. type @c double) * if units cancel. * * @pre @p Quantity2 must satisfy @c quantity_concept * * @param y divide by this amount * @return x.divide(y) returns amount representing x/y **/ 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 * 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()->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("r_scale", r_scale)); log && log(xtag("repr_type", Reflect::require()->canonical_name())); # endif return quantity::promote(r_scale); } // quantity operator*=() // quantity operator/=() /** * @brief scale this quantity *x* by dimensionless amount @p y * * @return quantity representing @c x*y **/ template auto scale_by(Repr2 y) const { static_assert(!quantity_concept); using r_repr_type = std::common_type_t; r_repr_type r_scale = this->scale_ * y; //std::cerr << "quantity::scale_by: scale=" << scale << ", repr_type=" << reflect::Reflect::require()->canonical_name() << std::endl; return quantity::promote(r_scale); } /** * @brief divide this quantity *x* by dimensionless amount @p y * * @return quantity representing @c x/y **/ 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); } /** * @brief divide dimensionless number @p x by this quantity @c y * * @return quantity representing @c x/y **/ 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); } ///@} /** @defgroup quantity-arithmetic **/ ///@{ /** @brief add quantity in-place * * @pre @p y must have the same dimension as @c *this. * * @param y quantity to add * @return this quantity after adding y **/ template quantity & operator+=(Quantity2 y) { static_assert(same_dimension_v); /* relying on assignment that correctly converts-to-lhs-units */ quantity y2 = y; this->scale_ += y2.scale(); return *this; } /** @brief subtract quantity in-place * * @pre @p y must have the same dimensions as @c *this * * @param y quantity to subtract * @return this quantity after subtracting y **/ template quantity & operator-=(Quantity2 y) { static_assert(same_dimension_v); quantity y2 = y; /* relying on assignment that correctly converts-to-lhs-units */ this->scale_ -= y2.scale(); return *this; } ///@} /** @addtogroup quantity-unit-conversion **/ ///@{ /** @brief convert to quantity with same dimension, different {unit_type, repr_type} * * @pre @c Quantity2 must have the same dimension as @c *this. **/ template constexpr 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()); } ///@} /** @defgroup quantity-print-support **/ ///@{ /** @brief write printed representation on stream * * @param os write on this output stream **/ void display(std::ostream & os) const { os << this->scale() << unit_cstr(); } ///@} /** @defgroup quantity-assignment **/ ///@{ /** @brief copy constructor **/ quantity & operator=(quantity const & x) = default; /** @brief move constructor **/ 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*/ template constexpr auto quantity::promote(Repr x) { //std::cerr << "quantity::promote: x=" << x << ", R=" << reflect::Reflect::require()->canonical_name() << std::endl; return promoter::promote(x); } // ----- operator+ ----- template inline constexpr 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 constexpr 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 constexpr 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 constexpr 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 constexpr 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 constexpr 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 constexpr 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 constexpr 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 ----- /** @brief create quantity representing @p x milligrams **/ template inline constexpr auto milligrams(Repr x) -> quantity { return quantity::promote(x); }; /** @brief create quantity representing @p x grams **/ template inline constexpr auto grams(Repr x) -> quantity { return quantity::promote(x); }; /** @brief create quantity representing @p x kilograms **/ template inline constexpr auto kilograms(Repr x) -> quantity { return quantity::promote(x); }; // ----- distance ----- /** @brief create quantity representing @p x millimeters **/ template inline constexpr auto millimeters(Repr x) -> quantity { return quantity::promote(x); } /** @brief create quantity representing @p x meters **/ template inline constexpr auto meters(Repr x) -> quantity { return quantity::promote(x); } /** @brief create quantity representing @p x kilometers **/ template inline constexpr auto kilometers(Repr x) -> quantity { return quantity::promote(x); } // ----- time ----- /** @brief create quantity representing @p x nanoseconds **/ template inline constexpr auto nanoseconds(Repr x) -> quantity { return quantity::promote(x); } /** @brief create quantity representing @p x microseconds **/ template inline constexpr auto microseconds(Repr x) -> quantity { return quantity::promote(x); } /** @brief create quantity representing @p x milliseconds **/ template inline constexpr auto milliseconds(Repr x) -> quantity { return quantity::promote(x); } /** @brief create quantity representing @p x seconds **/ template inline constexpr auto seconds(Repr x) -> quantity { return quantity::promote(x); } /** @brief create quantity representing @p x minutes **/ template inline constexpr auto minutes(Repr x) -> quantity { return quantity::promote(x); } /** @brief create quantity representing @p x hours **/ template inline constexpr auto hours(Repr x) -> quantity { return quantity::promote(x); } /** @brief create quantity representing @p x days (1 day = exactly 24 hours) **/ template inline constexpr auto days(Repr x) -> quantity { return quantity::promote(x); } // ----- time/volatility ----- /** quantity in units of 1/sqrt(1dy) **/ template inline constexpr auto volatility1d(Repr x) -> quantity { return quantity::promote(x); } /** quantity in units of 1/sqrt(30days) **/ template inline constexpr auto volatility30d(Repr x) -> quantity { return quantity::promote(x); } /** quantity in units of 1/sqrt(250days) **/ template inline constexpr auto volatility250d(Repr x) -> quantity { return quantity::promote(x); } } /*namespace qty*/ namespace unit_qty { /** @brief quantity with mass dimension, representing 1mg (1 milligram = 10^-3 grams) **/ static constexpr auto milligram = qty::milligrams(1.0); /** @brief quantity with mass dimension, representing 1g (1 gram) **/ static constexpr auto gram = qty::grams(1.0); /** @brief quantity with mass dimension, representing 1kg (1 kilogram = 1000 grams) **/ static constexpr auto kilogram = qty::kilograms(1.0); /** @brief quantity with length dimension representing 1mm (10^-3 meters) **/ static constexpr auto millimeter = qty::millimeters(1.0); /** @brief quantity with length dimension representing 1m (1 meter) **/ static constexpr auto meter = qty::meters(1.0); /** @brief quantity with length dimension representing 1km (1 kilometer = 1000 meters) **/ static constexpr auto kilometer = qty::kilometers(1.0); /** @brief quantity with time dimension representing 1ns (1 nanosecond = 10^-9 seconds) **/ static constexpr auto nanosecond = qty::microseconds(1); /** @brief quantity with time dimension representing 1us (1 microsecond = 10^-6 seconds) **/ static constexpr auto microsecond = qty::microseconds(1); /** @brief quantity with time dimension representing 1ms (1 milliseconds = 10^-3 seconds) **/ static constexpr auto millisecond = qty::milliseconds(1); /** @brief quantity with time dimension representing 1s (1 second) **/ static constexpr auto second = qty::seconds(1); /** @brief quantity with time dimension representing 1min (1 minute = 60 seconds) **/ static constexpr auto minute = qty::minutes(1); /** @brief quantity with time dimension representing 1hr (1 hour = 60 minutes) **/ static constexpr auto hour = qty::hours(1); /** @brief quantity with time dimension representing 1dy (1 day = 24 hours) **/ static constexpr auto day = qty::days(1); } } /*namespace unit*/ } /*namespace xo*/ /* end quantity.hpp */