1 /++
2 This module quantities that are checked at compile-time for
3 dimensional consistency.
4 
5 Copyright: Copyright 2013-2018, Nicolas Sicard
6 Authors: Nicolas Sicard
7 License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
8 Source: $(LINK https://github.com/biozic/quantities)
9 +/
10 module quantities.compiletime.quantity;
11 
12 import quantities.internal.dimensions;
13 import quantities.runtime.qvariant;
14 import std.format;
15 import std.math;
16 import std.traits : isNumeric, isIntegral;
17 
18 /++
19 A quantity checked at compile-time for dimensional consistency.
20 
21 Params:
22     N = the numeric type of the quantity.
23 
24 See_Also:
25     QVariant has the same public members and overloaded operators as Quantity.
26 +/
27 struct Quantity(N, alias unitSpec)
28 {
29     static assert(isNumeric!N);
30     static assert(isQVariant!(typeof(unitSpec)));
31     static assert(Quantity.sizeof == N.sizeof);
32 
33 private:
34     N _value;
35     alias unit = unitSpec;
36 
37     // Creates a new quantity with non-empty dimensions
38     static Quantity make(T)(T scalar)
39             if (isNumeric!T)
40     {
41         Quantity result;
42         result._value = scalar;
43         return result;
44     }
45 
46     mixin template checkDim(alias u)
47     {
48         static if (unit.dimensions != u.dimensions)
49             static assert(false,
50                     "Dimension error: %s is not consistent with %s".format(unit.dimensions,
51                         u.dimensions));
52     }
53 
54     mixin template checkDimensionless(alias u)
55     {
56         static assert(u.isDimensionless, "Dimension error: %s instead of no dimensions");
57     }
58 
59 package(quantities):
60     alias valueType = N;
61 
62     N rawValue() const
63     {
64         return _value;
65     }
66 
67 public:
68     /// Creates a new quantity from another one with the same dimensions
69     this(Q)(auto ref const Q qty)
70             if (isQuantity!Q)
71     {
72         mixin checkDim!(Q.unit);
73         _value = qty._value;
74     }
75 
76     /// Ditto
77     this(Q)(auto ref const Q qty)
78             if (isQVariant!Q)
79     {
80         import std.exception;
81 
82         enforce(unit.dimensions == qty.dimensions,
83                 new DimensionException("Incompatible dimensions", unit.dimensions, qty.dimensions));
84         _value = qty.rawValue;
85     }
86 
87     /// Creates a new dimensionless quantity from a number
88     this(T)(T scalar)
89             if (isNumeric!T && unit.isDimensionless)
90     {
91         _value = scalar;
92     }
93 
94     /// Returns the dimensions of the quantity
95     enum dimensions = unit.dimensions;
96 
97     /++
98     Implicitly convert a dimensionless value to the value type.
99     +/
100     static if (unit.isDimensionless)
101     {
102         N get() const
103         {
104             return _value;
105         }
106 
107         alias get this;
108     }
109 
110     /++
111     Gets the _value of this quantity when expressed in the given target unit.
112     +/
113     N value(Q)(auto ref const Q target) const 
114             if (isQuantity!Q)
115     {
116         mixin checkDim!(Q.unit);
117         return _value / target._value;
118     }
119 
120     /++
121     Test whether this quantity is dimensionless
122     +/
123     enum bool isDimensionless = unit.isDimensionless;
124 
125     /++
126     Tests wheter this quantity has the same dimensions as another one.
127     +/
128     bool isConsistentWith(Q)(auto ref const Q qty) const 
129             if (isQuantity!Q)
130     {
131         enum yesOrNo = unit.isConsistentWith(Q.unit);
132         return yesOrNo;
133     }
134 
135     /++
136     Cast a dimensionless quantity to a numeric type.
137 
138     The cast operation will throw DimensionException if the quantity is not
139     dimensionless.
140     +/
141     static if (unit.isDimensionless)
142     {
143         T opCast(T)() const 
144                 if (isNumeric!T)
145         {
146             return _value;
147         }
148     }
149 
150     // Assign from another quantity
151     /// Operator overloading
152     ref Quantity opAssign(Q)(auto ref const Q qty)
153             if (isQuantity!Q)
154     {
155         mixin checkDim!(Q.unit);
156         _value = qty._value;
157         return this;
158     }
159 
160     // Assign from a numeric value if this quantity is dimensionless
161     /// ditto
162     ref Quantity opAssign(T)(T scalar)
163             if (isNumeric!T)
164     {
165         mixin checkDimensionless!unit;
166         _value = scalar;
167         return this;
168     }
169 
170     // Unary + and -
171     /// ditto
172     Quantity opUnary(string op)() const 
173             if (op == "+" || op == "-")
174     {
175         return Quantity.make(mixin(op ~ "_value"));
176     }
177 
178     // Unary ++ and --
179     /// ditto
180     Quantity opUnary(string op)()
181             if (op == "++" || op == "--")
182     {
183         mixin(op ~ "_value;");
184         return this;
185     }
186 
187     // Add (or substract) two quantities if they share the same dimensions
188     /// ditto
189     Quantity opBinary(string op, Q)(auto ref const Q qty) const 
190             if (isQuantity!Q && (op == "+" || op == "-"))
191     {
192         mixin checkDim!(Q.unit);
193         return Quantity.make(mixin("_value" ~ op ~ "qty._value"));
194     }
195 
196     // Add (or substract) a dimensionless quantity and a number
197     /// ditto
198     Quantity opBinary(string op, T)(T scalar) const 
199             if (isNumeric!T && (op == "+" || op == "-"))
200     {
201         mixin checkDimensionless!unit;
202         return Quantity.make(mixin("_value" ~ op ~ "scalar"));
203     }
204 
205     /// ditto
206     Quantity opBinaryRight(string op, T)(T scalar) const 
207             if (isNumeric!T && (op == "+" || op == "-"))
208     {
209         mixin checkDimensionless!unit;
210         return Quantity.make(mixin("scalar" ~ op ~ "_value"));
211     }
212 
213     // Multiply or divide a quantity by a number
214     /// ditto
215     Quantity opBinary(string op, T)(T scalar) const 
216             if (isNumeric!T && (op == "*" || op == "/" || op == "%"))
217     {
218         return Quantity.make(mixin("_value" ~ op ~ "scalar"));
219     }
220 
221     /// ditto
222     Quantity opBinaryRight(string op, T)(T scalar) const 
223             if (isNumeric!T && op == "*")
224     {
225         return Quantity.make(mixin("scalar" ~ op ~ "_value"));
226     }
227 
228     /// ditto
229     auto opBinaryRight(string op, T)(T scalar) const 
230             if (isNumeric!T && (op == "/" || op == "%"))
231     {
232         alias RQ = Quantity!(N, 1 / unit);
233         return RQ.make(mixin("scalar" ~ op ~ "_value"));
234     }
235 
236     // Multiply or divide two quantities
237     /// ditto
238     auto opBinary(string op, Q)(auto ref const Q qty) const 
239             if (isQuantity!Q && (op == "*" || op == "/"))
240     {
241         alias RQ = Quantity!(N, mixin("unit" ~ op ~ "Q.unit"));
242         return RQ.make(mixin("(_value" ~ op ~ "qty._value)"));
243     }
244 
245     /// ditto
246     Quantity opBinary(string op, Q)(auto ref const Q qty) const 
247             if (isQuantity!Q && (op == "%"))
248     {
249         mixin checkDim!(Q.unit);
250         return Quantity.make(mixin("(_value" ~ op ~ "qty._value)"));
251     }
252 
253     // Add/sub assign with a quantity that shares the same dimensions
254     /// ditto
255     void opOpAssign(string op, Q)(auto ref const Q qty)
256             if (isQuantity!Q && (op == "+" || op == "-"))
257     {
258         mixin checkDim!(Q.unit);
259         mixin("_value " ~ op ~ "= qty._value;");
260     }
261 
262     // Add/sub assign a number to a dimensionless quantity
263     /// ditto
264     void opOpAssign(string op, T)(T scalar)
265             if (isNumeric!T && (op == "+" || op == "-"))
266     {
267         mixin checkDimensionless!unit;
268         mixin("_value " ~ op ~ "= scalar;");
269     }
270 
271     // Mul/div assign another dimensionless quantity to a dimensionsless quantity
272     /// ditto
273     void opOpAssign(string op, Q)(auto ref const Q qty)
274             if (isQuantity!Q && (op == "*" || op == "/" || op == "%"))
275     {
276         mixin checkDimensionless!unit;
277         mixin("_value" ~ op ~ "= qty._value;");
278     }
279 
280     // Mul/div assign a number to a quantity
281     /// ditto
282     void opOpAssign(string op, T)(T scalar)
283             if (isNumeric!T && (op == "*" || op == "/"))
284     {
285         mixin("_value" ~ op ~ "= scalar;");
286     }
287 
288     /// ditto
289     void opOpAssign(string op, T)(T scalar)
290             if (isNumeric!T && op == "%")
291     {
292         mixin checkDimensionless!unit;
293         mixin("_value" ~ op ~ "= scalar;");
294     }
295 
296     // Exact equality between quantities
297     /// ditto
298     bool opEquals(Q)(auto ref const Q qty) const 
299             if (isQuantity!Q)
300     {
301         mixin checkDim!(Q.unit);
302         return _value == qty._value;
303     }
304 
305     // Exact equality between a dimensionless quantity and a number
306     /// ditto
307     bool opEquals(T)(T scalar) const 
308             if (isNumeric!T)
309     {
310         mixin checkDimensionless!unit;
311         return _value == scalar;
312     }
313 
314     // Comparison between two quantities
315     /// ditto
316     int opCmp(Q)(auto ref const Q qty) const 
317             if (isQuantity!Q)
318     {
319         mixin checkDim!(Q.unit);
320         if (_value == qty._value)
321             return 0;
322         if (_value < qty._value)
323             return -1;
324         return 1;
325     }
326 
327     // Comparison between a dimensionless quantity and a number
328     /// ditto
329     int opCmp(T)(T scalar) const 
330             if (isNumeric!T)
331     {
332         mixin checkDimensionless!unit;
333         if (_value < scalar)
334             return -1;
335         if (_value > scalar)
336             return 1;
337         return 0;
338     }
339 
340     void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const
341     {
342         sink.formatValue(_value, fmt);
343         sink(" ");
344         sink.formattedWrite!"%s"(unit.dimensions);
345     }
346 }
347 
348 /// Creates a new monodimensional unit as a QVariant
349 auto unit(N, string symbol)()
350 {
351     import quantities.runtime.qvariant;
352 
353     enum u = quantities.runtime.qvariant.unit!N(symbol);
354     return Quantity!(N, u).make(1);
355 }
356 
357 /// Tests whether T is a quantity type
358 template isQuantity(T)
359 {
360     import std.traits : Unqual;
361 
362     alias U = Unqual!T;
363     static if (is(U == Quantity!X, X...))
364         enum isQuantity = true;
365     else
366         enum isQuantity = false;
367 }
368 
369 /// Basic math functions that work with Quantity.
370 auto square(Q)(auto ref const Q quantity)
371         if (isQuantity!Q)
372 {
373     enum u = { return QVariant!(Q.valueType)(1, Q.dimensions.pow(2)); }();
374     return Quantity!(Q.valueType, u).make(quantity._value ^^ 2);
375 }
376 
377 /// ditto
378 auto sqrt(Q)(auto ref const Q quantity)
379         if (isQuantity!Q)
380 {
381     enum u = { return QVariant!(Q.valueType)(1, Q.dimensions.powinverse(2)); }();
382     return Quantity!(Q.valueType, u).make(std.math.sqrt(quantity._value));
383 }
384 
385 /// ditto
386 auto cubic(Q)(auto ref const Q quantity)
387         if (isQuantity!Q)
388 {
389     enum u = { return QVariant!(Q.valueType)(1, Q.dimensions.pow(3)); }();
390     return Quantity!(Q.valueType, u).make(quantity._value ^^ 3);
391 }
392 
393 /// ditto
394 auto cbrt(Q)(auto ref const Q quantity)
395         if (isQuantity!Q)
396 {
397     enum u = { return QVariant!(Q.valueType)(1, Q.dimensions.powinverse(3)); }();
398     return Quantity!(Q.valueType, u).make(std.math.cbrt(quantity._value));
399 }
400 
401 /// ditto
402 auto pow(int n, Q)(auto ref const Q quantity)
403         if (isQuantity!Q)
404 {
405     enum u = { return QVariant!(Q.valueType)(1, Q.dimensions.pow(n)); }();
406     return Quantity!(Q.valueType, u).make(std.math.pow(quantity._value, n));
407 }
408 
409 /// ditto
410 auto nthRoot(int n, Q)(auto ref const Q quantity)
411         if (isQuantity!Q)
412 {
413     enum u = { return QVariant!(Q.valueType)(1, Q.dimensions.powinverse(n)); }();
414     return Quantity!(Q.valueType, u).make(std.math.pow(quantity._value, 1.0 / n));
415 }
416 
417 /// ditto
418 Q abs(Q)(auto ref const Q quantity)
419         if (isQuantity!Q)
420 {
421     return Q.make(std.math.fabs(quantity._value));
422 }