/** @file Quantity.hpp * * Author: Roland Conybeare **/ #pragma once #include "quantity2_concept.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 * * Require: * - Repr supports numeric operations (+, -, *, /) * - Repr supports conversion from double. **/ template class Quantity { public: using repr_type = Repr; using unit_type = natural_unit; using ratio_int_type = Int; public: constexpr Quantity(Repr scale, const natural_unit & unit) : scale_{scale}, unit_{unit} {} constexpr const repr_type & scale() const { return scale_; } constexpr const unit_type & unit() const { return unit_; } constexpr Quantity unit_qty() const { return Quantity(1, unit_); } constexpr Quantity reciprocal() const { return Quantity(1.0 / scale_, unit_.reciprocal()); } template static constexpr auto multiply(const Quantity & x, const Quantity2 & y) { using r_repr_type = std::common_type_t; using r_int_type = std::common_type_t; auto rr = detail::nu_product(x.unit(), y.unit()); r_repr_type r_scale = (::sqrt(rr.outer_scale_sq_) * rr.outer_scale_exact_.template to() * static_cast(x.scale()) * static_cast(y.scale())); return Quantity(r_scale, rr.natural_unit_); } template static constexpr auto divide(const Quantity & x, const Quantity2 & y) { using r_repr_type = std::common_type_t; using r_int_type = std::common_type_t; auto rr = detail::nu_ratio(x.unit(), y.unit()); /* note: nu_ratio() reports multiplicative outer scaling factors, * so multiply is correct here */ r_repr_type r_scale = (::sqrt(rr.outer_scale_sq_) * rr.outer_scale_exact_.template to() * static_cast(x.scale()) / static_cast(y.scale())); return Quantity(r_scale, rr.natural_unit_); } template static constexpr auto add(const Quantity & x, const Quantity2 & y) { using r_repr_type = std::common_type_t; using r_int_type = std::common_type_t; /* conversion to get y in same units as x: multiply by y/x */ auto rr = detail::nu_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_exact_.template to() * static_cast(y.scale()))); return Quantity(r_scale, x.unit_.template to_repr()); } else { /* units don't match! */ return Quantity(std::numeric_limits::quiet_NaN(), x.unit_.template to_repr()); } } template static constexpr auto subtract(const Quantity & x, const Quantity2 & y) { using r_repr_type = std::common_type_t; using r_int_type = std::common_type_t; /* conversion to get y in same units as x: multiply by y/x */ auto rr = detail::nu_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_exact_.template to() * static_cast(y.scale()))); return Quantity(r_scale, x.unit_.template to_repr()); } else { /* units don't match! */ return Quantity(std::numeric_limits::quiet_NaN(), x.unit_.template to_repr()); } } private: /** @brief quantity represents this multiple of a unit amount **/ Repr scale_ = Repr{}; /** @brief unit for this quantity **/ natural_unit unit_; }; /*Quantity2*/ /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template inline constexpr Quantity unit_qty(const scaled_unit & u) { return Quantity (u.outer_scale_exact_.template 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 Quantity natural_unit_qty(const natural_unit & nu) { return Quantity(1.0, nu); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity2_concept && quantity2_concept constexpr auto operator* (const Quantity & x, const Quantity2 & y) { return Quantity::multiply(x, y); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity2_concept && quantity2_concept constexpr auto operator/ (const Quantity & x, const Quantity2 & y) { return Quantity::divide(x, y); } /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) **/ template requires quantity2_concept && quantity2_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 quantity2_concept && quantity2_concept constexpr auto operator- (const Quantity & x, const Quantity2 & y) { return Quantity::subtract(x, y); } namespace unit { constexpr auto nanogram = natural_unit_qty(nu2::nanogram); } } /*namespace qty*/ } /*namespace xo*/ /** end Quantity.hpp **/