forked from dlang/phobos
-
Notifications
You must be signed in to change notification settings - Fork 1
/
units.d
1992 lines (1823 loc) · 64.7 KB
/
units.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* Type-based, i.e. statically checked, _units of measurement.
*
* A $(I quantity) is a wrapper struct around an arbitrary value type,
* parametrized with a certain unit. A $(I unit) is basically little more than
* a marker type to keep different kinds of quantities apart, but might
* additionally have an associated name and symbol string, and – more
* more importantly – define $(I conversions) to other _units. While all
* of the possible conversions must be defined statically for type-checking,
* arbitrary callables are supported for actually converting the values, which
* do not necessarily need to be evauatable at compile time.
*
* Conversions only happen if explicitly requested and there is no different
* internal representation of values – for example $(D 1 * kilo(metre)) is
* stored just as 1 in memory, not as 1000 or relative to any other »canonical
* unit«.
*
* On top of the template core of the module, to which units are types only,
* a layer making use of »dummy« unit instances with operators defined on them
* makes it possible to work with quantities and units in a natural way, such
* that the actual unit types never need to be user directly in client code
* (see the example below).
*
* In the design of this module, the explicit concept of $(I dimensions) does
* not appear, because it would add a fair amount of complication to both the
* interface and the implementation for little benefit. Rather, the notion is
* established implicitly by defining conversions between pairs of _units – to
* see if two _units share the same dimension, just check for convertibility.
*
* The $(D std.si) module defines SI prefixes and _units for use with this
* module.
*
* Example:
* ---
* enum foo = baseUnit!("foo", "f");
* enum bar = scale!(foo, 21, "bar", "b");
*
* auto a = 2 * bar;
* assert(convert!foo(a) == 42 * foo);
* ---
*
* Todo:$(UL
* $(LI Integration with the rest of Phobos ($(D std.datetime), $(D std.math), …))
* $(LI Replace the proof-of-concept unit conversion implementation with an
* optimized one – currently some unneeded function calls are generated.)
* $(LI For $(D scale)/$(D ScaledUnit), use runtime rational/two longs
* instead of double conversion per default, to avoid precision issues?)
* $(LI Benchmark quantity operations vs. plain value type operations.)
* $(LI Make quantities of the same unit implicitly convertible if the value
* types are – e.g. via $(D ImplicitConversionTargets) once multiple
* alias this statements are allowed.)
* $(LI Are multiple conversion targets for a unit really needed? Disallowing
* that would remove some odd corner cases.)
* $(LI Just forward $(LREF Quantity) instanciations with unit
* $(LREF dimensionless) to the value type altogether to avoid the current
* limitations regarding $(D alias this)?)
* )
*
* License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
* Authors: $(WEB klickverbot.at, David Nadlinger)
*/
module std.units;
/*
* General implementation notes:
*
* In the unit tests, enums and static asserts are used quite frequently to
* make sure all of the simple functionality is usable at compile time. In
* real code, of course, most of the time stuff would be executed at runtime.
*/
import std.conv : to;
import std.functional : compose;
import std.traits : isAssignable, mangledName;
// @@BUG@@: Only importing TypeTuple, allSatisfy and staticMap leads to a
// compilation error (Error: undefined identifier module units.std) in
// UnitImpl.
import std.typetuple;
/**
* Mixin template containing the implementation of the unit instance operators.
*
* Furthermore, it marks the surrounding type as unit – every unit type has to
* mixin this template.
*
* In addition, a unit type might also define a toString() function returning
* a custom unit symbol/name, and a list of $(LREF Conversion)s. The example
* shows how a unit type for inches might be defined if $(LREF scale) and
* $(LREF ScaledUnit) did not exist (which remove the need to write
* boilerplate code like this).
*
* Example:
* ---
* struct Inch {
* mixin UnitImpl;
*
* static string toString(UnitString type = UnitString.name) {
* final switch (type) {
* case UnitString.name: return "inch";
* case UnitString.symbol: return "in";
* }
* }
*
* alias TypeTuple!(
* Conversion!(centi(metre), toCm, fromCm)
* ) Conversions;
*
* static V toCm(V)(V v) {
* return cast(V)(v * 2.54);
* }
* static V fromCm(V)(V v) {
* return cast(V)(v / 2.54);
* }
* }
* enum inch = Inch.init; // Unit instance to use with abbreviated syntax.
* ---
*
* Note: Two existing units $(D a) and $(D c) can't be retroactively extended
* with a direct conversion between them. This is by design, as it would break
* D's modularization/encapsulation approach (alternative: use mixin template
* for defining conversion functions, then it would be possible to have
* different behavior of the conversion function in each module). However,
* currently it $(I is) possible to create a third unit $(D b) which is
* convertible to both $(D a) and $(D c), and then perform the conversion in
* two steps: $(D convert!c(convert!b(1 * a))))
*/
mixin template UnitImpl() {
/**
* Multiplication/division of two unit instances, yielding a unit instance
* representing the product/quotient unit.
*
* Example:
* ---
* enum joule = newton * metre;
* enum watt = joule / second;
* ---
*/
auto opBinary(string op : "*", Rhs)(Rhs rhs) if (isUnit!Rhs) {
return ProductUnit!(typeof(this), Rhs).Result.init;
}
/// ditto
auto opBinary(string op : "/", Rhs)(Rhs rhs) if (isUnit!Rhs) {
return QuotientUnit!(typeof(this), Rhs).Result.init;
}
/**
* Multiplication/division of an unit and a value type, constructing a
* Quantity instance.
*
* Example:
* ---
* auto a = 2 * metre;
* auto b = 2 / metre;
* auto c = metre * 2;
* auto d = metre / 2;
* ---
*/
auto opBinary(string op : "*", V)(V rhs) if (!(isUnit!V || isQuantity!V)) {
return Quantity!(typeof(this), V).fromValue(rhs);
}
/// ditto
auto opBinary(string op : "/", V)(V rhs) if (!(isUnit!V || isQuantity!V)) {
// We cannot just do rhs ^^ -1 because integer types cannot be raised
// to negative powers.
return Quantity!(typeof(this), V).fromValue((rhs ^^ 0) / rhs);
}
/// ditto
auto opBinaryRight(string op : "*", V)(V lhs) if (!(isUnit!V || isQuantity!V)) {
return Quantity!(typeof(this), V).fromValue(lhs);
}
/// ditto
auto opBinaryRight(string op : "/", V)(V lhs) if (!(isUnit!V || isQuantity!V)) {
return Quantity!(PowerUnit!(typeof(this), Rational!(-1)), V).fromValue(lhs);
}
alias void SuperSecretAliasToMarkThisAsUnit; // See isUnit(T) below.
}
/**
* Possible string representations of units.
*/
enum UnitString {
name, /// Use full unit names when constructing strings.
symbol /// Use unit symbols when constructing strings.
}
private {
template isUnit(T) {
// KLUDGE: To avoid operator overloading ambiguities due to the
// opBinaryRight definitions in the dummy unit instances and Quantity,
// we need to know whether a type is a unit or not.
// Another solution would be to limit the possible unit types to
// BaseUnit/DerivedUnit/… instances, but this is not really desirable,
// especially because units with large custom type conversion tables
// are often clearer as custom structs.
enum isUnit = __traits(compiles, T.SuperSecretAliasToMarkThisAsUnit);
}
template isUnitInstance(alias T) {
static if (!__traits(compiles, isUnit!T)) {
enum isUnitInstance = isUnit!(typeof(T));
} else static if (!isUnit!T) {
enum isUnitInstance = isUnit!(typeof(T));
} else {
enum isUnitInstance = false;
}
}
/*
* Returns a string for the unit U, falling back to stringof if U doesn't
* have toString() defined.
*
* If ct is given, the result is guaranteed to be evaluatable at compile
* time (for use in error messages). In theory, this should not be
* necessary, as all of the toString functions in here should be CTFE'able,
* but due to a @@BUG@@ not yet tracked down, the ones for ScaledUnit
* and AffineUnit are not (maybe related to the alias parameters?).
*/
string getUnitString(U, bool ct = false)(UnitString type = UnitString.name) {
static if (__traits(compiles, { enum a = U.toString(UnitString.init); })) {
return U.toString(type);
} else static if (!ct && __traits(compiles,
{ auto a = U.toString(UnitString.init); }))
{
return U.toString(type);
} else static if (__traits(compiles, { enum a = U.toString(); })) {
return U.toString();
} else static if (!ct && __traits(compiles, { auto a = U.toString(); })) {
return U.toString();
} else {
return U.stringof;
}
}
}
/**
* Shordhand for creating a basic unit with a name and a symbol, and no
* conversions defined.
*
* When using BaseUnit, in virtually every use case, you also want to define
* an associated unit instance, as shown below. As there should be no real use
* for the unit type in user case anyway, you can also use baseUnit which
* directly returns a unit instance.
*
* Example:
* ---
* alias BaseUnit!("Ampere", "A") Ampere;
* enum ampere = Ampere.init;
* // or
* enum ampere = baseUnit!("Ampere", "A");
* ---
*/
struct BaseUnit(string name, string symbol = null) {
mixin UnitImpl;
static string toString(UnitString type = UnitString.name) {
switch (type) {
case UnitString.symbol: if (symbol) return symbol;
default: return name;
}
}
}
/// ditto
template baseUnit(string name, string symbol = null) {
enum baseUnit = (BaseUnit!(name, symbol)).init;
}
version (unittest) private {
// Some types for use as base units in unit tests.
alias BaseUnit!("foo", "f") Foo;
enum foo = Foo.init;
static assert(baseUnit!("foo", "f") == foo);
alias BaseUnit!("bar", "br") Bar;
enum bar = Bar.init;
alias BaseUnit!("baz", "bz") Baz;
enum baz = Baz.init;
}
/**
* A special unit used to represent dimensionless quantities.
*/
struct Dimensionless {
mixin UnitImpl;
static string toString(UnitString type = UnitString.name) {
final switch (type) {
case UnitString.name: return "dimensionless";
case UnitString.symbol: return "";
}
}
}
enum dimensionless = Dimensionless.init; /// ditto
/**
* A pair of a (base) unit and a compile-time rational exponent.
*
* Multiple BaseUnitExps make up a $(LREF DerivedUnit).
*/
struct BaseUnitExp(B, R) if (!isDerivedUnit!B && isUnit!B && isRational!R) {
alias B BaseUnit;
alias R Exp;
}
private {
template isBaseUnitExp(T) {
// We can't fold this into a single expression because the special
// is syntax works only inside static ifs.
static if (is(T _ : BaseUnitExp!(B, R), B, R)) {
enum isBaseUnitExp = true;
} else {
enum isBaseUnitExp = false;
}
}
unittest {
static assert(isBaseUnitExp!(BaseUnitExp!(Foo, Rational!1)));
static assert(!isBaseUnitExp!(Rational!1));
}
}
/**
* Constructs a derived unit, consisting of a number of base units and
* associated exponents.
*
* Usually, constructing unit types using the operators defined on the unit
* instances is preferred over using this template directly.
*
* Internally, derived units are represented as DUnit instances, but never try
* to use it directly – the DerivedUnit template performs canonicalization to
* ensure that semantically equivalent units share the same underlying types.
* Also, the result will not actually be a DUnit in some cases, but rather
* Dimensionless or a single base unit without exponent.
*
* Example:
* ---
* alias DerivedUnit!(
* BaseUnitExp!(Ampere, Rational!1),
* BaseUnitExp!(Second, Rational!1)
* ) Coulomb;
* enum coulomb = Coulomb.init;
*
* // In most cases, you would want to just use the operators
* // on the unit instances instead:
* enum coulomb = ampere * second;
* ---
*/
template DerivedUnit(T...) if (allSatisfy!(isBaseUnitExp, T)) {
alias MakeDerivedUnit!(T).Result DerivedUnit;
}
unittest {
alias DerivedUnit!(BaseUnitExp!(Foo, Rational!(-2))) A;
static assert(is(
A.BaseUnitExps == TypeTuple!(BaseUnitExp!(Foo, Rational!(-2)))
), "Basic compound unit construction does not work.");
static assert(is(
DerivedUnit!(
BaseUnitExp!(Foo, Rational!2),
BaseUnitExp!(Bar, Rational!1),
BaseUnitExp!(Baz, Rational!3)
) == DerivedUnit!(
BaseUnitExp!(Bar, Rational!1),
BaseUnitExp!(Baz, Rational!3),
BaseUnitExp!(Foo, Rational!2)
)
), "Base unit sorting does not work.");
// FIXME: Another problem probably related to signed/unsigned integer
// literals, uncomment BarBU-related arguments below to see the assert fail.
static assert(is(
DerivedUnit!(
BaseUnitExp!(Foo, Rational!2), BaseUnitExp!(Foo, Rational!1)/+,
BaseUnitExp!(Bar, Rational!(-3)), BaseUnitExp!(Bar, Rational!2)+/
) == DerivedUnit!(
BaseUnitExp!(Foo, Rational!3), /+BaseUnitExp!(Bar, Rational!(-1)+/
)
), "Base unit joining does not work.");
static assert(is(
DerivedUnit!(
BaseUnitExp!(Foo, Rational!2),
BaseUnitExp!(Foo, Rational!(-2))
) == Dimensionless
), "Zero exponent base unit pruning does not work.");
static assert(is(
DerivedUnit!(
BaseUnitExp!(Dimensionless, Rational!1),
BaseUnitExp!(Foo, Rational!1)
) == Foo
), "Removing Dimensionless during zero exponent pruning does not work.");
static assert(is(
DerivedUnit!(BaseUnitExp!(Foo, Rational!1)) == Foo
), "Demotion of trivial compound units to base units does not work.");
}
private {
/// ditto
struct DUnit(T...) {
alias T BaseUnitExps;
mixin UnitImpl;
/**
* Returns a formatted unit string consisting of base unit names and
* exponents.
*
* As recommended in the SI manual, the units are sorted alphabetically.
*/
static string toString(UnitString type = UnitString.name) {
string[] bueStrings;
foreach (i, Bue; BaseUnitExps) {
immutable n = Bue.Exp.numerator;
immutable d = Bue.Exp.denominator;
if (n == 0) continue;
string current = getUnitString!(Bue.BaseUnit)(type);
if (n != 1) {
current ~= "^";
immutable parens = n < 0 || d != 1;
if (parens) current ~= "(";
current ~= to!string(n);
if (d != 1) {
current ~= "/" ~ to!string(d);
}
if (parens) current ~= ")";
}
bueStrings ~= current;
}
// std.algorithm.sort() is currently buggy at compile-time, use
// our custom makeIndex implementation instead.
auto indices = new size_t[bueStrings.length];
makeIndexCtfe(bueStrings, indices);
string result = bueStrings[indices[0]];
foreach (i; indices[1..$]) {
result ~= " " ~ bueStrings[i];
}
return result;
}
}
/*
* Returns the canonical unit type for the passed parameters.
*
* The rest of unit handling code does not care about canonicalization, it
* just uses DerivedUnit where necessary.
*/
template MakeDerivedUnit(T...) {
static if (T.length == 0) {
alias Dimensionless Result;
} else {
alias CanonicalizeBaseUnitExps!T.Result CanonicalBUEs;
static if (CanonicalBUEs.length == 0) {
alias Dimensionless Result;
} else {
alias CanonicalBUEs[0] A;
static if (CanonicalBUEs.length == 1 && is(A.Exp == Rational!1)) {
// If we just have a single base unit with exponent 1,
// don't construct a DUnit, but just return that base unit
// instead.
alias A.BaseUnit Result;
} else {
alias DUnit!CanonicalBUEs Result;
}
}
}
}
template CanonicalizeBaseUnitExps(T...) {
// Sort the BaseUnitExp list by the mangled name of the base unit
// types. Just .mangleof could be used here as well, but
// mangledName conveniently is a template already.
template GetBU(BUE) {
alias BUE.BaseUnit GetBU;
}
alias staticMap!(mangledName, staticMap!(GetBU, T)) BaseUnitNames;
alias IndexedTuple!(makeArgumentIndex(BaseUnitNames), T) SortedBUEs;
alias PruneZeroBaseUnitExps!(
JoinBaseUnitExps!(SortedBUEs).Result
).Result Result;
}
/*
* Takes a type tuple of BaseUnitExps and joins adjacent sets which share
* the same BaseUnit.
*
* The resulting type tuple is accesible via JoinBaseUnitExps.Result.
*/
template JoinBaseUnitExps(T...) {
static if (T.length < 2) {
alias TypeTuple!T Result;
} else {
// Have to alias T[0] and T[1] here because otherwise trying to
// access T[0].BaseUnit results in a syntax error. If it
// wasn't for that, this could be an eponymous template. Is this
// a @@BUG@@?
alias T[0] A;
alias T[1] B;
static if (is(A.BaseUnit == B.BaseUnit)) {
alias JoinBaseUnitExps!(
BaseUnitExp!(A.BaseUnit, Sum!(A.Exp, B.Exp)),
T[2 .. $]
).Result Result;
} else {
alias TypeTuple!(A, JoinBaseUnitExps!(T[1..$]).Result) Result;
}
}
}
/*
* Takes a type tuple of BaseUnitExps and removes ones with exponent zero
* or unit Dimensionless (this can happen if »dimensionless« is used
* directly).
*
* The pruned list is accesible via PruneZeroBaseUnitExps.Result.
*/
template PruneZeroBaseUnitExps(T...) {
static if (T.length == 0) {
alias TypeTuple!() Result;
} else {
alias T[0] A;
static if (A.Exp.numerator == 0 || is(A.BaseUnit == Dimensionless)) {
alias PruneZeroBaseUnitExps!(T[1..$]).Result Result;
} else {
alias TypeTuple!(A, PruneZeroBaseUnitExps!(T[1..$]).Result) Result;
}
}
}
template isDerivedUnit(T) {
// Matching DUnit with its type tuple parameter directly doesn't work,
// so take advantage of the fact that we can access BaseUnitExps from
// outside.
static if (is(T.BaseUnitExps) && is(T : DUnit!(T.BaseUnitExps))) {
enum isDerivedUnit = true;
} else {
enum isDerivedUnit = false;
}
}
}
/**
* An affine unit – the most common case being a unit that is related to other
* units representing the same physical quantity not by a scale factor, but by
* a shift in the zero point.
*
* This is not a fundamentally new concept, adding a constant offset could
* just be implemented in a custom conversion function as well (see the
* $(LREF UnitImpl) documentation). However, Quantity is specialized on
* affine units such as to only provide operations which make sense for them:
*
* Informally speaking, an affine space is a vector space which »forgot« its
* origin, its elements are points, not vectors. Thus, a quantity of an
* affine unit cannot be added to another (as it makes no sense to add two
* points), but like vectors can be added to points to yield a new point, a
* quantity of the underlying base unit can be. Also, two affine quantities
* can be substracted to yield a quantity of the base unit (just as two
* points can be substracted to get a vector pointing from one to another).
*
* The most common example for this are units of temperature like degrees
* Celsius or Fahrenheit, as demonstrated below.
*
* Example:
* ---
* enum celsius = affine!(273.15, "degrees Celsius", "°C")(kelvin);
* auto t = 3.0 * celsius;
* t += 1.0 * kelvin; // adding Kelvin is okay
* assert(!__traits(compiles, t += 2.0 * celsius)); // adding Celsius is not
* writeln(t - 0.0 * celsius); // 4 Kelvin, not degrees Celsius
* ---
*/
struct AffineUnit(BaseUnit, alias toBaseOffset, string name,
string symbol = null) if (isUnit!BaseUnit)
{
alias BaseUnit LinearBaseUnit;
mixin UnitImpl;
static string toString(UnitString type = UnitString.name) {
switch (type) {
case UnitString.symbol: if (symbol) return symbol;
default: return name;
}
}
alias TypeTuple!(Conversion!(BaseUnit, toBase, fromBase)) Conversions;
static V toBase(V)(V v) {
return cast(V)(v + toBaseOffset);
}
static V fromBase(V)(V v) {
return cast(V)(v - toBaseOffset);
}
}
/// ditto
template AffineUnit(alias baseUnit, alias toBaseOffset, string name,
string symbol = null) if (isUnitInstance!baseUnit)
{
alias AffineUnit!(typeof(baseUnit), toBaseOffset, name, symbol) AffineUnit;
}
/// ditto
auto affine(alias toBaseOffset, string name, string symbol = null, U)(U u)
if (isUnit!U)
{
alias AffineUnit!(U, toBaseOffset, name, symbol) New;
return New.init;
}
/**
* A quantity consisting of a value and an associated unit of measurement.
*
* The unary plus, unary minus, addition, subtraction, multiplication,
* division, comparison, increment and decrement operators are forwarded to
* the underlying value type, if the operation is meaningful for the given
* unit(s).
*
* A quantity is only implicitly convertible to the underlying value type
* (via $(D alias this)) if it is dimensionless – divide a quantity by its
* unit if you want to access the raw value.
*/
struct Quantity(Unit, ValueType = double) if (isUnit!Unit) {
enum unit = Unit.init; /// An instance of Unit.
/**
* Two quantites of the same unit are implicitely convertible on
* assignment if the underlying value types are.
*
* Example:
* ---
* Quantity!(metre, float) a = 2 * metre;
* ---
*/
this(OtherV)(Quantity!(Unit, OtherV) other) if (
isAssignable!(ValueType, OtherV)
) {
value = other.value;
}
/// ditto
ref Quantity opAssign(OtherV)(Quantity!(Unit, OtherV) other) if (
isAssignable!(ValueType, OtherV)
) {
value = other.value;
return this;
}
/**
* A quantity is castable to another one with the same unit if the value
* type can be casted to the new one.
*
* For converting a quantity to another unit, see $(LREF convert) instead.
*
* Example:
* ---
* auto a = 2.0 * metre;
* assert(cast(Quantity!(metre, int))a == 2 * metre);
* ---
*/
Quantity!(Unit, NewV) opCast(T : Quantity!(Unit, NewV), NewV)() if (
is(typeof(cast(NewV)ValueType.init))
) {
return Quantity!(Unit, NewV).fromValue(cast(NewV)value);
}
// No way to specialize Quantity on AffineUnit (since it takes an alias
// template parameter), so just check if we can access its
// LinearBaseUnit here.
static if (!is(Unit.LinearBaseUnit BaseUnit))
{
/**
* Unary plus/minus operators.
*
* Example:
* ---
* auto l = 6 * metre;
* assert(+l == 6 * metre);
* assert(-l == (-6) * metre);
* ---
*/
auto opUnary(string op)() if (
(op == "+" || op == "-") && is(typeof(mixin(op ~ "ValueType.init")))
) {
return Quantity!(Unit, typeof(mixin(op ~ "value"))).fromValue(
mixin(op ~ "value"));
}
/**
* Prefix increment/decrement operators.
*
* They are only provided dimensionless quantities, because they are
* semantically equivalent to adding the dimensionless quantity 1.
*/
auto opUnary(string op)() if (
(op == "++" || op == "--") && is(Unit == Dimensionless) &&
is(typeof({ ValueType v; return mixin(op ~ "v"); }()))
) {
return Quantity!(Dimensionless, typeof(mixin(op ~ "value"))).fromValue(
mixin(op ~ "value")
);
}
/**
* Addition/substraction of a quantity with the same unit.
*
* Example:
* ---
* auto a = 3 * metre;
* auto b = 2 * metre;
* assert(a + b == 5 * metre);
* a -= b;
* assert(a == 1 * metre);
* ---
*/
auto opBinary(string op, RhsV)(Quantity!(Unit, RhsV) rhs) if (
(op == "+" || op == "-") &&
is(typeof(mixin("ValueType.init" ~ op ~ "RhsV.init")))
) {
return Quantity!(Unit, typeof(mixin("value" ~ op ~ "rhs.value"))).
fromValue(mixin("value" ~ op ~ "rhs.value"));
}
/// ditto
ref Quantity opOpAssign(string op, RhsV)(Quantity!(Unit, RhsV) rhs) if (
(op == "+" || op == "-") &&
__traits(compiles, { ValueType v; mixin("v" ~ op ~ "= RhsV.init;"); }())
) {
mixin("value" ~ op ~ "= rhs.value;");
return this;
}
/**
* Multplication/division by a plain value (i.e. a dimensionless quantity
* not represented by a Quantity instance).
*
* Example:
* ---
* auto l = 6 * metre;
* assert(l * 2 == 12 * metre);
* l /= 2;
* assert(l == 3 * metre);
* ---
*/
auto opBinary(string op, T)(T rhs) if (
(op == "*" || op == "/") && !isUnit!T && !isQuantity!T &&
is(typeof(mixin("ValueType.init" ~ op ~ "T.init")))
) {
return Quantity!(Unit, typeof(mixin("value" ~ op ~ "rhs"))).fromValue(
mixin("value " ~ op ~ " rhs"));
}
/// ditto
auto opBinaryRight(string op, T)(T lhs) if (
(op == "*" || op == "/") && !isUnit!T && !isQuantity!T &&
is(typeof(mixin("T.init" ~ op ~ "ValueType.init")))
) {
return Quantity!(Unit, typeof(mixin("lhs" ~ op ~ "value"))).fromValue(
mixin("lhs " ~ op ~ " value"));
}
/// ditto
ref Quantity opOpAssign(string op, T)(T rhs) if (
(op == "*" || op == "/") && !isUnit!T && !isQuantity!T &&
__traits(compiles, { ValueType v; mixin("v" ~ op ~ "= T.init;"); }())
) {
mixin("value " ~ op ~ "= rhs;");
return this;
}
/**
* Multiplication with a unit instance.
*
* Returns a quantity with the same value, but the new unit.
*
* Example:
* ---
* auto l = 6 * metre;
* assert(l * metre == 6 * pow!2(metre));
* ---
*/
auto opBinary(string op : "*", Rhs)(Rhs rhs) if (isUnit!Rhs) {
return Quantity!(
ProductUnit!(Unit, Rhs).Result,
ValueType
).fromValue(value);
}
/// ditto
auto opBinaryRight(string op : "*", Lhs)(Lhs lhs) if (isUnit!Lhs) {
return Quantity!(
ProductUnit!(Lhs, Unit).Result,
ValueType
).fromValue(value);
}
/**
* Division by a unit instance.
*
* Returns a quantity with the same value, but the new unit.
*
* Example:
* ---
* auto l = 6 * metre;
* assert(l / metre == 6 * dimensionless);
* ---
*/
auto opBinary(string op : "/", RhsU)(RhsU rhs) if (isUnit!RhsU) {
return Quantity!(
QuotientUnit!(Unit, RhsU).Result,
ValueType
).fromValue(value);
}
/// ditto
auto opBinaryRight(string op : "/", Lhs)(Lhs rhs) if (isUnit!Lhs) {
// We cannot just do value ^^ -1 because integer types cannot be
// raised to negative powers.
return Quantity!(
QuotientUnit!(Lhs, Unit).Result,
ValueType
).fromValue((value ^^ 0) / value);
}
/**
* Multiplication with another quantity.
*
* Example:
* ---
* auto w = 3 * metre;
* auto h = 2 * metre;
* assert(w * h == 12 * pow!2(metre));
* ---
*/
auto opBinary(string op : "*", RhsU, RhsV)(Quantity!(RhsU, RhsV) rhs) {
return Quantity!(
ProductUnit!(Unit, RhsU).Result, typeof(value * rhs.value)
).fromValue(value * rhs.value);
}
/**
* Division by another quantity.
*
* Example:
* ---
* auto s = 6 * metre;
* auto t = 2 * second;
* assert(s / t == 3 * metre / second);
* ---
*/
auto opBinary(string op : "/", RhsU, RhsV)(Quantity!(RhsU, RhsV) rhs) {
return Quantity!(
QuotientUnit!(Unit, RhsU).Result, typeof(value / rhs.value)
).fromValue(value / rhs.value);
}
} else {
/**
* Substracts a quantity of the same unit, yielding a quantity of the non-
* affine base unit.
*
* Example:
* ---
* auto a = 3 * celsius;
* auto b = 2 * celsius;
* assert(a - b == 1 * kelvin);
* ---
*/
auto opBinary(string op : "-", RhsV)(Quantity!(Unit, RhsV) rhs) if (
is(typeof(ValueType.init - RhsV.init))
) {
return Quantity!(BaseUnit, typeof(value - rhs.value)).
fromValue(value - rhs.value);
}
/**
* Addition/substraction of a quantity with the linear base unit.
*
* Example:
* ---
* auto a = 3 * celsius;
* auto b = 2 * kelvin;
* assert(a + b == 5 * celsius);
* a -= b;
* assert(a == 1 * celsius);
* ---
*/
auto opBinary(string op, RhsV)(Quantity!(BaseUnit, RhsV) rhs) if (
(op == "+" || op == "-") &&
is(typeof(mixin("ValueType.init" ~ op ~ "RhsV.init")))
) {
return Quantity!(Unit, typeof(mixin("value" ~ op ~ "rhs.value"))).
fromValue(mixin("value" ~ op ~ "rhs.value"));
}
/// ditto
ref Quantity opOpAssign(string op, RhsV)(Quantity!(BaseUnit, RhsV) rhs) if (
(op == "+" || op == "-") &&
__traits(compiles, { ValueType v; mixin("v" ~ op ~ "= RhsV.init;"); }())
) {
mixin("value" ~ op ~ "= rhs.value;");
return this;
}
/// ditto
auto opBinaryRight(string op : "+", RhsV)(Quantity!(BaseUnit, RhsV) lhs) if (
is(typeof(ValueType.init + RhsV.init))
) {
return Quantity!(Unit, typeof(value + lhs.value)).
fromValue(value + lhs.value);
}
}
/**
* Comparison with another quantity of the same unit.
*
* Example:
* ---
* auto a = 3 * metre;
* auto b = 4 * metre;
* auto c = 5 * second;
* assert(a != b);
* assert(a < b);
* assert(!__traits(compiles, a != c));
* assert(!__traits(compiles, a < c));
* ---
*/
int opEquals(RhsV)(Quantity!(Unit, RhsV) rhs) if (
is(typeof(ValueType.init == RhsV.init) : bool)
) {
return value == rhs.value;
}
/// ditto
auto opCmp(RhsV)(Quantity!(Unit, RhsV) rhs) if (
is(typeof(ValueType.init < RhsV.init) : bool)
) {
static if (__traits(compiles, value - rhs.value)) {
return value - rhs.value;
} else {
return (value == rhs.value) ? 0 : (value < rhs.value ? -1 : 1);
}
}
/**
* Returns a string representation of the quantity, consisting of the
* value and a unit symbol or name.
*
* Example:
* ---
* auto l = 6 * metre / second;
* assert(l.toString() == "6 metre second^(-1)");
* assert(l.toString(UnitString.symbol) == "6 m s^(-1)");
* ---
*/
string toString(UnitString type = UnitString.name) {
return to!string(value) ~ " " ~ getUnitString!Unit(type);
}
static if (is(Unit == Dimensionless)) {
ValueType rawValue() @property {
return value;
}
void rawValue(ValueType newValue) @property {
value = newValue;
}
alias rawValue this;
}
private:
static Quantity fromValue(ValueType value) {
Quantity q = void;
q.value = value;
return q;
}
ValueType value;
}
/// ditto
template Quantity(alias unit, ValueType = double) if (isUnitInstance!unit) {
alias Quantity!(typeof(unit), ValueType) Quantity;
}
unittest {
enum f1 = 6 * foo;
static assert(+f1 == f1);
static assert(-f1 == (-6) * foo);
static assert(f1 + f1 == 12 * foo);
static assert(f1 - f1 == 0 * foo);