Line data Source code
1 : /** @file Quantity.hpp
2 : *
3 : * Author: Roland Conybeare
4 : **/
5 :
6 : #pragma once
7 :
8 : #include "quantity2_concept.hpp"
9 : #include "scaled_unit.hpp"
10 : #include "natural_unit.hpp"
11 :
12 : namespace xo {
13 : namespace qty {
14 : /** @class quantity
15 : * @brief represent a scalar quantity with attached units. enforce dimensional consistency.
16 : *
17 : * Constexpr implementation, but units are explicitly represented:
18 : * sizeof(Quantity2) > sizeof(Repr)
19 : *
20 : * Explicit unit representation allows introducing units at runtime,
21 : * for example in python bindings
22 : *
23 : * Require:
24 : * - Repr supports numeric operations (+, -, *, /)
25 : * - Repr supports conversion from double.
26 : **/
27 : template <typename Repr = double,
28 : typename Int = std::int64_t>
29 : class Quantity {
30 : public:
31 : using repr_type = Repr;
32 : using unit_type = natural_unit<Int>;
33 : using ratio_int_type = Int;
34 :
35 : public:
36 22 : constexpr Quantity(Repr scale,
37 : const natural_unit<Int> & unit)
38 22 : : scale_{scale}, unit_{unit} {}
39 :
40 22 : constexpr const repr_type & scale() const { return scale_; }
41 11 : constexpr const unit_type & unit() const { return unit_; }
42 :
43 : constexpr bool is_dimensionless() const { return unit_.is_dimensionless(); }
44 :
45 : constexpr Quantity unit_qty() const { return Quantity(1, unit_); }
46 : constexpr Quantity reciprocal() const { return Quantity(1.0 / scale_, unit_.reciprocal()); }
47 :
48 : constexpr
49 12 : auto rescale(const natural_unit<Int> & unit2) const {
50 : /* conversion factor from .unit -> unit2*/
51 12 : auto rr = detail::nu_ratio(this->unit_, unit2);
52 :
53 12 : if (rr.natural_unit_.is_dimensionless()) {
54 12 : repr_type r_scale = (::sqrt(rr.outer_scale_sq_)
55 12 : * rr.outer_scale_exact_.template to<repr_type>()
56 12 : * this->scale_);
57 12 : return Quantity(r_scale, unit2);
58 : } else {
59 0 : return Quantity(std::numeric_limits<repr_type>::quiet_NaN(), unit2);
60 : }
61 : }
62 :
63 : template <typename Dimensionless>
64 : requires std::is_arithmetic_v<Dimensionless>
65 : constexpr auto scale_by(Dimensionless x) const {
66 : return Quantity(x * this->scale_, this->unit_);
67 : }
68 :
69 : template <typename Dimensionless>
70 : requires std::is_arithmetic_v<Dimensionless>
71 : constexpr auto divide_by(Dimensionless x) const {
72 : return Quantity(this->scale_ / x, this->unit_);
73 : }
74 :
75 : template <typename Dimensionless>
76 : requires std::is_arithmetic_v<Dimensionless>
77 : constexpr auto divide_into(Dimensionless x) const {
78 : return Quantity(x / this->scale_, this->unit_.reciprocal());
79 : }
80 :
81 : template <typename Quantity2>
82 : static constexpr
83 3 : auto multiply(const Quantity & x, const Quantity2 & y) {
84 : using r_repr_type = std::common_type_t<typename Quantity::repr_type,
85 : typename Quantity2::repr_type>;
86 : using r_int_type = std::common_type_t<typename Quantity::ratio_int_type,
87 : typename Quantity2::ratio_int_type>;
88 :
89 3 : auto rr = detail::nu_product(x.unit(), y.unit());
90 :
91 3 : r_repr_type r_scale = (::sqrt(rr.outer_scale_sq_)
92 3 : * rr.outer_scale_exact_.template to<r_repr_type>()
93 3 : * static_cast<r_repr_type>(x.scale())
94 3 : * static_cast<r_repr_type>(y.scale()));
95 :
96 : return Quantity<r_repr_type, r_int_type>(r_scale,
97 3 : rr.natural_unit_);
98 : }
99 :
100 : template <typename Quantity2>
101 : static constexpr
102 3 : auto divide(const Quantity & x, const Quantity2 & y) {
103 : using r_repr_type = std::common_type_t<typename Quantity::repr_type,
104 : typename Quantity2::repr_type>;
105 : using r_int_type = std::common_type_t<typename Quantity::ratio_int_type,
106 : typename Quantity2::ratio_int_type>;
107 :
108 3 : auto rr = detail::nu_ratio(x.unit(), y.unit());
109 :
110 : /* note: nu_ratio() reports multiplicative outer scaling factors,
111 : * so multiply is correct here
112 : */
113 3 : r_repr_type r_scale = (::sqrt(rr.outer_scale_sq_)
114 3 : * rr.outer_scale_exact_.template to<r_repr_type>()
115 3 : * static_cast<r_repr_type>(x.scale())
116 3 : / static_cast<r_repr_type>(y.scale()));
117 :
118 : return Quantity<r_repr_type, r_int_type>(r_scale,
119 3 : rr.natural_unit_);
120 : }
121 :
122 : template <typename Quantity2>
123 : static constexpr
124 2 : auto add(const Quantity & x, const Quantity2 & y) {
125 : using r_repr_type = std::common_type_t<typename Quantity::repr_type,
126 : typename Quantity2::repr_type>;
127 : using r_int_type = std::common_type_t<typename Quantity::ratio_int_type,
128 : typename Quantity2::ratio_int_type>;
129 :
130 : /* conversion to get y in same units as x: multiply by y/x */
131 2 : auto rr = detail::nu_ratio(y.unit(), x.unit());
132 :
133 2 : if (rr.natural_unit_.is_dimensionless()) {
134 2 : r_repr_type r_scale = (static_cast<r_repr_type>(x.scale())
135 2 : + (::sqrt(rr.outer_scale_sq_)
136 2 : * rr.outer_scale_exact_.template to<r_repr_type>()
137 2 : * static_cast<r_repr_type>(y.scale())));
138 :
139 2 : return Quantity<r_repr_type, r_int_type>(r_scale, x.unit_.template to_repr<r_int_type>());
140 : } else {
141 : /* units don't match! */
142 : return Quantity<r_repr_type, r_int_type>(std::numeric_limits<Repr>::quiet_NaN(),
143 0 : x.unit_.template to_repr<r_int_type>());
144 : }
145 : }
146 :
147 : template <typename Quantity2>
148 : static constexpr
149 2 : auto subtract(const Quantity & x, const Quantity2 & y) {
150 : using r_repr_type = std::common_type_t<typename Quantity::repr_type,
151 : typename Quantity2::repr_type>;
152 : using r_int_type = std::common_type_t<typename Quantity::ratio_int_type,
153 : typename Quantity2::ratio_int_type>;
154 :
155 : /* conversion to get y in same units as x: multiply by y/x */
156 2 : auto rr = detail::nu_ratio(y.unit(), x.unit());
157 :
158 2 : if (rr.natural_unit_.is_dimensionless()) {
159 2 : r_repr_type r_scale = (static_cast<r_repr_type>(x.scale())
160 2 : - (::sqrt(rr.outer_scale_sq_)
161 2 : * rr.outer_scale_exact_.template to<r_repr_type>()
162 2 : * static_cast<r_repr_type>(y.scale())));
163 :
164 2 : return Quantity<r_repr_type, r_int_type>(r_scale, x.unit_.template to_repr<r_int_type>());
165 : } else {
166 : /* units don't match! */
167 : return Quantity<r_repr_type, r_int_type>(std::numeric_limits<Repr>::quiet_NaN(),
168 0 : x.unit_.template to_repr<r_int_type>());
169 : }
170 : }
171 :
172 : template <typename Quantity2>
173 : static constexpr
174 12 : auto compare(const Quantity & x, const Quantity2 & y) {
175 12 : Quantity y2 = y.rescale(x.unit_);
176 :
177 12 : return x.scale() <=> y2.scale();
178 : }
179 :
180 : private:
181 : /** @brief quantity represents this multiple of a unit amount **/
182 : Repr scale_ = Repr{};
183 : /** @brief unit for this quantity **/
184 : natural_unit<Int> unit_;
185 : }; /*Quantity2*/
186 :
187 : /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
188 : **/
189 : template <typename Repr = double,
190 : typename Int = std::int64_t>
191 : inline constexpr Quantity<Repr, Int>
192 : unit_qty(const scaled_unit<Int> & u) {
193 : return Quantity<Repr, Int>
194 : (u.outer_scale_exact_.template to<double>() * ::sqrt(u.outer_scale_sq_),
195 : u.natural_unit_);
196 : }
197 :
198 : /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
199 : **/
200 : template <typename Repr = double,
201 : typename Int = std::int64_t>
202 : inline constexpr Quantity<Repr, Int>
203 : natural_unit_qty(const natural_unit<Int> & nu) {
204 : return Quantity<Repr, Int>(1.0, nu);
205 : }
206 :
207 : /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
208 : **/
209 : template <typename Quantity, typename Quantity2>
210 : requires quantity2_concept<Quantity> && quantity2_concept<Quantity2>
211 : constexpr auto
212 3 : operator* (const Quantity & x, const Quantity2 & y)
213 : {
214 3 : return Quantity::multiply(x, y);
215 : }
216 :
217 : /** note: does not require unit scaling, so constexpr with c++23 **/
218 : template <typename Dimensionless, typename Quantity>
219 : requires std::is_arithmetic_v<Dimensionless> && quantity2_concept<Quantity>
220 : constexpr auto
221 : operator* (Dimensionless x, const Quantity & y)
222 : {
223 : return y.scale_by(x);
224 : }
225 :
226 : /** note: does not require unit scaling, so constexpr with c++23 **/
227 : template <typename Dimensionless, typename Quantity>
228 : requires std::is_arithmetic_v<Dimensionless> && quantity2_concept<Quantity>
229 : constexpr auto
230 : operator* (const Quantity & x, Dimensionless y)
231 : {
232 : return x.scale_by(y);
233 : }
234 :
235 : /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
236 : **/
237 : template <typename Quantity, typename Quantity2>
238 : requires quantity2_concept<Quantity> && quantity2_concept<Quantity2>
239 : constexpr auto
240 3 : operator/ (const Quantity & x, const Quantity2 & y)
241 : {
242 3 : return Quantity::divide(x, y);
243 : }
244 :
245 : /** note: doesn not require unit scaling, so constexpr with c++23 **/
246 : template <typename Quantity, typename Dimensionless>
247 : requires quantity2_concept<Quantity> && std::is_arithmetic_v<Dimensionless>
248 : constexpr auto
249 : operator/ (const Quantity & x, Dimensionless y)
250 : {
251 : return x.divide_by(y);
252 : }
253 :
254 : /** note: doesn not require unit scaling, so constexpr with c++23 **/
255 : template <typename Dimensionless, typename Quantity>
256 : requires std::is_arithmetic_v<Dimensionless> && quantity2_concept<Quantity>
257 : constexpr auto
258 : operator/ (Dimensionless x, const Quantity & y)
259 : {
260 : return y.divide_into(x);
261 : }
262 :
263 : /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
264 : **/
265 : template <typename Quantity, typename Quantity2>
266 : requires quantity2_concept<Quantity> && quantity2_concept<Quantity2>
267 : constexpr auto
268 2 : operator+ (const Quantity & x, const Quantity2 & y)
269 : {
270 2 : return Quantity::add(x, y);
271 : }
272 :
273 : /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
274 : **/
275 : template <typename Quantity, typename Quantity2>
276 : requires quantity2_concept<Quantity> && quantity2_concept<Quantity2>
277 : constexpr auto
278 2 : operator- (const Quantity & x, const Quantity2 & y)
279 : {
280 2 : return Quantity::subtract(x, y);
281 : }
282 :
283 : /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
284 : **/
285 : template <typename Quantity, typename Quantity2>
286 : requires quantity2_concept<Quantity> && quantity2_concept<Quantity2>
287 : constexpr auto
288 4 : operator== (const Quantity & x, const Quantity2 & y)
289 : {
290 4 : return (Quantity::compare(x, y) == 0);
291 : }
292 :
293 : /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
294 : **/
295 : template <typename Quantity, typename Quantity2>
296 : requires quantity2_concept<Quantity> && quantity2_concept<Quantity2>
297 : constexpr auto
298 8 : operator<=> (const Quantity & x, const Quantity2 & y)
299 : {
300 8 : return Quantity::compare(x, y);
301 : }
302 :
303 : namespace unit {
304 : constexpr auto nanogram = natural_unit_qty(nu2::nanogram);
305 : }
306 : } /*namespace qty*/
307 : } /*namespace xo*/
308 :
309 : /** end Quantity.hpp **/
|