/
1935R2_a_cpp_approach_to_physical_units.bs
2752 lines (2168 loc) · 124 KB
/
1935R2_a_cpp_approach_to_physical_units.bs
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
<pre class='metadata'>
Title: A C++ Approach to Physical Units
Shortname: P1935
Revision: 2
Status: P
Group: WG21
Audience: LEWG, SG6, SG18
URL: https://mpusz.github.io/wg21-papers/papers/1935_a_cpp_approach_to_physical_units.html
Editor: Mateusz Pusz, Epam Systems http://www.epam.com, mateusz.pusz@gmail.com, http://www.train-it.eu
Abstract: This document starts the discussion about the Physical Units support for the
C++ Standard Library. The reader will find here the rationale for such a library.
After that comes the review and comparison of current solutions on the market
followed by the analysis of the problems related to their usage and user
experience. The rest of the document describes solutions and techniques that
can be used to mitigate those issues. All of them were implemented and tested
by the author in the mp-units library.
Repository: mpusz/wg21_papers
!Source: <a href="https://github.com/mpusz/wg21_papers/blob/master/src/1935R2_a_cpp_approach_to_physical_units.bs">github.com/mpusz/wg21_papers/blob/master/src/1935R2_a_cpp_approach_to_physical_units.bs</a>
Markup Shorthands: markdown on
</pre>
Revision History {#revision-history}
====================================
r1 ➡ r2 {#r1r2}
----------------
- [[MP-UNITS]] code samples updated to reflect the current library design
- [[#other-design-decisions]] chapter added
- [[#systems-support]] chapter reworked
- [[#dimensionless-quantities]] chapter reworked
- [[#no-runtime-conversions]] chapter added
- [[#no-quantity-kind]] chapter added
- [[#text-output]] chapter added
- [[#open-questions]] chapter reworked
r0 ➡ r1 {#r0r1}
----------------
- New definitions added (coherent system of units, base dimension, derived dimension, reduced
dimension) to [[#terms-and_definitions]]
- [[#prior-work]] chapter updated
- Prefixes and units chapter extended with last paragraph
- [[#systems-support]] extended
- Integral UDLs chapter updated
- Number concept chapter added
- Unicode chapter added
- [[#implementation]] extended
- [[#duration-interoperability]] alternative 2 replaced with a better idea
- [[#polls]] extended and split to separate ISO C++ rooms
- [[MP-UNITS]] code samples updated to reflect the current library design
Introduction {#introduction}
============================
Overview {#overview}
--------------------
Human history knows many expensive failures and accidents caused by mistakes in
calculations involving different physical units. The most famous and probably the most
expensive example in the software engineering domain is the Mars Climate Orbiter that in
1999 failed to enter Mars orbit and crashed while entering its atmosphere [[MARS_ORBITER]].
That is not the only example here. People tend to confuse units quite often. We see similar
errors occurring in various domains over the years:
- On October 12, 1492, Christopher Columbus unintentionally discovered America because
during his travel preparations he mixed Arabic mile with a Roman mile which led to
the wrong estimation of the equator and his expected travel distance [[COLUMBUS]]
- Air Canada Flight 143 ran out of fuel on July 23, 1983, at an altitude of 41 000 feet
(12 000 metres), midway through the flight because the fuel had been calculated in
pounds instead of kilograms by the ground crew [[GIMLI_GLIDER]]
- On April 15, 1999, Korean Air Cargo Flight 6316 crashed due to the miscommunication
between pilots about desired flight altitude [[FLIGHT_6316]]
- In February 2001 Zoo crew built an enclosure for Clarence the Tortoise with a weight of
250 pounds instead of 250 kilograms [[CLARENCE]]
- In December 2003, one of the roller coaster's cars at Tokyo Disneyland’s Space Mountain
attraction suddenly derailed due to a broken axle caused by the confusion after
upgrading the specification from imperial to metric units [[DISNEY]]
- An American company sold a shipment of wild rice to a Japanese customer, quoting a price
of 39 cents per pound, but the customer thought the quote was for 39 cents per kilogram
[[WILD_RICE]]
- A whole set of medication dose errors...
Lack of strong types {#lack-of-strong-types}
--------------------------------------------
It turns out that in the C++ software most of our calculations in the physical units domain
are handled with fundamental types like `double`. Code like below is a typical example
here:
```cpp
double GlidePolar::MacCreadyAltitude(double emcready,
double Distance,
const double Bearing,
const double WindSpeed,
const double WindBearing,
double *BestCruiseTrack,
double *VMacCready,
const bool isFinalGlide,
double *TimeToGo,
const double AltitudeAboveTarget,
const double cruise_efficiency,
const double TaskAltDiff);
```
Even though this example comes from an Open Source project, expensive revenue-generating
production source code often does not differ too much. We lack strong typedefs feature in the
core language, and without it, we are often too lazy to handcraft a new class type for each
use case.
The proliferation of magic numbers {#magic-numbers}
-----------------------------------------------
There are a lot of constants and conversion factors involved in the dimensional analysis.
Source code responsible for such computations is often trashed with magic numbers
```cpp
// Air Density(kg/m3) from relative humidity(%),
// temperature(°C) and absolute pressure(Pa)
double AirDensity(double hr, double temp, double abs_press)
{
return (1/(287.06*(temp+273.15))) *
(abs_press - 230.617 * hr * exp((17.5043*temp)/(241.2+temp)));
}
```
Motivation and Scope {#motivation-and-scope}
============================================
Motivation {#motivation}
------------------------
There is a huge demand for high-quality physical units library in the industry and
scientific environments. The code that we write for fun and living should be correct, safe,
and easy to write. Although there are multiple such libraries available on the market, none
of them is a widely accepted production standard. We could just provide a yet another 3rd
party library covering this topic, but it is probably not the best idea.
First of all, software that could benefit from such a library is not a niche in the market.
If it was the case, probably its needs could be fulfilled with a 3rd party highly-specialized
and narrow-use library. On the contrary, a broad range of production projects deals with units
conversions and dimensional analysis. Right now, having no other reasonable and easy to access
alternatives results in the proliferation of plain `double` type usage to express physical
quantities. Space, aviation, automotive, embedded, scientific, computer science, and many
other domains could benefit from strong types and conversions provided by such a library.
Secondly, yet another library will not solve the issue for many customers. Many corporations
are not allowed to use 3rd party libraries in the production code. Also, an important point
here is the cooperation of different products from multiple vendors that use physical quantities
as vocabulary types in their interfaces. From the author's experience gathered while working
with numerous corporations all over the world, there is a considerable difference between the
adoption of a mature 3rd party library and the usage of features released as a part of
the C++ Standard Library. If it were not the case all products would use Boost.Units already.
A motivating example here can be `std::chrono` released as a part of C++11. Right now, no one
asks questions on how to represent timestamps and how to handle their conversions in the code.
`std::chrono` is the ultimate answer. So let us try to get `std::units` in the C++
Standard Library too.
The Goal {#goal}
----------------
The aim of this paper is to standardize a physical units library that enables operations on
various dimensions and units:
```cpp
// simple numeric operations
static_assert(10km / 2 == 5km);
// unit conversions
static_assert(1h == 3600s);
static_assert(1km + 1m == 1001m);
// dimension conversions
static_assert(1km / 1s == 1000mps);
static_assert(2kmph * 2h == 4km);
static_assert(2km / 2kmph == 1h);
static_assert(1000 / 1s == 1kHz);
static_assert(10km / 5km == 2);
```
We intent to provide users with cleaner interfaces by using strong types and concepts in
the interfaces rather than fundamental types with meaning described in comments or documentation:
```cpp
constexpr std::units::Velocity auto avg_speed(std::units::Length auto d, std::units::Time auto t)
{
return d / t;
}
```
We further aim to provide unit conversion facilities and constants for users to rely on,
instead of magic numbers:
```cpp
using namespace std::units_literals;
const std::units::Velocity auto speed = avg_speed(220km, 2h);
std::cout << "Average speed: "
<< std::units::quantity_cast<std::units::si::kilometre_per_hour>(speed) << '\n';
```
Scope {#scope}
--------------
There is a public demand for a generic units library that could handle any units and dimensions.
There are many, often conflicting, requirements. Some of them can be found in [[P1930R0]].
To limit the initial scope, the author suggests scoping the Committee efforts only on the
physical and possibly computer science (i.e. `bit`, `byte`, `bitrate`) units first. The library
should be designed with easy extensibility in mind so anyone needing a new base or derived
dimensions (i.e. `coffee/milk/water/sugar` system) could achieve this with a few lines of
the C++ code (not preprocessor macros).
After releasing a first, restricted version of the library and observing how it is used we
can consider standardizing additional dimensions, units, and constants in the following
C++ releases.
Terms and definitions {#terms-and_definitions}
==============================================
ISO 80000-1:2009(E) definitions {#iso-definitions}
--------------------------------------------------
ISO 80000-1:2009(E) Quantities and units - Part 1: General
[[!ISO_80000-1]] defines among others the following terms:
<dfn>quantity</dfn>
- Property of a phenomenon, body, or substance, where the property has a magnitude that can
be expressed by means of a number and a reference.
- A reference can be a measurement unit, a measurement procedure, a reference material, or
a combination of such.
- A quantity as defined here is a scalar. However, a vector or a tensor, the components of
which are quantities, is also considered to be a quantity.
- The concept ’quantity’ may be generically divided into, e.g. ‘physical quantity’,
‘chemical quantity’, and ‘biological quantity’, or ‘base quantity’ and ‘derived quantity’.
- Examples of quantities are: mass, length, density, magnetic field strength, etc.
<dfn lt="kind of quantity|kind">kind of quantity, kind</dfn>
- Aspect common to mutually comparable [=quantities=].
- The division of the concept ‘quantity’ into several kinds is to some extent arbitrary
- i.e. the quantities diameter, circumference, and wavelength are generally considered
to be [=quantities=] of the same kind, namely, of the kind of quantity called length.)
- [=Quantities=] of the same kind within a given [=system of quantities=] have the same quantity
[=dimension=]. However, [=quantities=] of the same [=dimension=] are not necessarily of
the same kind.
- For example, the absorbed dose and the dose equivalent have the same
dimension. However, the former measures the absolute amount of radiation one receives
whereas the latter is a weighted measurement taking into account the kind of radiation
on was exposed to.
<dfn lt="system of quantities|system">system of quantities, system</dfn>
- Set of [=quantities=] together with a set of non-contradictory equations relating those
[=quantities=].
- Examples of systems of quantities are: the International System of Quantities, the Imperial
System, etc.
<dfn>base quantity</dfn>
- [=Quantity=] in a conventionally chosen subset of a given [=system of quantities=], where
no [=quantity=] in the subset can be expressed in terms of the other [=quantities=]
within that subset.
- Base quantities are referred to as being mutually independent since a base quantity
cannot be expressed as a product of powers of the other base quantities.
<dfn>derived quantity</dfn>
- [=Quantity=], in a [=system of quantities=], defined in terms of the base quantities of
that system.
<dfn lt="International System of Quantities|ISQ">International System of Quantities (ISQ)</dfn>
- [=System of quantities=] based on the seven [=base quantities=]: length, mass, time,
electric current, thermodynamic temperature, amount of substance, and luminous intensity.
- The International System of Units (SI) is based on the ISQ.
<dfn lt="dimension of a quantity|quantity dimension|dimension">dimension of a quantity,
quantity dimension, dimension</dfn>
- Expression of the dependence of a [=quantity=] on the [=base quantities=] of
a [=system of quantities=] as a product of powers of factors corresponding to the [=base
quantities=], omitting any numerical factors.
- A power of a factor is the factor raised to an exponent. Each factor is the dimension of
a [=base quantity=].
- In deriving the dimension of a quantity, no account is taken of its scalar, vector, or
tensor character.
- In a given [=system of quantities=]:
- [=quantities=] of the same [=kind=] have the same quantity dimension,
- [=quantities=] of different quantity dimensions are always of different [=kinds=],
- [=quantities=] having the same quantity dimension are not necessarily of the same [=kind=].
<dfn lt="quantity of dimension one|quantities of dimension one|dimensionless quantity">
quantity of dimension one, dimensionless quantity</dfn>
- [=Quantity=] for which all the exponents of the factors corresponding to the
[=base quantities=] in its [=quantity dimension=] are zero.
- The term “dimensionless quantity” is commonly used and is kept here for historical reasons.
It stems from the fact that all exponents are zero in the symbolic representation of
the [=dimension=] for such [=quantities=]. The term “quantity of dimension one” reflects
the convention in which the symbolic representation of the [=dimension=] for such
[=quantities=] is the symbol 1. This [=dimension=] is not a number, but the neutral
element for multiplication of [=dimensions=].
- The measurement [=units=] and values of [=quantities=] of dimension one are numbers, but
such [=quantities=] convey more information than a number.
- Some [=quantities=] of dimension one are defined as the ratios of two [=quantities=] of
the same kind. The [=coherent derived unit=] is the number one, symbol 1.
- Numbers of entities are quantities of dimension one.
<dfn lt="unit of measurement|measurement unit|unit">unit of measurement, measurement unit,
unit</dfn>
- Real scalar [=quantity=], defined and adopted by convention, with which any other
[=quantity=] of the same [=kind=] can be compared to express the ratio of the second
quantity to the first one as a number.
- Measurement units are designated by conventionally assigned names and symbols.
- Measurement units of [=quantities=] of the same [=quantity dimension=] may be designated
by the same name and symbol even when the [=quantities=] are not of the same [=kind=].
For example, joule per kelvin and J/K are respectively the name and symbol of both a
measurement unit of heat capacity and a measurement unit of entropy, which are generally
not considered to be [=quantities=] of the same [=kind=]. However, in some cases special
measurement unit names are restricted to be used with [=quantities=] of specific kind
only. For example, the measurement unit ‘second to the power minus one’ (1/s) is called
hertz (Hz) when used for frequencies and becquerel (Bq) when used for activities of
radionuclides. As another example, the joule (J) is used as a unit of energy, but never
as a unit of moment of force, i.e. the newton metre (N · m).
- Measurement units of [=quantities of dimension one=] are numbers. In some cases, these
measurement units are given special names, e.g. radian, steradian, and decibel, or are
expressed by quotients such as millimole per mole equal to 10<sup>−3</sup> and microgram
per kilogram equal to 10<sup>−9</sup>.
<dfn>base unit</dfn>
- Measurement unit that is adopted by convention for a [=base quantity=].
- In each [=coherent system of units=], there is only one base unit for each [=base quantity=].
- A base unit may also serve for a [=derived quantity=] of the same [=quantity dimension=].
- For example, the ISQ has the base units of: metre, kilogram, second, Ampere, Kelvin, mole,
and candela.
<dfn>derived unit</dfn>
- Measurement unit for a [=derived quantity=].
- For example, in the ISQ Newton, Pascal, and katal are derived units.
<dfn>coherent derived unit</dfn>
- Derived [=unit=] that, for a given [=system of quantities=] and for a chosen set of
[=base units=], is a product of powers of [=base units=] with no other proportionality
factor than one.
- A power of a [=base unit=] is the [=base unit=] raised to an exponent.
- Coherence can be determined only with respect to a particular [=system of quantities=]
and a given set of [=base units=]. That is, if the metre and the second are base units,
the metre per second is the coherent derived unit of velocity.
<dfn>system of units</dfn>
- Set of [=base units=] and [=derived units=], together with their multiples and submultiples,
defined in accordance with given rules, for a given [=system of quantities=].
<dfn>coherent system of units</dfn>
- [=System of units=], based on a given [=system of quantities=], in which the measurement
unit for each [=derived quantity=] is a [=coherent derived unit=].
- A [=system of units=] can be coherent only with respect to a [=system of quantities=] and
the adopted [=base units=].
<dfn lt="off-system measurement unit|off-system unit">off-system measurement unit,
off-system unit</dfn>
- [=Measurement unit=] that does not belong to a given [=system of units=]. For example, the
electronvolt (≈ 1,602 18 × 10–19 J) is an off-system measurement unit of energy with
respect to the SI or day, hour, minute are off-system measurement units of time with
respect to the SI.
<dfn lt="International System of Units|SI">International System of Units (SI)</dfn>
- [=System of units=], based on the [=International System of Quantities=], their names and
symbols, including a series of prefixes and their names and symbols, together with rules
for their use, adopted by the General Conference on Weights and Measures (CGPM)
<dfn>multiple of a unit</dfn>
- [=Measurement unit=] obtained by multiplying a given [=measurement unit=] by an integer
greater than one.
- [=SI=] prefixes refer strictly to powers of 10, and should not be used for powers of 2. That
is, 1 kbit should not be used to represent 1024 bits (2<sup>10</sup> bits), which is a
kibibit (1 Kibit).
<dfn>submultiple of a unit</dfn>
- [=Measurement unit=] obtained by dividing a given [=measurement unit=] by an integer
greater than one.
<dfn lt="quantity value|value of a quantity|value">quantity value, value of a quantity,
value</dfn>
- Number and reference together expressing magnitude of a [=quantity=].
- A quantity value can be presented in more than one way.
Other definitions {#other-definitions}
--------------------------------------
<dfn>base dimension</dfn>
- A [=dimension=] of a [=base quantity=].
<dfn>derived dimension</dfn>
- A [=dimension=] of a [=derived quantity=].
- Often implemented as a list of exponents of [=base dimensions=].
<dfn>normalized dimension</dfn>
A [=derived dimension=] in which:
- [=base dimensions=] are not repeated in a list (each base dimension is provided at most once),
- [=base dimensions=] are consistently ordered,
- [=base dimensions=] having zero exponent are elided.
Prior Work {#prior-work}
========================
There are multiple dimensional analysis libraries available on the market today. Some of them
are more successful than others, but none of them is a widely accepted standard in the C++
codebase (both for Open Source as well as production code). The next sections of this chapter
will describe the most interesting parts of selected libraries. The last section provides
an extensive comparison of their main features.
Boost.Units {#boost.units}
--------------------------
Boost.Units [[BOOST.UNITS]] is probably the most widely adopted library in this domain.
It was first included in Boost 1.36.0 that was released in 2008.
### Usage example ### {#boost.units.usage.example}
```cpp
#include <boost/units/io.hpp>
#include <boost/units/quantity.hpp>
#include <boost/units/systems/si/length.hpp>
#include <boost/units/systems/si/time.hpp>
#include <boost/units/systems/si/velocity.hpp>
#include <cassert>
#include <iostream>
namespace bu = boost::units;
constexpr bu::quantity<bu::si::velocity> avg_speed(bu::quantity<bu::si::length> d,
bu::quantity<bu::si::time> t)
{ return d / t; }
void test()
{
const auto v = avg_speed(10 * bu::si::meters, 2 * bu::si::seconds);
assert(v == 5 * bu::si::meters_per_second); // passes
assert(v.value() == 5); // passes
std::cout << v << '\n'; // prints "5 m s^-1"
}
```
[Compiler Explorer](https://godbolt.org/z/XITZI2)
First thing to notice above is that a few headers have to be included just to make such a
simple code to compile. Novices with Boost.Units library report this as an issue as sometimes
it is not obvious why the code does not compile and which headers are missing.
Now, let us extend such a code sample for a real-life use case where we would like to pass
a distance in kilometers or miles and duration in hours and get a velocity in those [=units=].
```cpp
#include <boost/units/base_units/metric/hour.hpp>
#include <boost/units/base_units/us/mile.hpp>
#include <boost/units/io.hpp>
#include <boost/units/make_scaled_unit.hpp>
#include <boost/units/quantity.hpp>
#include <boost/units/systems/si/length.hpp>
#include <boost/units/systems/si/time.hpp>
#include <boost/units/systems/si/velocity.hpp>
#include <boost/units/systems/si/prefixes.hpp>
#include <cassert>
#include <iostream>
namespace bu = boost::units;
using kilometer_base_unit = bu::make_scaled_unit<bu::si::length, bu::scale<10, bu::static_rational<3>>>::type;
using length_kilometer = kilometer_base_unit::unit_type;
using length_mile = bu::us::mile_base_unit::unit_type;
BOOST_UNITS_STATIC_CONSTANT(miles, length_mile);
using time_hour = bu::metric::hour_base_unit::unit_type;
BOOST_UNITS_STATIC_CONSTANT(hours, time_hour);
using velocity_kilometers_per_hour = bu::divide_typeof_helper<length_kilometer, time_hour>::type;
BOOST_UNITS_STATIC_CONSTANT(kilometers_per_hour, velocity_kilometers_per_hour);
using velocity_miles_per_hour = bu::divide_typeof_helper<length_mile, time_hour>::type;
BOOST_UNITS_STATIC_CONSTANT(miles_per_hour, velocity_miles_per_hour);
constexpr bu::quantity<bu::si::velocity> avg_speed(bu::quantity<bu::si::length> d,
bu::quantity<bu::si::time> t)
{ return d / t; }
void test1()
{
const auto v = avg_speed(bu::quantity<bu::si::length>(220 * bu::si::kilo * bu::si::meters),
bu::quantity<bu::si::time>(2 * hours));
// assert(v.value() == 110); // fails
bu::quantity<velocity_kilometers_per_hour> kmph(v);
// assert(kmph == 110 * kilometers_per_hour); // fails
std::cout << kmph << '\n'; // prints "110 k(m h^-1)"
}
void test2()
{
const auto v = avg_speed(bu::quantity<bu::si::length>(140 * miles),
bu::quantity<bu::si::time>(2 * hours));
// assert(v.value() == 70); // fails
bu::quantity<velocity_miles_per_hour> mph(v);
// assert(mph == 70 * miles_per_hour); // fails
std::cout << mph << '\n'; // prints "70 mi h^-1"
}
```
[Compiler Explorer](https://godbolt.org/z/-hRa66)
Even with such a simple example we immediately need to include even more headers and we
have to define custom [=unit=] types and their constants for quantities that should be common
and provided by the library for user's convenience.
Also, please notice that both pairs of asserts fail. This is caused by the fact that this
and many other units libraries implicitly convert all the units to the [=coherent derived
units=] of their dimensions which impacts the runtime performance and precision. This is another
common problem reported by users for Boost.Units. More information on this subject can be
found at [[#limiting-intermediate-value-conversions]]).
To remove unnecessary conversions we will use a function template. The good part is it
makes the assert to pass as there are no more intermediate conversions being done in both cases.
However, the side effect of this change is an increased complexity of code which now is
probably too hard to be implemented by a common C++ developer:
```cpp
template<typename LengthSystem, typename Rep1, typename TimeSystem, typename Rep2>
constexpr bu::quantity<typename bu::divide_typeof_helper<bu::unit<bu::length_dimension, LengthSystem>,
bu::unit<bu::time_dimension, TimeSystem>>::type>
avg_speed(bu::quantity<bu::unit<bu::length_dimension, LengthSystem>, Rep1> d,
bu::quantity<bu::unit<bu::time_dimension, TimeSystem>, Rep2> t)
{ return d / t; }
void test1()
{
const auto v = avg_speed(220 * bu::si::kilo * bu::si::meters, 2 * hours);
assert(v.value() == 110); // passes
assert(v == 110 * kilometers_per_hour); // passes
std::cout << v << '\n'; // prints "110 k(m h^-1)"
}
void test2()
{
const auto v = avg_speed(140 * miles, 2 * hours);
assert(v.value() == 70); // passes
assert(v == 70 * miles_per_hour); // passes
std::cout << v << '\n'; // prints "70 mi h^-1"
}
```
[Compiler Explorer](https://godbolt.org/z/6kRNEb)
The above example will be used as base for comparison to other units libraries described
in the next chapters.
### Design ### {#boost.units.design}
[=Base dimensions=] are associated with tag types that have assigned a unique integer in order
to be able to sort them on a list of a [=derived dimension=]. Negative ordinals are reserved
for use by the library.
```cpp
template<typename Derived, long N>
class base_dimension : public ordinal<N> {
public:
typedef unspecified dimension_type;
typedef Derived type;
};
```
To define custom base dimension the user has to:
```cpp
struct my_dimension : boost::units::base_dimension<my_dimension, 1> {};
```
To define [=derived dimensions=] corresponding to the [=base dimensions=], MPL-conformant
type lists of [=base dimensions=] must be created by using the `dim` class to encapsulate
pairs of [=base dimensions=] and `static_rational` exponents. The `make_dimension_list`
class acts as a wrapper to ensure that the resulting type is in the form of a
[=normalized dimension=]:
```cpp
typedef make_dimension_list<
boost::mpl::list<dim<length_base_dimension,static_rational<1>>>
>::type length_dimension;
```
This can also be accomplished using a convenience typedef provided by `base_dimension`:
```cpp
typedef length_base_dimension::dimension_type length_dimension;
```
To define the [=derived dimension=] similar steps have to be done:
```cpp
typedef make_dimension_list<
boost::mpl::list<dim<mass_base_dimension, static_rational<1>>,
dim<length_base_dimension, static_rational<2>>,
dim<time_base_dimension, static_rational<-2>>>
>::type energy_dimension;
```
or
```cpp
typedef derived_dimension<mass_base_dimension, 1,
length_base_dimension, 2,
time_base_dimension, -2>::type energy_dimension;
```
A [=unit=] is defined as a set of [=base units=] each of which can be raised to an arbitrary
rational exponent. Units are, like dimensions, purely compile-time variables with no
associated value.
```cpp
template<typename Dim, typename System, typename Enable>
class unit {
public:
typedef unit<Dim, System> unit_type;
typedef unit<Dim, System> this_type;
typedef Dim dimension_type;
typedef System system_type;
unit();
unit(const this_type&);
BOOST_CXX14_CONSTEXPR this_type& operator=(const this_type&);
};
```
In addition to supporting the compile-time dimensional analysis operations, the `+`, `-`,
`*`, and `/` runtime operators are provided for unit variables.
Base units are defined much like base dimensions and again negative ordinals are reserved:
```cpp
template<typename Derived, typename Dim, long N>
class base_unit;
```
To define a simple [=system of units=]:
```cpp
struct meter_base_unit : base_unit<meter_base_unit, length_dimension, 1> { };
struct kilogram_base_unit : base_unit<kilogram_base_unit, mass_dimension, 2> { };
struct second_base_unit : base_unit<second_base_unit, time_dimension, 3> { };
typedef make_system<meter_base_unit, kilogram_base_unit, second_base_unit>::type mks_system;
typedef unit<dimensionless_type, mks_system> dimensionless;
typedef unit<length_dimension, mks_system> length;
typedef unit<mass_dimension, mks_system> mass;
typedef unit<time_dimension, mks_system> time;
typedef unit<area_dimension, mks_system> area;
typedef unit<energy_dimension, mks_system> energy;
```
The macro `BOOST_UNITS_STATIC_CONSTANT` is provided to facilitate ODR- and thread-safe
constant definition in header files. With this some constants are defined for the supported
units to simplify variable definitions:
```cpp
BOOST_UNITS_STATIC_CONSTANT(meter, length);
BOOST_UNITS_STATIC_CONSTANT(meters, length);
BOOST_UNITS_STATIC_CONSTANT(kilogram, mass);
BOOST_UNITS_STATIC_CONSTANT(kilograms, mass);
BOOST_UNITS_STATIC_CONSTANT(second, time);
BOOST_UNITS_STATIC_CONSTANT(seconds, time);
BOOST_UNITS_STATIC_CONSTANT(square_meter, area);
BOOST_UNITS_STATIC_CONSTANT(square_meters, area);
BOOST_UNITS_STATIC_CONSTANT(joule, energy);
BOOST_UNITS_STATIC_CONSTANT(joules, energy);
```
To provide a textual output of units specialize the `base_unit_info` class for each
fundamental dimension tag:
```cpp
template<>
struct base_unit_info<meter_base_unit> {
static std::string name() { return "meter"; }
static std::string symbol() { return "m"; }
};
```
and similarly for `kilogram_base_unit` and `second_base_unit`.
It is possible to define a [=base unit=] as being a multiple of another [=base unit=].
For example, the way that `kilogram_base_unit` is actually defined by the library is
along the following lines:
```cpp
struct gram_base_unit : boost::units::base_unit<gram_base_unit, mass_dimension, 1> {};
typedef scaled_base_unit<gram_base_unit, scale<10, static_rational<3>>> kilogram_base_unit;
```
It is also possible to scale a [=unit=] as a whole, rather than scaling the individual
[=base units=] which comprise it. For this purpose, the metafunction `make_scaled_unit`
is used:
```cpp
typedef make_scaled_unit<si::time, scale<10, static_rational<-9>>>::type nanosecond;
```
Interesting point to note here is that even though Boost.Units has a strong and deeply
integrated support for [=system of units|systems of units=] it implements a US Customary
Units in an [=SI=] system rather than as an independent system of units:
```cpp
namespace us {
struct yard_base_unit : public boost::units::base_unit<yard_base_unit,
si::meter_base_unit::dimension_type, -501> {
static const char* name();
static const char* symbol();
};
typedef scaled_base_unit<yard_base_unit, scale<1760, static_rational<1>>> mile_base_unit;
}
template<> struct base_unit_info<us::mile_base_unit>;
```
[=Quantities=] are implemented by the `quantity` class template:
```cpp
template<class Unit,class Y = double>
class quantity;
```
Operators `+`, `-`, `*`, and `/` are provided for algebraic operations between scalars and
[=units=], scalars and [=quantities=], [=units=] and [=quantities=], and between [=quantities=].
Also, the standard set of boolean comparison operators (`==`, `!=`, `<`, `<=`, `>`, and
`>=`) are provided to allow comparison of [=quantities=] from the same [=system of units=].
In addition, integral and rational powers and roots can be computed using the `pow<R>` and
`root<R>` non-member functions.
To provide conversions between different [=units=] the following macro has to be used:
```cpp
BOOST_UNITS_DEFINE_CONVERSION_FACTOR(foot_base_unit, meter_base_unit, double, 0.3048);
```
The macro `BOOST_UNITS_DEFAULT_CONVERSION` specifies a conversion that will be applied
to a base unit when no direct conversion is possible. This can be used to make arbitrary
conversions work with a single specialization:
```cpp
struct my_unit_tag : boost::units::base_unit<my_unit_tag, boost::units::force_type, 1> {};
// define the conversion factor
BOOST_UNITS_DEFINE_CONVERSION_FACTOR(my_unit_tag, SI::force, double, 3.14159265358979323846);
// make conversion to SI the default.
BOOST_UNITS_DEFAULT_CONVERSION(my_unit_tag, SI::force);
```
Boost.Units also allows to provide runtime-defined conversion factors with:
```cpp
using boost::units::base_dimension;
using boost::units::base_unit;
static const long currency_base = 1;
struct currency_base_dimension : base_dimension<currency_base_dimension, 1> {};
typedef currency_base_dimension::dimension_type currency_type;
template<long N>
struct currency_base_unit : base_unit<currency_base_unit<N>, currency_type, currency_base + N> {};
typedef currency_base_unit<0> us_dollar_base_unit;
typedef currency_base_unit<1> euro_base_unit;
typedef us_dollar_base_unit::unit_type us_dollar;
typedef euro_base_unit::unit_type euro;
// an array of all possible conversions
double conversion_factors[2][2] = {
{1.0, 1.0},
{1.0, 1.0}
};
double get_conversion_factor(long from, long to) {
return (conversion_factors[from][to]);
}
void set_conversion_factor(long from, long to, double value) {
conversion_factors[from][to] = value;
conversion_factors[to][from] = 1.0 / value;
}
BOOST_UNITS_DEFINE_CONVERSION_FACTOR_TEMPLATE((long N1)(long N2),
currency_base_unit<N1>,
currency_base_unit<N2>,
double, get_conversion_factor(N1, N2));
```
This library is designed to emphasize safety above convenience when performing operations
with dimensioned [=quantities=]. Specifically:
- construction of [=quantities=] is required to fully specify both value and [=unit=]
- direct construction from a scalar value is prohibited (though the static member function
`from_value` is provided to enable this functionality where it is necessary)
- `quantity_cast` to a reference allows direct access to the underlying value of a
[=quantity=] variable
- an explicit constructor is provided to enable conversion between dimensionally compatible
[=quantities=] in different [=system|unit systems=]
- implicit conversions between [=system of units|systems of units=] are allowed only when
the [=normalized dimensions=] are identical, allowing, for example, trivial conversions
between equivalent units in different systems (such as SI seconds and CGS seconds) while
simultaneously enabling unintentional [=system|unit system=] mismatches to be caught
at compile time and preventing potential loss of precision and performance overhead
from unintended conversions
- assignment follows the same rules
- an exception is made for [=quantities=] for which the [=unit=] reduces to a [=dimensionless
quantity=];
in this case, implicit conversion to the underlying value type is allowed via class template
specialization
- [=quantities=] of different value types are implicitly convertible only if the value types
are themselves implicitly convertible
- the [=quantity=] class also defines a `value()` member for directly accessing the underlying
value
There are two distinct types of systems that can be envisioned:
1. Homogeneous systems
Systems which hold a linearly independent set of base units which can be used to represent
many different dimensions. For example, the SI system has seven base dimensions and seven
base units corresponding to them. It can represent any unit which uses only those seven
base dimensions. Thus it is a homogeneous_system.
2. Heterogeneous systems
Systems which store the exponents of every base unit involved are termed heterogeneous.
Some units can only be represented in this way. For example, area in `m ft` is intrinsically
heterogeneous, because the base units of meters and feet have identical dimensions. As
a result, simply storing a dimension and a set of base units does not yield a unique
solution. A practical example of the need for heterogeneous units, is an empirical
equation used in aviation: `H = (r/C)^2` where `H` is the radar beam height in feet and
`r` is the radar range in nautical miles. In order to enforce dimensional correctness of
this equation, the constant, `C`, must be expressed in nautical `miles per foot^(1/2)`,
mixing two distinct base units of length.
```cpp
namespace cgs {
typedef scaled_base_unit<boost::units::si::meter_base_unit,
scale<10, static_rational<-2>>> centimeter_base_unit;
typedef make_system<centimeter_base_unit,
gram_base_unit,
boost::units::si::second_base_unit,
biot_base_unit>::type system;
}
```
```cpp
quantity<si::area> A(1.5*si::meter*cgs::centimeter);
std::cout << 1.5*si::meter*cgs::centimeter << std::endl // prints 1.5 cm m
<< A << std::endl // prints 0.015 m^2
<< std::endl;
```
To provide temperature support Boost.Units define 2 new systems:
```cpp
namespace celsius {
typedef make_system<boost::units::temperature::celsius_base_unit>::type system;
typedef unit<temperature_dimension, system> temperature;
static const temperature degree;
static const temperature degrees;
}
namespace fahrenheit {
typedef make_system<boost::units::temperature::fahrenheit_base_unit>::type system;
typedef unit<temperature_dimension, system> temperature;
static const temperature degree;
static const temperature degrees;
}
```
and a wrapper for handling absolute units (points rather than vectors) to provide affine space support:
```cpp
template<typename Y>
class absolute {
public:
// types
typedef absolute<Y> this_type;
typedef Y value_type;
// construct/copy/destruct
absolute();
absolute(const value_type &);
absolute(const this_type &);
BOOST_CXX14_CONSTEXPR this_type & operator=(const this_type &);
// public member functions
BOOST_CONSTEXPR const value_type & value() const;
BOOST_CXX14_CONSTEXPR const this_type & operator+=(const value_type &);
BOOST_CXX14_CONSTEXPR const this_type & operator-=(const value_type &);
};
```
With above we can:
```cpp
template<class From, class To>
struct conversion_helper {
static BOOST_CONSTEXPR To convert(const From&);
};
typedef conversion_helper<quantity<absolute<fahrenheit::temperature>>,
quantity<absolute<si::temperature>>> absolute_conv_type;
typedef conversion_helper<quantity<fahrenheit::temperature>,
quantity<si::temperature>> relative_conv_type;
quantity<absolute<fahrenheit::temperature>> T1p(32.0 * absolute<fahrenheit::temperature>());
quantity<fahrenheit::temperature> T1v(32.0 * fahrenheit::degrees);
quantity<absolute<si::temperature>> T2p(T1p);
quantity<si::temperature> T2v(T1v);
std::cout << T1p << std::endl // prints 32 absolute F
<< absolute_conv_type::convert(T1p) << std::endl // prints 273.15 absolute K
<< T2p << std::endl // prints 273.15 absolute K
<< T1v << std::endl // prints 32 F
<< relative_conv_type::convert(T1v) << std::endl // prints 17.7778 K
<< T2v << std::endl // prints 17.7778 K
<< std::endl;
```
cppnow17-units {#cppnow17-units}
--------------------------------
Steven Watanabe, the coauthor of the previous library, started the work on the modernized
version of the library based on the results of LiaW on C++Now 2017 [[CPPNOW17-UNITS]]. As
the library was never finished we will not discuss it in details.
### Design ### {#cppnow17-units.design}
The main design is similar to [[BOOST.UNITS]] with one important difference - no systems.
Steven Watanabe provided the following rationale for this design change:
*"My take is that a system is essentially a set of units with linearly independent
dimensions and this can be implemented as a convenience on top of the core functionality.
Boost.Units started out with a design based solely on systems, but that proved to be too
inflexible. We added support for combining individual units, similar to current libraries.
However, having both systems and base units supported directly in the core library results
in a very convoluted design and is one of the main issues that I wanted to fix in a new
library."*
Another interesting design change is the approach for temperatures. With the new design
Celsius and Fahrenheit are always treated as absolute temperatures and only Kelvins can
act as an absolute or relative value.
```cpp
kelvin + kelvin = kelvin
celsius - celsius = kelvin
celsius + kelvin = celsius
```
PhysUnits-CT-Cpp11 {#PhysUnits-CT-Cpp11}
----------------------------------------
[[PHYSUNITS-CT-CPP11]] is the library based on the work of Michael Kenniston from 2001 and
expanded and adapted for C++11 by Martin Moene.
### Usage example ### {#PhysUnits-CT-Cpp11.usage.example}