/** @file xquantity.hpp * * Author: Roland Conybeare **/ #pragma once #include "quantity_ops.hpp" #include "scaled_unit.hpp" #include "natural_unit.hpp" namespace xo { namespace qty { /** @class xquantity * @brief represent a scalar quantity with polymorphic units. * * - @p Repr type used represent a dimensionless multiple of a natural unit. * * Constexpr implementation, but units are explicitly represented: * @code * sizeof(xquantity) > sizeof(xquantity::repr_type) * @endcode * * Explicit unit representation allows introducing units at runtime, * for example in python bindings. * See for example xo-pyutil * * See @ref quantity for implementation with units established at compile time * * Require: * - Repr supports numeric operations (+, -, *, /) * - Repr supports conversion from double. **/ template class xquantity { public: /** @defgroup xquantity-type-traits xquantity type traits **/ ///@{ /** @brief runtime representation for this value's scale **/ using repr_type = Repr; /** @brief runtime representation for this value's unit **/ using unit_type = natural_unit; /** @brief type used for numerator and denominator in basis-unit scalefactor ratios **/ using ratio_int_type = Int; /** @brief double-width type for numerator and denominator of intermediate * scalefactor ratios. Use to mitigate loss of precision during computation * of conversion factors between units with widely-differing magnitude **/ using ratio_int2x_type = detail::width2x_t; ///@} public: /** @defgroup xquantity-ctors xquantity constructors **/ ///@{ /** create dimensionless, zero quantity **/ constexpr xquantity() : scale_{0}, unit_{natural_unit()} {} /** create quantity representing multiple of @p scale times @p unit **/ constexpr xquantity(Repr scale, const natural_unit & unit) : scale_{scale}, unit_{unit} {} /** create quantity representing multiple of @p scale times @p unit. * * Collects outer scalefactors (if any) from @p unit, * so for example: * * @code * using namespace xo::qty; * xquantity q(123, u::meter * u::millimeter); * * q.scale() --> 0.123 * @endcode **/ constexpr xquantity(Repr scale, const scaled_unit & unit) : scale_(scale * unit.outer_scale_factor_.template convert_to() * ((unit.outer_scale_sq_ == 1.0) ? 1.0 : ::sqrt(unit.outer_scale_sq_)) ), unit_{unit.natural_unit_} {} ///@} /** @defgroup xquantity-constants static xquantity constants **/ ///@{ /** false since unit information may be unknown at compile time. * Coordinates with @c quantity::always_constexpr_unit **/ static constexpr bool always_constexpr_unit = false; ///@} /** @defgroup xquantity-access-methods xquantity access methods **/ ///@{ /** get member @ref scale_ **/ constexpr const repr_type & scale() const { return scale_; } /** get member @ref unit_ **/ constexpr const unit_type & unit() const { return unit_; } /** true iff this quantity has no dimension **/ constexpr bool is_dimensionless() const { return unit_.is_dimensionless(); } /** return abbreviation for quantities with this unit **/ constexpr nu_abbrev_type abbrev() const { return unit_.abbrev(); } ///@} /** @defgroup xquantity-arithmetic-support**/ ///@{ /** create unit quantity with same unit as @c this **/ constexpr xquantity unit_qty() const { return xquantity(1, unit_); } /** create zero quantity with same unit as @c this **/ constexpr xquantity zero_qty() const { return xquantity(0, unit_); } /** create quantity representing reciprocal of @c this **/ constexpr xquantity reciprocal() const { return xquantity(1.0 / scale_, unit_.reciprocal()); } /** create quantity representing this value scaled by dimensionless mutliplier @p x **/ template requires std::is_arithmetic_v constexpr auto scale_by(Dimensionless x) const { return xquantity(x * this->scale_, this->unit_); } /** create quantity representing this value scaled by dimensionless multiplier @p 1/x **/ template requires std::is_arithmetic_v constexpr auto divide_by(Dimensionless x) const { return xquantity(this->scale_ / x, this->unit_); } /** create quantity representing dimensionless numerator @p x divided by this value **/ template requires std::is_arithmetic_v constexpr auto divide_into(Dimensionless x) const { return xquantity(x / this->scale_, this->unit_.reciprocal()); } /** multiply two @c xquantity values, or a mixed (@c xquantity, @c quantity) pair **/ template static constexpr auto multiply(const xquantity & x, const Quantity2 & y) { using r_repr_type = std::common_type_t; using r_int_type = std::common_type_t; using r_int2x_type = std::common_type_t; auto rr = detail::su_product(x.unit(), y.unit()); r_repr_type r_scale = (::sqrt(rr.outer_scale_sq_) * rr.outer_scale_factor_.template convert_to() * static_cast(x.scale()) * static_cast(y.scale())); return xquantity(r_scale, rr.natural_unit_); } /** compute quotient @p x / @p y, where @p x and @p y are xquantities **/ template static constexpr auto divide(const xquantity & x, const Quantity2 & y) { using r_repr_type = std::common_type_t; using r_int_type = std::common_type_t; using r_int2x_type = std::common_type_t; auto rr = detail::su_ratio(x.unit(), y.unit()); /* note: su_ratio() reports multiplicative outer scaling factors, * so multiply is correct here */ r_repr_type r_scale = (::sqrt(rr.outer_scale_sq_) * rr.outer_scale_factor_.template convert_to() * static_cast(x.scale()) / static_cast(y.scale())); return xquantity(r_scale, rr.natural_unit_); } /** compute sum @p x + @p y, where @p x and @p y are xquantities **/ template static constexpr auto add(const xquantity & x, const Quantity2 & y) { using r_repr_type = std::common_type_t; using r_int_type = std::common_type_t; using r_int2x_type = std::common_type_t; /* conversion to get y in same units as x: multiply by y/x */ auto rr = detail::su_ratio(y.unit(), x.unit()); if (rr.natural_unit_.is_dimensionless()) { r_repr_type r_scale = (static_cast(x.scale()) + (::sqrt(rr.outer_scale_sq_) * rr.outer_scale_factor_.template convert_to() * static_cast(y.scale()))); return xquantity(r_scale, x.unit_.template to_repr()); } else { /* units don't match! */ return xquantity(std::numeric_limits::quiet_NaN(), x.unit_.template to_repr()); } } /** compute difference @p x - @p y, where @p x and @p y are xquantities **/ template static constexpr auto subtract(const xquantity & x, const Quantity2 & y) { using r_repr_type = std::common_type_t; using r_int_type = std::common_type_t; using r_int2x_type = std::common_type_t; /* conversion to get y in same units as x: multiply by y/x */ auto rr = detail::su_ratio(y.unit(), x.unit()); if (rr.natural_unit_.is_dimensionless()) { r_repr_type r_scale = (static_cast(x.scale()) - (::sqrt(rr.outer_scale_sq_) * rr.outer_scale_factor_.template convert_to() * static_cast(y.scale()))); return xquantity(r_scale, x.unit_.template to_repr()); } else { /* units don't match! */ return xquantity(std::numeric_limits::quiet_NaN(), x.unit_.template to_repr()); } } ///@} /** @defgroup xquantity-unit-conversion **/ ///@{ /** create quantity representing the same value, but in units of @p unit2 **/ constexpr auto rescale(const natural_unit & unit2) const { /* conversion factor from .unit -> unit2*/ auto rr = detail::su_ratio(this->unit_, unit2); if (rr.natural_unit_.is_dimensionless()) { repr_type r_scale = (::sqrt(rr.outer_scale_sq_) * rr.outer_scale_factor_.template convert_to() * this->scale_); return xquantity(r_scale, unit2); } else { return xquantity(std::numeric_limits::quiet_NaN(), unit2); } } constexpr auto rescale_ext(const scaled_unit & unit2) const { /* conversion factor from .unit -> unit2*/ auto rr = detail::su_ratio(unit_, unit2.natural_unit_); if (rr.natural_unit_.is_dimensionless()) { /* NOTE: test for unit .outer_scale_sq values to get constexpr result with c++23 * and integer dimension powers. * * NOTE: we don't intend to support mixed-unit quantities. * If we change intention, will need to take into account * (s_scaled_unit.outer_scale_factor_, s_scaled_unit.outer_scale_sq_) */ repr_type r_scale = ((((rr.outer_scale_sq_ == 1.0) && (unit2.outer_scale_sq_ == 1.0)) ? 1.0 : ::sqrt(rr.outer_scale_sq_ / unit2.outer_scale_sq_)) * rr.outer_scale_factor_.template convert_to() * this->scale_ / unit2.outer_scale_factor_.template convert_to()); return xquantity(r_scale, unit2); } else { return xquantity(std::numeric_limits::quiet_NaN(), unit2); } } ///@} /** @defgroup xquantity-comparison-support xquantity comparison support methods **/ ///@{ /** perform 3-way comparison between @c xquantity values @p x and @p y **/ template static constexpr auto compare(const xquantity & x, const Quantity2 & y) { xquantity y2 = y.rescale(x.unit_); return x.scale() <=> y2.scale(); } ///@} /** @defgroup xquantity-operators xquantity operators **/ ///@{ /** add @p x in-place, converting units if necessary **/ template xquantity & operator+= (const Quantity2 & x) { *this = *this + x; return *this; } /** unary negation; preserves unit information **/ xquantity operator-() const { return xquantity(-scale_, unit_); } /** subtract @p x in-place, converting units if necessary **/ template xquantity & operator-= (const Quantity2 & x) { *this = *this - x; return *this; } /** multiply @p x in-place, converting units if necessary * * @note: unlike @c quantity::operator*=, may change dimension of lhs **/ template xquantity & operator*= (const Quantity2 & x) { *this = *this * x; return *this; } /** divide @p x in-place, converting units if necessary * * @note: unlike @c quantity::operator/=, may change dimension of lhs **/ template xquantity & operator/= (const Quantity2 & x) { *this = *this / x; return *this; } ///@} private: /** @defgroup xquantity-instance-vars **/ ///@{ /** quantity represents this multiple of a unit amount **/ Repr scale_ = Repr{}; /** unit for this quantity **/ natural_unit unit_; ///@} }; /*xquantity*/ /** note: won't have constexpr result until c++26 (when @c sqrt(), @c pow() are constexpr) **/ template inline constexpr xquantity unit_qty(const scaled_unit & u) { return xquantity (u.outer_scale_factor_.template convert_to() * ::sqrt(u.outer_scale_sq_), u.natural_unit_); } /** note: won't have constexpr result until c++26 (when @c sqrt(), @c pow() are constexpr) **/ template inline constexpr xquantity natural_unit_qty(const natural_unit & nu) { return xquantity(1.0, nu); } /** note: won't have constexpr result until c++26 (when @c sqrt(), @c pow() are constexpr) **/ template requires (quantity_concept && quantity_concept && (!Q1::always_constexpr_unit || !Q2::always_constexpr_unit)) constexpr auto operator* (const Q1 & x, const Q2 & y) { return Q1::multiply(x, y); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity_concept constexpr auto operator* (double x, const Quantity & y) { return y.scale_by(x); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity_concept constexpr auto operator* (const Quantity & x, double y) { return x.scale_by(y); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity_concept && quantity_concept constexpr auto operator/ (const Quantity & x, const Quantity2 & y) { return Quantity::divide(x, y); } /** note: doesn not require unit scaling, so constexpr with c++23 **/ template requires quantity_concept && std::is_arithmetic_v constexpr auto operator/ (const Quantity & x, Dimensionless y) { return x.divide_by(y); } /** note: doesn not require unit scaling, so constexpr with c++23 **/ template requires std::is_arithmetic_v && quantity_concept constexpr auto operator/ (Dimensionless x, const Quantity & y) { return y.divide_into(x); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity_concept && quantity_concept constexpr auto operator+ (const Quantity & x, const Quantity2 & y) { return Quantity::add(x, y); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity_concept constexpr auto operator+ (const Quantity & x, double y) { return x + Quantity(y, u::dimensionless); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity_concept constexpr auto operator+ (double x, const Quantity & y) { return Quantity(x, u::dimensionless) + y; } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires (quantity_concept && quantity_concept) constexpr auto operator- (const Quantity & x, const Quantity2 & y) { return Quantity::subtract(x, y); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity_concept constexpr auto operator- (const Quantity & x, double y) { return x - Quantity(y, u::dimensionless); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity_concept constexpr auto operator- (double x, const Quantity & y) { return Quantity(x, u::dimensionless) - y; } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity_concept constexpr auto operator== (const Quantity & x, double y) { return (x == Quantity(y, u::dimensionless)); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity_concept constexpr auto operator== (double x, const Quantity & y) { return (Quantity(x, u::dimensionless) == y); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity_concept constexpr auto operator<=> (const Quantity & x, double y) { return Quantity::compare(x, Quantity(y, u::dimensionless)); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity_concept constexpr auto operator<=> (double x, const Quantity & y) { return Quantity::compare(Quantity(x, u::dimensionless), y); } namespace xu { constexpr auto nanogram = xquantity(1.0, u::nanogram); } } /*namespace qty*/ } /*namespace xo*/ /** end xquantity.hpp **/