/** @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 Quantity * @brief represent a scalar quantity with attached units. enforce dimensional consistency. * * Constexpr implementation, but units are explicitly represented: * sizeof(Quantity2) > sizeof(Repr) * * Explicit unit representation allows introducing units at runtime, * for example in python bindings * * See xo::qty::quantity<> for implementation with units established at compile time * * Require: * - Repr supports numeric operations (+, -, *, /) * - Repr supports conversion from double. **/ template > class xquantity { public: using repr_type = Repr; using unit_type = natural_unit; using ratio_int_type = Int; using ratio_int2x_type = Int2x; public: /* zero, dimensionless */ constexpr xquantity() : scale_{0}, unit_{natural_unit()} {} constexpr xquantity(Repr scale, const natural_unit & unit) : scale_{scale}, unit_{unit} {} static constexpr bool always_constexpr_unit = false; constexpr const repr_type & scale() const { return scale_; } constexpr const unit_type & unit() const { return unit_; } constexpr bool is_dimensionless() const { return unit_.is_dimensionless(); } constexpr xquantity unit_qty() const { return xquantity(1, unit_); } constexpr xquantity zero_qty() const { return xquantity(0, unit_); } constexpr xquantity reciprocal() const { return xquantity(1.0 / scale_, unit_.reciprocal()); } 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); } } template requires std::is_arithmetic_v constexpr auto scale_by(Dimensionless x) const { return xquantity(x * this->scale_, this->unit_); } template requires std::is_arithmetic_v constexpr auto divide_by(Dimensionless x) const { return xquantity(this->scale_ / x, this->unit_); } template requires std::is_arithmetic_v constexpr auto divide_into(Dimensionless x) const { return xquantity(x / this->scale_, this->unit_.reciprocal()); } 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_); } 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_); } 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()); } } 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()); } } template static constexpr auto compare(const xquantity & x, const Quantity2 & y) { xquantity y2 = y.rescale(x.unit_); return x.scale() <=> y2.scale(); } xquantity operator-() const { return xquantity(-scale_, unit_); } /* also works with Quantity2 = double, int, .. */ template xquantity & operator*= (const Quantity2 & x) { *this = *this * x; return *this; } /* also works with Quantity2 = double, int, .. */ template xquantity & operator/= (const Quantity2 & x) { *this = *this / x; return *this; } // TODO: operator+=, operator-= constexpr nu_abbrev_type abbrev() const { return unit_.abbrev(); } private: /** @brief quantity represents this multiple of a unit amount **/ Repr scale_ = Repr{}; /** @brief unit for this quantity **/ natural_unit unit_; }; /*xquantity*/ /** note: won't have constexpr result until c++26 (when ::sqrt(), ::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 ::sqrt(), ::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 ::sqrt(), ::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 && 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, nu::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, nu::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, nu::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, nu::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, nu::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, nu::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, nu::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, nu::dimensionless), y); } namespace unit { constexpr auto nanogram = natural_unit_qty(nu::nanogram); } } /*namespace qty*/ } /*namespace xo*/ /** end xquantity.hpp **/