Bettering our security with a bodily portions and items library
Modifications since [P2981R0]
One of the ways C++ can significantly improve the safety of
applications being written by thousands of developers is by introducing
a type-safe, well-tested, proven in production, standardized way to
handle physical quantities and their units. The rationale is that people
tend to have problems communicating or using proper units in code and
daily life. Another benefit of adding strongly typed quantities and
units to the standard is that it will allow catching some mistakes at
compile-time instead of relying on runtime checks which might have
spotty coverage.
This paper scopes on the safety aspects of introducing such a library
to the C++ standard. In the following chapters, we will describe which
industries are desperately looking for such standardized solutions,
enumerate some failures and accidents caused by misinterpretation of
quantities and units in human history, review all the features of such a
library that improve the safety of our code, and also discuss potential
safety issues that the library does not prevent against.
Note: This paper refers to practices used in the [mp-units] library and presents code
examples utilizing its interfaces. These might not precisely mirror the ultimate
interface design that’s going to be proposed within the follow-up papers.
We’re nonetheless doing a little small fine-tuning to enhance the
library.
This doc persistently makes use of the official metrology vocabulary
outlined within the [ISO/IEC Guide 99]
and [JCGM 200:2012].
Not that way back, self-driving vehicles had been a factor from SciFi motion pictures.
It was one thing so futuristic and onerous to think about that it solely appeared
in motion pictures set within the very far future. At present, autonomous vehicles are
turning into a actuality on our streets even when they aren’t (but) broadly
adopted.
It’s now not solely the area business or airline pilots that
profit from the autonomous operations of some machines. We dwell in a
world the place an increasing number of strange individuals belief machines with their
lives each day. The autonomous automotive is only one instance which is able to have an effect on
our each day life. Medical units, comparable to surgical robots and sensible
well being care units, are already a factor and can see wider adoption in
the long run. And there shall be extra machines with safety- and even
life-critical duties sooner or later. Consequently, many extra C++ engineers
are anticipated to jot down life-critical software program in the present day than a number of years
in the past.
Sadly, expertise on this area is difficult to come back by, and
coaching alone is not going to resolve the difficulty of errors brought on by complicated
items or portions. Moreover, the C++ customary doesn’t change quick
sufficient to implement a safe-by-construction code, which turns into much more
essential if the code dealing with the bodily computation is written by
area consultants comparable to physicists that aren’t essentially fluent in
C++.
When individuals take into consideration industries that might use bodily portions
and unit libraries, they assume of some corporations associated to aerospace,
autonomous vehicles, or embedded industries. That’s all true, however there are
many different potential customers for such a library.
Here’s a listing of some much less apparent candidates:
- Manufacturing,
- maritime business,
- freight transport,
- army,
- astronomy,
- 3D design,
- robotics,
- audio,
- medical units,
- nationwide laboratories,
- scientific establishments and universities,
- every kind of navigation and charting,
- GUI frameworks,
- finance (together with HFT).
As we are able to see, the vary of domains for such a library is huge and
not restricted to functions involving particularly bodily items. Any
software program that includes measurements, or operations on counts of some
customary or domain-specific portions, may gain advantage from a zero-cost
abstraction for working on amount values and their items. The
library additionally gives affine area abstractions, which can show helpful
in lots of functions.
Human historical past is aware of many costly failures and accidents brought on by
errors in calculations involving completely different bodily items. Essentially the most
well-known and possibly the costliest instance within the software program
engineering area is the Mars Local weather Orbiter that in 1999 didn’t
enter Mars’ orbit and crashed whereas getting into its environment [Mars Orbiter]. That is considered one of many
examples right here. Folks are likely to confuse items very often. We see related
errors occurring in numerous domains over time:
- On October 12, 1492, Christopher Columbus unintentionally found
the ocean route from Europe to America as a result of, throughout his journey
preparations, he combined the Arabic mile with a Roman mile, which led to
the improper estimation of the equator and his anticipated journey distance
[Columbus]. - In 1628, a brand new warship, Vasa, unintentionally had an asymmetrical hull
(being thicker on the port facet than the starboard facet), which was one
of the explanations for her sinking lower than a mile into her maiden voyage,
ensuing within the loss of life of 30 individuals on board. This asymmetry might have
been induced by way of completely different programs of measurement, as
archaeologists have discovered 4 rulers utilized by the employees who constructed the
ship. Two had been calibrated in Swedish toes, which had 12 inches, whereas
the opposite two measured Amsterdam toes, which had 11 inches [Vasa]. - Air Canada Flight 143 ran out of gasoline on July 23, 1983, at an
altitude of 41 000 toes (12 000 metres), halfway by means of the flight
as a result of the gasoline had been calculated in kilos as an alternative of kilograms by
the bottom crew [Gimli
Glider]. - The British rock band Black Sabbath, throughout its Born Once more tour in
1983, ordered a duplicate of Stonehenge as props for the scene.
Sadly, they needed to depart them within the storage space as a result of, whereas
submitting the order, their supervisor wrote dimensions down in meters when
he meant toes, and so the stones didn’t match the scene. “It price a
fortune to make, however there was not a constructing on Earth that you would
match it into” [Stonehenge]. - On April 15, 1999, Korean Air Cargo Flight 6316 crashed resulting from a
miscommunication between pilots in regards to the desired flight altitude [Flight 6316]. - In February 2001, the crew of the Moorpark Faculty Zoo constructed an
enclosure for Clarence the Tortoise with a weight of 250 kilos as an alternative
of 250 kilograms [Clarence]. - In December 2003, one of many curler coaster’s vehicles at Tokyo
Disneyland’s Area Mountain attraction all of a sudden derailed resulting from a damaged
axle brought on by confusion after upgrading the specification from imperial
to metric items [Disney]. - Through the development of the Hochrheinbrücke bridge to attach the
small German city of Laufenburg with Swiss Laufenburg, the development
group made an indication error that resulted in a discrepancy of 54 cm between
the 2 outer ends of the bridge [Hochrheinbrücke]. - An American firm bought a cargo of untamed rice to a Japanese
buyer, quoting a value of 39 cents per pound, however the buyer
thought the quote was for 39 cents per kilogram [Wild Rice]. - On October 17, 2023, The Guardian printed an article titled
“File Warmth: Malawi swelters with temperatures practically 68F above
common” with many points associated to the affine area varieties and
temperature items. Because of incorrect logic, most likely throughout the
translation of the article to the U.S. market,
20 °C
above the common
temperature was transformed to
68 °F
. The precise temperature
enhance was32 °F
, not
68 °F
[The Guardian]. - A complete set of [Medication dose
errors]…
On this chapter, we’re going to evaluation typical questions of safety associated
to bodily portions and items within the C++ code when a correct library
isn’t used. Despite the fact that all of the examples come from the Open Supply
initiatives, costly revenue-generating manufacturing supply code typically is
related.
The proliferation of
double
It turns out that in the C++ software, most of our calculations in
the physical quantities and units domain are handled with fundamental
types like double
. Code like
below is a typical example here:
double GlidePolar::MacCreadyAltitude(double MCREADY,
double Distance,
const double Bearing,
const double WindSpeed,
const double WindBearing,
double *BestCruiseTrack,
double *VMacCready,
const bool isFinalGlide,
double *TimeToGo,
const double AltitudeAboveTarget=1.0e6,
const double cruise_efficiency=1.0,
const double TaskAltDiff=-1.0e6);
There are a number of issues with such an strategy: The abundance of
double
parameters makes it simple
to unintentionally change values and there’s no method of noticing such a
mistake at compile-time. The code isn’t self-documenting in what items
the parameters are anticipated. Is
Distance
in meters or
kilometers? Is WindSpeed
in
meters per second or knots? Totally different code bases select other ways
to encode this data, which can be internally inconsistent. A
sturdy sort system would assist reply these questions on the time the
interface is written, and the compiler would confirm it at
compile-time.
The proliferation of magic
numbers
There are a lot of constants and conversion factors involved in the
quantity equations. Source code responsible for such computations is
often trashed with magic numbers:
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)));
}
Other than the plain readability points, such code is difficult to
keep, and it wants quite a lot of area data on the developer’s
facet. Whereas it will be simple to exchange these numbers with named
constants, the query of which unit the fixed is in stays. Is
287.06
in kilos per sq. inch
(psi) or millibars (mbar)?
The proliferation of conversion
macros
The lack of automated unit conversions often results in handwritten
conversion functions or macros that are spread everywhere among the code
base:
#ifndef PI
static const double PI = (4*atan(1));
#endif
#define EARTH_DIAMETER 12733426.0 // Diameter of earth in meters
#define SQUARED_EARTH_DIAMETER 162140137697476.0 // Diameter of earth in meters (EARTH_DIAMETER*EARTH_DIAMETER)
#ifndef DEG_TO_RAD
#define DEG_TO_RAD (PI / 180)
#define RAD_TO_DEG (180 / PI)
#endif
#define NAUTICALMILESTOMETRES (double)1851.96
#define KNOTSTOMETRESSECONDS (double)0.5144
#define TOKNOTS (double)1.944
#define TOFEETPERMINUTE (double)196.9
#define TOMPH (double)2.237
#define TOKPH (double)3.6
// meters to.. conversion
#define TONAUTICALMILES (1.0 / 1852.0)
#define TOMILES (1.0 / 1609.344)
#define TOKILOMETER (0.001)
#define TOFEET (1.0 / 0.3048)
#define TOMETER (1.0)
Once more, the query of which unit the fixed is in stays. With out
wanting on the code, it’s unimaginable to inform from which unit
TOMETER
converts. Additionally, macros
have the issue that they aren’t scoped to a namespace and thus can
simply conflict with different macros or capabilities, particularly if they’ve
such frequent names like PI
or
RAD_TO_DEG
. A fast search
by means of open supply C++ code bases reveals that, for instance, the
RAD_TO_DEG
macro is outlined in a
multitude of various methods – typically even throughout the identical
repository:
#define RAD_TO_DEG (180 / PI)
#define RAD_TO_DEG 57.2957795131
#define RAD_TO_DEG ( radians ) ((radians ) * 180.0 / M_PI)
#define RAD_TO_DEG 57.2957805f
...
Example
search across multiple repositories
Multiple
redefinitions in the same repository
One other security difficulty occurring right here is the truth that macro values can
be intentionally tainted by compiler settings at constructed time and may
purchase values that aren’t current within the supply code. Human evaluations
gained’t catch such points.
Additionally, a lot of the macros don’t comply with finest practices. Usually,
vital parentheses are lacking, processing in a preprocessor finally ends up
with redundant casts, or some compile-time constants use too many digits
for a price to be precise for a particular sort
(e.g. float
).
Lack of consistency
If we not only lack strong types to isolate the abstractions from
each other, but also lack discipline to keep our code consistent, we end
up in an awful place:
void DistanceBearing(double lat1, double lon1,
double lat2, double lon2,
double *Distance, double *Bearing);
double DoubleDistance(double lat1, double lon1,
double lat2, double lon2,
double lat3, double lon3);
void FindLatitudeLongitude(double Lat, double Lon,
double Bearing, double Distance,
double *lat_out, double *lon_out);
double CrossTrackError(double lon1, double lat1,
double lon2, double lat2,
double lon3, double lat3,
double *lon4, double *lat4);
double ProjectedDistance(double lon1, double lat1,
double lon2, double lat2,
double lon3, double lat3,
double *xtd, double *crs);
Customers can simply make errors if the interface designers aren’t
constant in ordering parameters. It’s actually onerous to recollect which
perform takes latitude or
Bearing
first and when a
latitude or Distance
is within the
entrance.
Lack of a conceptual
framework
The previous points mean that the fundamental types can’t be
leveraged to model the different concepts of quantities and units
frameworks. There is no shared vocabulary between different libraries.
User-facing APIs use ad-hoc conventions. Even internal interfaces are
inconsistent between themselves.
Arithmetic types such as int
and double
are used to model
different concepts. They are used to represent any abstraction (be it a
magnitude, difference, point, or kind) of any quantity type of any unit.
These are weak types that make up weakly-typed interfaces. The resulting
interfaces and implementations built with these types easily allow
mixing up parameters and using operations that are not part of the
represented quantity.
This chapter describes the features that enforce safety in our code
bases. It starts with obvious things, but then it moves to probably less
known benefits of using physical quantities and units libraries. This
chapter also serves as a proof that it is not easy to implement such a
library correctly, and that there are many cases where the lack of
experience or time for the development of such a utility may easily lead
to safety issues as well.
Before we go through all the features, it is essential to note that
they do not introduce any runtime overhead over the raw unsafe code
doing the same thing. This is a massive benefit of C++ compared to other
programming languages (e.g., Python, Java, etc.).
Unit conversions
The first thing that comes to our mind when discussing the safety of
such libraries is automated unit conversions between values of the same
physical quantity. This is probably the most important subject here. We
learned about its huge benefits long ago thanks to the
std::chrono::duration
that made
conversions of time durations error-proof.
Unit conversions are typically performed either via a converting
constructor or a dedicated conversion function:
Such a feature benefits from the fact that the library knows about
the magnitudes of the source and destination units at compile-time, and
may use that information to calculate and apply a conversion factor
automatically for the user.
In std::chrono::duration
, the
magnitude of a unit is always expressed with
std::ratio
. This is not enough
for a general-purpose physical units library. Some of the derived units
have huge or tiny ratios. The difference from the base units is so huge
that it cannot be expressed with
std::ratio
, which is implemented
in terms of std::intmax_t
. This
makes it impossible to define units like electronvolt (eV), where 1 eV =
1.602176634×10−19 J, or Dalton (Da), where 1 Da =
1.660539040(20)×10−27 kg. Moreover, some conversions, such as
radian to a degree, require a conversion factor based on an irrational
number like pi.
Preventing truncation of data
The second safety feature of such libraries is preventing accidental
truncation of a quantity value. If we try the operations above with
swapped units, both conversions should fail to compile:
auto q1 = 5 * m;
std::cout << q1.in(km) << 'n'; // Compile-time error
quantity<si::kilo<si::metre>, int> q2 = q1; // Compile-time error
We can’t preserve the value of a source quantity when we convert it
to one with a unit of a lower resolution while dealing with an integral
representation type for a quantity. In the example above, converting
5
meters would result in
0
kilometers if internal
conversion is performed using regular integer arithmetic.
While this could be a valid behavior, the problem arises when the
user expects to be able to convert the quantity back to the original
unit without loss of information. So the library should prevent such
conversions from happening implicitly; [mp-units] presents the named solid
value_cast
for these conversions
marked as unsafe.
To make the above conversions compile, we might use a floating-point
illustration sort:
auto q1 = 5. * m; // source quantity uses `double` as a representation type
std::cout << q1.in(km) << 'n';
quantity<si::kilo<si::metre>> q2 = q1;
or:
auto q1 = 5 * m; // source quantity uses `int` as a representation type
std::cout << value_cast<double>(q1).in(km) << 'n';
quantity<si::kilo<si::metre>> q2 = q1; // `double` by default
The [mp-units] library
follows std::chrono::length
logic and treats floating-point varieties as value-preserving.
One other chance can be to pressure such a truncating conversion
explicitly from the code:
auto q1 = 5 * m; // source quantity uses `int` as a representation type
std::cout << q1.force_in(km) << 'n';
quantity<si::kilo<si::metre>, int> q2 = value_cast<km>(q1);
The code above makes it clear that “something bad” may happen here if
we are not extra careful.
Another case for truncation happens when we assign a quantity with a
floating-point representation type to the one using an integral
representation type for its value:
Such an operation should fail to compile as well. Again, to force
such a truncation, we have to be explicit in the code:
As we can see, it is essential not to allow such truncating
conversions to happen implicitly, and a good physical quantities and
units library should fail at compile-time in case an user makes such a
mistake.
The affine space
The affine space has two types of entities:
- point – a position specified with coordinate values (i.e., location,
address, etc.) - vector – the difference between two points (i.e., shift, offset,
displacement, duration, etc.)
One can do a limited set of operations in affine space on points and
vectors. This greatly helps to prevent quantity equations that do not
have physical sense.
People often think that affine space is needed only to model
temperatures and maybe time points (following the std::chrono::time_point
instance). Nonetheless, the applicability of this idea is way wider.
For instance, if we want to mannequin a Mount Everest climbing
expedition, we might cope with two sorts of altitude-related entities.
The primary can be absolute altitudes above the imply sea degree (factors)
like base camp altitude, mount peak altitude, and many others. The second would
be the heights of each day climbs (vectors). Though it makes bodily
sense so as to add heights of each day climbs, there isn’t a sense in including
altitudes. What does including the altitude of a base camp and the mountain
peak imply in any case?
Modeling such affine area entities with the
amount
(vector) and
quantity_point
(level) class
templates improves the general challenge’s security by solely offering the
operators outlined by the ideas.
express
isn’t express
sufficient
Consider the following structure and a code using it:
Everything works fine for years until, at some point, someone changes
the structure to:
The code continues to compile fine, but all the calculations are now
off by orders of magnitude. This is why a good physical quantities and
units library should not provide an explicit quantity constructor taking
a raw value.
To solve this issue, a quantity in the [mp-units] library all the time requires
details about each a quantity and a unit throughout development:
For consistency and to prevent similar safety issues, the
quantity_point
in the [mp-units] library can’t be created from
a standalone worth of a amount
(opposite to the
std::chrono::time_point
design).
Such some extent has to all the time be related to an express origin:
Obtaining the numerical value
of a quantity
Continuing our previous example, let’s assume that we have an
underlying “legacy” API that requires us to pass a raw numerical value
of a quantity and that we do the following to use it:
The preceding code is incorrect. Even though the duration stores a
quantity equal to 42 s, it is not stored in seconds (it’s either
microseconds or milliseconds, depending on which of the interfaces from
the previous chapter is the current one). Such issues can be prevented
with the usage of
std::chrono::duration_cast
:
However, users often forget about this step, especially when, at the
moment of writing such code, the duration stores the underlying raw
value in the expected unit. But as we know, the interface can be
refactored at any point to use a different unit, and the code using an
underlying numerical value without the usage of an explicit cast will
become incorrect.
To prevent such safety issues, the [mp-units] library exposes solely the
interface that returns a amount numerical worth within the required unit
to make sure that no knowledge truncation occurs:
or in case we are fine with data truncation:
As the above member functions may need to do a conversion to provide
a value in the expected unit, their results are prvalues.
Preventing dangling
references
Besides returning prvalues, sometimes users need to get an actual
reference to the underlying numerical value stored in a
quantity
. For those cases, the
[mp-units] library exposes amount::numerical_value_ref_in(Unit)
that participates in overload decision solely:
- for lvalues (rvalue reference certified overload is explicitly
deleted), - when the offered
Unit
has
the identical magnitude because the one at present utilized by the amount.
The primary situation above goals to restrict the potential of dangling
references. We need to enhance the probabilities that the reference/pointer
offered to an underlying API stays legitimate for the time of its utilization.
(We’re a lot much less involved about efficiency elements for a
amount
right here, as we count on the
majority (if not all) of illustration varieties to be low cost to repeat.)
That mentioned, we acknowledge that this strategy to stopping dangling
references conflates worth class with lifetime. Whereas it might forestall
the vast majority of dangling references, it additionally admits each false positives
and false negatives, as defined in [Value
Category Is Not Lifetime]. We need to spotlight this dilemma
for the committee’s consideration.
In case we do determine to maintain this coverage of deleting rvalue
overloads, right here’s an instance of code that it will forestall from
compiling.
The [mp-units] library
goes one step additional, by implementing all compound assignments,
pre-increment, and pre-decrement operators as non-member capabilities that
protect the preliminary worth class. Due to that, the next will
additionally not compile:
legacy_func((4 * s += 2 * s).numerical_value_ref_in(si::second)); // Compile-time error
legacy_func((++get_duration()).numerical_value_ref_in(si::second)); // Compile-time error
The second condition above enables the usage of various equivalent
units. For example, J
is
equivalent to N * m
, and
kg * m2 / s2
. As those have the
same magnitude, it does not matter exactly which one is being used here,
as the same numerical value should be returned for all of them.
quantity q1 = 42 * J;
quantity q2 = 42 * N * (2 * m);
quantity q3 = 42 * kJ;
legacy_func(q1.numerical_value_ref_in(si::joule)); // OK
legacy_func(q2.numerical_value_ref_in(si::joule)); // OK
legacy_func(q3.numerical_value_ref_in(si::joule)); // Compile-time error
Here are a few examples provided by our users where enabling a
quantity type to return a reference to its underlying numerical value is
required:
-
Interoperability with the Dear
ImGui: -
Obtaining a temperature via the C library:
As we can see in the second example,
quantity_point
also provides an
lvalue-ref-qualified
quantity_ref_from(PointOrigin)
member function that returns a reference to its stored
quantity
. Also, for reasons
similar to the ones described in the previous chapter, this function
requires that the argument provided by the user is the same as the
origin of a quantity point.
Quantity kinds
What should be the result of the following quantity equation?
We have checked a few leading libraries on the market, and here are
the results:
- [Boost.Units]
claims the reply to be 2 Hz (bauds aren’t supported by it, so we
eliminated it from the equation), - [nholthaus/units]
claims it’s 2 s-1 (no help for bauds as effectively), - [Pint] library in Python claims that the
result’s 3.0 Hz, - [JSR 385] library in Java throws an
exception saying that we are able to’t add these portions.
Now let’s verify what [ISO/IEC Guide 99] says about amount
varieties:
- Portions could also be grouped collectively into classes of portions
which might be mutually comparable - Mutually comparable portions are known as portions of the
identical sort - Two or extra portions can’t be added or subtracted except
they belong to the identical class of mutually comparable
portions - Portions of the identical sort inside a given system
of portions have the same amount dimension - Portions of the identical dimension aren’t essentially of the
identical sort
[ISO 80000] additionally explicitly notes:
Measurement items of portions of the same amount
dimension could also be designated by the identical identify and image even when the
portions aren’t of the identical sort. For instance, joule per
kelvin and J/Okay are respectively the identify and image of each a
measurement unit of warmth capability and a measurement unit of entropy,
that are usually not thought of to be portions of the identical sort.
Nonetheless, in some instances particular measurement unit names are
restricted for use with portions of particular sort solely.
For instance, the measurement unit ‘second to the facility minus one’ (1/s)
known as hertz (Hz) when used for frequencies and becquerel (Bq) when
used for actions of radionuclides. As one other instance, the joule (J)
is used as a unit of power, however by no means as a unit of second of pressure,
i.e. the newton metre (N · m).
To summarize the above, [ISO
80000] explicitly states that frequency is measured in Hz and
exercise is measured in Bq, that are portions of various varieties. As
such, they shouldn’t be capable of be in contrast, added, or subtracted. So,
the one library from the above that was appropriate was [JSR 385]. The remainder of them are improper to
enable such operations. Doing so might result in weak questions of safety
when two unrelated portions of the identical dimension are unintentionally
added or assigned to one another.
The explanation for a lot of the libraries in the marketplace to be improper in
this area is the truth that their portions are carried out solely in
phrases of the idea of dimension. Nonetheless, we’ve simply discovered {that a}
dimension isn’t sufficient to precise a amount sort.
The [mp-units] library
goes past that and correctly fashions amount varieties. We imagine that it
is a major characteristic that improves the protection of the library, and
that’s the reason we additionally plan to suggest amount varieties for standardization
as talked about in [P2980R0].
Numerous portions of the identical
sort
Proper modeling of distinct kinds for quantities of the same
dimension is often not enough from the safety point of view. Most of the
libraries allow us to write the following code in the type-safe way:
quantity<isq::speed[m/s]> avg_speed(quantity<isq::length[m]> l, quantity<isq::time[s]> t)
{
return l / t;
}
However, they fail when we need to model an abstraction using more
than one quantity of the same kind:
class Box {
quantity<isq::area[m2]> base_;
quantity<isq::length[m]> height_;
public:
Box(quantity<isq::length[m]> l, quantity<isq::length[m]> w, quantity<isq::length[m]> h)
: base_(l * w), height_(h)
{}
// ...
};
This does not provide strongly typed interfaces anymore.
Again, it turns out that [ISO
80000] has a solution. This specification standardizes
lots of of portions, a lot of that are of the identical sort. For instance,
for portions of the sort size, it gives the next:
As we are able to see, numerous portions of the identical sort aren’t a flat
set. They kind a hierarchy tree which influences
- conversion guidelines, and
- the amount sort being the results of including or subtracting
completely different portions of the identical sort.
The [mp-units] library
might be the primary one in the marketplace (in any programming language)
that fashions such abstractions.
Changing between portions
of the identical sort
Quantity conversion rules can be defined based on the same hierarchy
of quantities of kind length.
-
Implicit conversions
- Every
width
is a
length
. - Every
radius
is a
width
.
static_assert(implicitly_convertible(isq::width, isq::length)); static_assert(implicitly_convertible(isq::radius, isq::length)); static_assert(implicitly_convertible(isq::radius, isq::width));
In the [mp-units] library,
implicit conversions are allowed on copy-initialization: - Every
-
Explicit conversions
- Not every
length
is a
width
. - Not every
width
is a
radius
.
static_assert(!implicitly_convertible(isq::length, isq::width)); static_assert(!implicitly_convertible(isq::length, isq::radius)); static_assert(!implicitly_convertible(isq::width, isq::radius)); static_assert(explicitly_convertible(isq::length, isq::width)); static_assert(explicitly_convertible(isq::length, isq::radius)); static_assert(explicitly_convertible(isq::width, isq::radius));
In the [mp-units] library,
express conversions are pressured by passing the amount to a name
operator of aquantity_spec
sort: - Not every
-
Explicit casts
height
is never a
width
, and vice versa.- Both
height
and
width
are quantities of kind
length
.
static_assert(!implicitly_convertible(isq::height, isq::width)); static_assert(!explicitly_convertible(isq::height, isq::width)); static_assert(castable(isq::height, isq::width));
In the [mp-units] library,
express casts are pressured with a devoted
quantity_cast
perform: -
No conversion
time
has nothing in common
withlength
.
static_assert(!implicitly_convertible(isq::time, isq::length)); static_assert(!explicitly_convertible(isq::time, isq::length)); static_assert(!castable(isq::time, isq::length));
In the [mp-units] library,
even the specific casts is not going to pressure such a conversion:
With the above rules, one can write the following short application
to calculate a fuel consumption:
inline constexpr struct fuel_volume : quantity_spec<isq::volume> {} fuel_volume;
inline constexpr struct fuel_consumption : quantity_spec<fuel_volume / isq::distance> {} fuel_consumption;
const quantity fuel = fuel_volume(40. * l);
const quantity distance = isq::distance(550. * km);
const quantity<fuel_consumption[l / (mag<100> * km)]> q = fuel / distance;
std::cout << "Fuel consumption: " << q << "n";
The above code prints:
Fuel consumption: 7.27273 × 10⁻² l/km
Please note that, despite the dimensions of
fuel_consumption
and
isq::area
being the same (L²),
the constructor of a quantity q
below will fail to compile when we pass an argument being the quantity
of area:
static_assert(fuel_consumption.dimension == isq::area.dimension);
const quantity<isq::area[m2]> football_field = isq::length(105 * m) * isq::width(68 * m);
const quantity<fuel_consumption[l / (mag<100> * km)]> q2 = football_field; // Compile-time error
const quantity q3 = q + football_field; // Compile-time error
if (q == football_field) { // Compile-time error
// ...
}
Comparing, adding, and
subtracting quantities of the same kind
[ISO/IEC Guide 99]
explicitly states that width
and
top
are portions of the
identical sort and as such they
- are mutually comparable, and
- will be added and subtracted.
If we take the above with no consideration, the one affordable results of
1 * width + 1 * top
is
2 * size
, the place the results of
size
is called a typical
amount sort. A results of such an equation is all the time the primary frequent
node in a hierarchy tree of the identical sort. For instance:
static_assert(common_quantity_spec(isq::width, isq::height) == isq::length);
static_assert(common_quantity_spec(isq::thickness, isq::radius) == isq::width);
static_assert(common_quantity_spec(isq::distance, isq::path_length) == isq::path_length);
quantity q = isq::thickness(1 * m) + isq::radius(1 * m);
static_assert(q.quantity_spec == isq::width);
One could argue that allowing to add or compare quantities of height
and width might be a safety issue, but we need to be consistent with the
requirements of [ISO 80000].
Furthermore, from our expertise, disallowing such operations and requiring
an express solid to a typical amount all over the place makes the
code so cluttered with casts that it practically renders the library
unusable.
Fortuitously, the above-mentioned conversion guidelines make the code secure
by development anyway. Let’s analyze the next instance:
inline constexpr struct horizontal_length : quantity_spec<isq::length> {} horizontal_length;
namespace christmas {
struct gift {
quantity<horizontal_length[m]> length;
quantity<isq::width[m]> width;
quantity<isq::height[m]> height;
};
std::array<quantity<isq::length[m]>, 2> gift_wrapping_paper_size(const gift& g)
{
const auto dim1 = 2 * g.width + 2 * g.height + 0.5 * g.width;
const auto dim2 = g.length + 2 * 0.75 * g.height;
return { dim1, dim2 };
}
} // namespace christmas
int main()
{
const christmas::gift lego = { horizontal_length(40 * cm), isq::width(30 * cm), isq::height(15 * cm) };
auto paper = christmas::gift_wrapping_paper_size(lego);
std::cout << "Paper needed to pack a lego box:n";
std::cout << "- " << paper[0] << " X " << paper[1] << "n"; // - 1.05 m X 0.625 m
std::cout << "- area = " << paper[0] * paper[1] << "n"; // - area = 0.65625 m²
}
In the beginning, we introduce a custom quantity
horizontal_length
of a kind
length, which then, together with
isq::width
and
isq::height
, are used to define
the dimensions of a Christmas gift. Next, we provide a function that
calculates the dimensions of a gift wrapping paper with some wraparound.
The result of both those expressions is a quantity of
isq::length
, as this is the
closest common quantity for the arguments used in this quantity
equation.
Regarding safety, it is important to mention here, that thanks to the
conversion rules provided above, it would be impossible to accidentally
do the following:
void foo(quantity<horizontal_length[m]> q);
quantity<isq::width[m]> q1 = dim1; // Compile-time error
quantity<isq::height[m]> q2{dim1}; // Compile-time error
foo(dim1); // Compile-time error
The reason of compilation errors above is the fact that
isq::length
is not implicitly
convertible to the quantities defined based on it. To make the above
code compile, an explicit conversion of a quantity type is needed:
void foo(quantity<horizontal_length[m]> q);
quantity<isq::width[m]> q1 = isq::width(dim1);
quantity<isq::height[m]> q2{isq::height(dim1)};
foo(horizontal_length(dim1));
To summarize, rules for addition, subtraction, and comparison of
quantities improve the library usability, while the conversion rules
enhance the safety of the library compared to the libraries that do not
model quantity kinds.
Modeling a quantity kind
In the physical units library, we also need an abstraction describing
an entire family of quantities of the same kind. Such quantities have
not only the same dimension but also can be expressed in the same
units.
To annotate a quantity to represent its kind we introduced the
kind_of<>
specifier. For
example, to express any quantity of length, we need to type
kind_of<isq::length>
. Such
an entity behaves as any quantity of its kind. This means that it is
implicitly convertible to any quantity in a hierarchy tree.
static_assert(!implicitly_convertible(isq::length, isq::height));
static_assert(implicitly_convertible(kind_of<isq::length>, isq::height));
Additionally, the result of operations on quantity kinds is also a
quantity kind:
static_assert(same_type<kind_of<isq::length> / kind_of<isq::time>, kind_of<isq::length / isq::time>>);
However, if at least one equation’s operand is not a kind, the result
becomes a “strong” quantity where all the kinds are converted to the
hierarchy tree’s root quantities:
static_assert(!same_type<kind_of<isq::length> / isq::time, kind_of<isq::length / isq::time>>);
static_assert(same_type<kind_of<isq::length> / isq::time, isq::length / isq::time>);
Restricting units to specific
quantity kinds
By default, units can be used to measure any kind of quantity with
the same dimension. However, as we have mentioned above, some units
(e.g., Hz, Bq) are constrained to be used only with a specific kind.
Also, base units of the SI are meant to measure all of the quantities of
their kinds. To model this, in the [mp-units] library, we do the
following:
// base units
inline constexpr struct metre : named_unit<"m", kind_of<isq::length>> {} metre;
inline constexpr struct second : named_unit<"s", kind_of<isq::time>> {} second;
// derived units
inline constexpr struct hertz : named_unit<"Hz", 1 / second, kind_of<isq::frequency>> {} hertz;
inline constexpr struct becquerel : named_unit<"Bq", 1 / second, kind_of<isq::activity>> {} becquerel;
inline constexpr struct baud : named_unit<"Bd", 1 / si::second, kind_of<iec80000::modulation_rate>> {} baud;
This means that every time we type
42 * m
, we create a quantity of
a kind length with the length dimension. Such a quantity can be added,
subtracted, or compared to any other quantity of the same kind.
Moreover, it is implicitly convertible to any quantity of its kind.
Again, this could be considered a safety issue as one could type:
const christmas::gift lego = { 40 * cm, 30 * cm, 15 * cm };
auto paper = christmas::gift_wrapping_paper_size(lego);
The above code compiles fine without the need to force specific
quantity types during construction. This is another tradeoff we have to
do here in order to improve the usability. Otherwise, we would need to
type the following every single time we want to initialize an array or
aggregate:
const quantity<isq::position_vector[m], int> measurements[] = { isq::position_vector(30'160 * m),
isq::position_vector(30'365 * m),
isq::position_vector(30'890 * m),
isq::position_vector(31'050 * m),
isq::position_vector(31'785 * m),
isq::position_vector(32'215 * m),
isq::position_vector(33'130 * m),
isq::position_vector(34'510 * m),
isq::position_vector(36'010 * m),
isq::position_vector(37'265 * m) };
As we can see above, it would be really inconvenient. With the
current rules, we type:
const quantity<isq::position_vector[m], int> measurements[] = { 30'160 * m, 30'365 * m, 30'890 * m, 31'050 * m,
31'785 * m, 32'215 * m, 33'130 * m, 34'510 * m,
36'010 * m, 37'265 * m };
which is more user-friendly.
Having such two options also gives users a choice. When we use
different quantities of the same kind in a project (e.g., radius,
wavelength, altitude), we should probably reach for strongly-typed
quantities to bring additional safety for those cases. Otherwise, we can
just use the simple mode for the remaining quantities. We can easily mix
simple and strongly-typed quantities in our projects, and the library
will do its best to protect us based on the information provided.
Non-negative quantities
Some quantity types are defined by [ISO
80000] as explicitly non-negative. These embody portions
like width, thickness, diameter, and radius. Nonetheless, it seems that
it’s potential to have damaging values of portions outlined as
non-negative. For instance,
-1 * isq::diameter[mm]
might
symbolize a change within the diameter of some object. Additionally, a subtraction
4 * width[mm] - 6 * width[mm]
ends in a damaging worth because the width of the second argument is
bigger than the primary one.
Non-negative portions aren’t restricted to these explicitly acknowledged as
being non-negative within the [ISO
80000]. Some portions are implicitly non-negative from
their definition. The obvious instance right here is likely to be scalar
portions specified as magnitudes of a vector amount. For instance,
pace is outlined because the magnitude of velocity. Once more,
-1 * pace[m/s]
might symbolize
a change in common pace between two measurements.
Which means imposing such constraints for amount varieties would possibly
be unimaginable as these sometimes are used to symbolize a distinction
between two states or measurements. Nonetheless, we might apply such
constraints to amount factors, which, by definition, describe the
absolute amount values. For instance, when top is the measure of an
object, a damaging worth is bodily meaningless.
Such logic errors could possibly be detected at runtime with contracts or some
different preconditions or invariants checks.
Vector and tensor portions
While talking about physical quantities and units libraries, everyone
expects that the library will protect (preferably at compile-time) from
accidentally replacing multiplication with division operations or vice
versa. Everyone knows and expects that the multiplication of length and
time should not result in speed. It does not mean that such a quantity
equation is invalid. It just results in a quantity of a different
type.
If we expect the above protection for scalar quantities, we should
also strive to provide similar guarantees for vector and tensor
quantities. First, the multiplication or division of two vectors or
tensors is not even mathematically defined. Such operations should be
impossible on quantities using vector or tensor representation
types.
While multiplication and division are with scalars, the dot and cross
products are for vector quantities. The result of the first one is a
scalar. The second one results in a vector perpendicular to both vectors
passed as arguments. A good physical quantities and units library should
protect the user from making such an error of accidentally replacing
those operations.
Vector and tensor quantities can be implemented in two ways:
-
Encapsulating multiple quantities into a homogeneous vector or
tensor representation typeThis solution is the most common in the C++ market. It requires the
quantities library to provide only basic arithmetic operations
(addition, subtraction, multiplication, and division) which are being
used to calculate the result of linear algebra math. However, this
solution can’t provide any compile-time safety described above, and will
also crash when someone passes a proper vector and tensor representation
type to a quantity, expecting it to work. -
Encapsulating a vector or tensor as a representation type of a
quantityThis provides all the required type safety, but requires the library
to implement more operations on quantities and properly constrain them
so they are selectively enabled when needed. Besides [mp-units], the one library that
helps such an strategy is [Pint]. Such an answer requires the
following operations to be uncovered for amount varieties (observe that
character refers back to the algebraic construction of both scalar, vector and
tensor):a + b
– addition the place each
arguments needs to be of the same amount sort and charactera - b
– subtraction the place
each arguments needs to be of the same amount sort and charactera % b
– modulo the place each
arguments needs to be of the same amount sort and charactera * b
– multiplication the place
one of many arguments needs to be a scalara / b
– division the place the
divisor needs to be scalara ⋅ b
– dot product of two
vectorsa × b
– cross product of two
vectors|a|
– magnitude of a
vectora ⊗ b
– tensor product of
two vectors or tensorsa ⋅ b
– internal product of two
tensorsa ⋅ b
– internal product of
tensor and vectora : b
– scalar product of
two tensors
Moreover, the [mp-units] library
is aware of the anticipated amount character, which is offered (implicitly or
explicitly) within the definition of every amount sort. Due to that, it
prevents the person, for instance, from offering a scalar illustration
sort for pressure or a vector illustration for energy portions.
QuantityOf<isq::velocity> q1 = 60 * km / h; // Compile-time error
QuantityOf<isq::velocity> q2 = la_vector{0, 0, -60} * km / h; // OK
QuantityOf<isq::force> q3 = 80 * kg * (10 * m / s2); // Compile-time error
QuantityOf<isq::force> q4 = 80 * kg * (la_vector{0, 0, -10} * m / s2); // OK
QuantityOf<isq::power> q5 = q2 * q4; // Compile-time error
QuantityOf<isq::power> q5 = dot(q2, q4); // OK
Note: q1
and
q3
can be permitted to compile
by explicitly specializing the
is_vector<T>
trait for the
representation type.
As we can see above, such features additionally improves the
compile-time safety of the library by ensuring that quantities are
created with proper quantity equations and are using correct
representation types.
Integer division
The physical units library can’t do any runtime branching logic for
the division operator. All logic has to be done at compile-time when the
actual values are not known, and the quantity types can’t change at
runtime.
If we expect
120 * km / (2 * h)
to return
60 km / h
, we have to agree with
the fact that 5 * km / (24 * h)
returns 0 km/h
. We can’t do a
range check at runtime to dynamically adjust scales and types based on
the values of provided function arguments.
The same applies to:
This is why floating-point representation types are recommended as a
default to store the numerical value of a quantity. Some popular
physical units libraries even forbid
integer division at all.
The issue is much like the one described within the part about
unintended truncation of values by means of conversion. Whereas the ensuing
use of floating-point illustration varieties could also be a good suggestion, it’s not
all the time potential. Particularly in close-to-the-metal functions and small
embedded programs, using floating-point varieties is typically not an
choice, both for efficiency causes or lack of {hardware} help.
Having completely different operators for secure floating-point operations and unsafe
integer operations would harm generic programming. As such, customers ought to
as an alternative use safer illustration varieties.
Lack of secure numeric varieties
Integers can overflow on arithmetics. This has already caused some
expensive failures in engineering [Ariane
flight V88].
Integers can be truncated throughout task to a narrower
sort.
Floating-point varieties might lose precision throughout task to a
narrower sort. Conversion from
std::int64_t
to
double
can also lose
precision.
If we had secure numeric varieties within the C++ customary library, they may
simply be used as a amount
illustration sort within the bodily portions and items library, which
would handle these security considerations.
Additionally, having a sort trait informing if a conversion from one sort to
one other is value-preserving would assist to deal with among the points
talked about above.
Potential surprises throughout
items composition
One of the most essential requirements for a good physical quantities
and units library is to implement units in such a way that they compose.
With that, one can easily create any derived unit using a simple unit
equation on other base or derived units. For example:
We can also easily obtain a quantity with:
Such a solution is an industry standard and is implemented not only
in [mp-units], but additionally is offered for
a few years now in each [Boost.Units] and [Pint].
We imagine that’s the appropriate factor to do. Nonetheless, we need to make
it straight on this paper that some potential points are related to
such a syntax. Inexperienced customers are sometimes stunned by the outcomes of
the next expression:
This looks like like 30 km/h
,
right? But it is not. Thanks to the order of operations, it results in
30 km⋅h
. In case we want to
divide 60 km
by
2 h
, parentheses are needed:
Another surprising issue may result from the following code:
template<typename T>
auto make_length(T v) { return v * si::metre; }
auto v = 42;
quantity q = make_length(v);
This might look like a good idea, but let’s consider what would
happen if the user provided a quantity as input:
The above function call will result in a quantity of area instead of
the expected quantity of length.
The issues mentioned above could be turned into compilation errors by
disallowing multiplying or dividing a quantity by a unit. The [mp-units] library initially offered
such an strategy, however with time, we determined this to not be
user-friendly. Forcing the person to place the parenthesis round all
derived items in amount equations just like the one beneath, was too verbose
and complicated:
It is important to notice that the problems mentioned above will
always surface with a compile-time error at some point in the user’s
code when they assign the resulting quantity to one with an explicitly
provided quantity type.
Below, we provide a few examples that correctly detect such issues at
compile-time:
quantity<si::kilo<si::metre> / non_si::hour, int> q1 = 60 * km / 2 * h; // Compile-time error
quantity<isq::speed[si::kilo<si::metre> / non_si::hour], int> q2 = 60 * km / 2 * h; // Compile-time error
QuantityOf<isq::speed> auto q3 = 60 * km / 2 * h; // Compile-time error
template<typename T>
auto make_length(T v) { return v * si::metre; }
auto v = 42 * m;
quantity<si::metre, int> q1 = make_length(v); // Compile-time error
quantity<isq::length[si::metre]> q2 = make_length(v); // Compile-time error
QuantityOf<isq::length> q3 = make_length(v); // Compile-time error
template<typename T>
QuantityOf<isq::length> auto make_length(T v) { return v * si::metre; }
auto v = 42 * m;
quantity q = make_length(v); // Compile-time error
template<Representation T>
auto make_length(T v) { return v * si::metre; }
auto v = 42 * m;
quantity q = make_length(v); // Compile-time error
Limitations of systems of
quantities
As stated before, modeling systems of quantities and Various quantities of the
same kind considerably improves the protection of the challenge. Nonetheless,
it’s important to say right here that such modeling isn’t best and
there is likely to be some pitfalls and surprises related to some nook
instances.
Permitting irrational amount
combos
Everyone probably agrees that multiplying two lengths is an area, and
that area should be implicitly convertible to the result of such a
multiplication:
static_assert(implicitly_convertible(isq::length * isq::length, isq::area));
static_assert(implicitly_convertible(isq::area, isq::length * isq::length));
Also, probably no one would be surprised by the fact that the
multiplication of width and height is also convertible to area. Still,
the reverse operation is not valid in this case. Not every area is an
area over width and height, so we need an explicit cast to make such a
conversion:
static_assert(implicitly_convertible(isq::width * isq::height, isq::area));
static_assert(!implicitly_convertible(isq::area, isq::width * isq::height));
static_assert(explicitly_convertible(isq::area, isq::width * isq::height));
However, it might be surprising to some that the similar behavior
will also be observed for the product of two heights:
static_assert(implicitly_convertible(isq::height * isq::height, isq::area));
static_assert(!implicitly_convertible(isq::area, isq::height * isq::height));
static_assert(explicitly_convertible(isq::area, isq::height * isq::height));
For humans, it is hard to imagine how two heights form an area, but
the library’s logic has no way to prevent such operations.
Quantities of dimension one
Some pitfalls might also arise when dealing with quantities of
dimension one (also known as dimensionless quantities).
If we divide two quantities of the same kind, we end up with a
quantity of dimension one. For example, we can divide two lengths to get
a slope of the ramp or two durations to get the clock accuracy. Those
ratios mean something fundamentally different, but from the dimensional
analysis standpoint, they are mutually comparable.
The above means that the following code is valid:
quantity q1 = 1 * m / (10 * m) + 1 * us / (1 * h);
quantity q2 = isq::length(1 * m) / isq::length(10 * m) + isq::time(1 * us) / isq::time(1 * h);
quantity q3 = isq::height(1 * m) / isq::length(10 * m) + isq::time(1 * us) / isq::time(1 * h);
The fact that the above code compiles fine might again be surprising
to some users of such a library. The result of all such quantity
equations is a dimensionless
quantity as it is the root of this hierarchy tree.
Now, let’s try to convert such results to some quantities of
dimension one. The q1
was
obtained from the expression that only used units in the equation, which
means that the actual result of it is a quantity of
kind_of<dimensionless>
,
which behaves like any quantity from the tree. Because of it, all of the
below will compile for q1
:
quantity<si::metre / si::metre> ok1 = q1;
quantity<(isq::length / isq::length)[m / m]> ok2 = q1;
quantity<(isq::height / isq::length)[m / m]> ok3 = q1;
For q2
and
q3
, the two first conversions
also succeed. The first one passes because all quantities of a kind are
convertible to such kind. The type of the second quantity is quantity<dimensionless[one]>
in
disguise. Such a quantity is a root of the kind tree, so again, all the
quantities from such a tree are convertible to it.
The third conversion fails in both cases, though. Not every
dimensionless quantity is a result of dividing height and length, so an
explicit conversion would be needed to force it to work.
quantity<si::metre / si::metre> ok4 = q2;
quantity<(isq::length / isq::length)[m / m]> ok5 = q2;
quantity<(isq::height / isq::length)[m / m]> bad1 = q2;
quantity<si::metre / si::metre> ok6 = q3;
quantity<(isq::length / isq::length)[m / m]> ok7 = q3;
quantity<(isq::height / isq::length)[m / m]> bad2 = q3;
Temperatures
Temperature support is one the most challenging parts of any physical
quantities and units library design. This is why it is probably
reasonable to dedicate a chapter to this subject to describe how they
are intended to work and what are the potential pitfalls or
surprises.
First, let’s run the following code:
quantity q1 = isq::thermodynamic_temperature(30. * K);
quantity q2 = isq::Celsius_temperature(30. * deg_C);
std::println("q1: {}, {}, {}", q1, q1.in(deg_C), q1.in(deg_F));
std::println("q2: {}, {}, {}", q2.in(K), q2, q2.in(deg_F));
This outputs:
q1: 30 K, 30 °C, 54 °F
q2: 30 K, 30 °C, 54 °F
Also doing the following:
outputs:
q3: 30 K
q4: 30 °C
Even though the [ISO 80000] gives
devoted amount varieties for thermodynamic temperature and Celsius
temperature, it explicitly states within the description of the primary
one:
Variations of thermodynamic temperatures or modifications could also be expressed
both in kelvin, image Okay, or in levels Celsius, image °C
Within the description of the second amount sort, we are able to learn:
The unit diploma Celsius is a particular identify for the kelvin to be used in
stating values of Celsius temperature. The unit diploma Celsius is by
definition equal in magnitude to the kelvin. A distinction or interval of
temperature could also be expressed in kelvin or in levels Celsius.
Because the amount
is a
differential amount sort, it’s okay to make use of any temperature unit for
these, and the outcomes ought to differ solely by the conversion issue. No
offset needs to be utilized right here to transform between the origins of
completely different unit scales.
It is very important point out right here that the existence of Celsius
temperature amount sort in [ISO
80000] is controversial.
[ISO 80000] (half 1) says:
The system of portions introduced on this doc is called the
Worldwide System of Portions (ISQ), in all languages. This identify
was not utilized in ISO 31 sequence, from which the current harmonized sequence
has developed. Nonetheless, the ISQ does seem in ISO/IEC Information 99 and is the
system of portions underlying the Worldwide System of Models,
denoted “SI”, in all languages in line with the SI Brochure.
In accordance with the [ISO/IEC Guide 99],
a system of portions is a “set of portions along with a set of
non-contradictory equations relating these portions”. It additionally defines
the system of items as “set of base items and derived items, collectively
with their multiples and submultiples, outlined in accordance with given
guidelines, for a given system of portions”.
To say it explicitly, the system of portions mustn’t assume or
use any particular items in its definitions. It’s important as numerous
programs of items will be outlined on high of it, and none of these ought to
be favored.
Nonetheless, the Celsius temperature amount sort is outlined in [ISO 80000] (half 5) as:
temperature distinction from the thermodynamic temperature of the ice
level known as the Celsius temperature (t), which is outlined by the amount
equation:(t = T − T_0)
the place (T) is thermodynamic
temperature (merchandise 5-1) and (T_0 =
273,15:Okay)
Celsius temperature is an distinctive amount within the ISQ because it makes use of
particular SI items in its definition. This breaks the route of
dependencies between programs of portions and items and imposes
vital implementation points.
As [mp-units]
implementation clearly distinguishes between programs of portions and
items and assumes that the latter straight is dependent upon the previous, this
amount definition doesn’t implement any items or offsets. It’s outlined
as only a extra specialised amount of the type of thermodynamic
temperature. We’ve added the Celsius temperature amount sort for
completeness and to realize extra expertise with it. Nonetheless, perhaps an excellent
resolution can be to skip it within the standardization course of to not
confuse customers.
After quoting the official definitions and phrases and presenting how
portions work, let’s talk about amount factors. These describe particular
factors and are measured relative to a offered origin:
quantity_point qp1 = si::absolute_zero + isq::thermodynamic_temperature(300. * K);
quantity_point qp2 = si::ice_point + isq::Celsius_temperature(30. * deg_C);
The above provides two different temperature points. The first one is
measured as a relative quantity to the absolute zero
(0 K
), and the second one stores
the value relative to the ice point being the beginning of the degree
Celsius scale.
It is essential to understand that the origins of quantity points
will not change if we convert their units:
quantity_point qp3 = qp1.in(deg_C);
quantity_point qp4 = qp2.in(K);
static_assert(std::is_same_v<decltype(qp3.point_origin), decltype(si::absolute_zero)>);
static_assert(std::is_same_v<decltype(qp4.point_origin), decltype(si::ice_point)>);
If we want to obtain the values of quantities in specific units
relative to the origins of their scales, we have to be explicit:
std::println("qp1: {}, {}, {}",
qp1.quantity_from(si::absolute_zero),
qp1.quantity_from(si::ice_point).in(deg_C),
qp1.quantity_from(usc::zero_Fahrenheit).in(deg_F));
std::println("qp2: {}, {}, {}",
qp2.quantity_from(si::absolute_zero).in(K),
qp2.quantity_from(si::ice_point),
qp2.quantity_from(usc::zero_Fahrenheit).in(deg_F));
The above outputs:
qp1: 300 K, 26.85 °C, 80.33 °F
qp2: 303.15 K, 30 °C, 86 °F
Of course, all other combinations are also possible. For example,
nothing should prevent us from checking how many degrees of Celsius are
there starting from the
zero_Fahrenheit
for a quantity
point using kelvins with the
absolute_zero
origin:
Please also note that there is no text output support for quantity
points in the [mp-units]
library.
Structural varieties
The quantity
and
quantity_point
class templates
are structural types to allow them to be passed as template arguments.
For example, we can write the following:
constexpr struct amsterdam_sea_level : absolute_point_origin<isq::altitude> {
} amsterdam_sea_level;
constexpr struct mediterranean_sea_level : relative_point_origin<amsterdam_sea_level + isq::altitude(-27 * cm)> {
} mediterranean_sea_level;
using altitude_DE = quantity_point<isq::altitude[m], amsterdam_sea_level>;
using altitude_CH = quantity_point<isq::altitude[m], mediterranean_sea_level>;
Unfortunately, current language rules require that all member data of
a structural type are public. This could be considered a safety issue.
We try really hard to provide unit-safe interfaces, but at the same time
expose the public “naked” data member that can be freely read or
manipulated by anyone.
Hopefully, this requirement on structural types will be relaxed
before the library gets standardized.
Special thanks and recognition goes to Epam Systems for supporting Mateusz’s
membership within the ISO C++ Committee and the manufacturing of this
proposal.
We’d additionally prefer to thank Peter Sommerlad for offering worthwhile
suggestions that helped us form the ultimate model of this doc.
https://en.wikipedia.org/wiki/Ariane_flight_V88
https://www.boost.org/doc/libs/1_83_0/doc/html/boost_units.html
https://www.latimes.com/archives/la-xpm-2001-feb-09-me-23253-story.html
https://en.wikipedia.org/wiki/Christopher_Columbus
Disneyland Park.
https://web.archive.org/web/20040209033827/http://www.olc.co.jp/news/20040121_01en.html
1999.
https://web.archive.org/web/20210917190721/https://www.ntsb.gov/news/press-releases/Pages/Korean_Air_Flight_6316_MD-11_Shanghai_China_-_April_15_1999.aspx
https://en.wikipedia.org/wiki/Gimli_Glider
bridge.
https://www.normaalamsterdamspeil.nl/wp-content/uploads/2015/03/website_bridge.pdf
https://www.iso.org/standard/76921.html
metrology — Primary and common ideas and related phrases (VIM).
https://www.iso.org/obp/ui#iso:std:iso-iec:guide:99
common ideas and related phrases (VIM) (JCGM 200:2012, third
version).
https://jcgm.bipm.org/vim/en
https://unitsofmeasurement.github.io/indriya
https://en.wikipedia.org/wiki/Mars_Climate_Orbiter
Granas. Treatment dose calculation errors and different numeracy mishaps in
hospitals: Evaluation of the character and enablers of incident stories.
https://onlinelibrary.wiley.com/doi/10.1111/jan.15072
https://mpusz.github.io/mp-units
https://github.com/nholthaus/units
Charles Hogg, Nicolas Holthaus, Roth Michaels, Vincent Reverdy.
2023-10-15. A motivation, scope, and plan for a bodily portions and
items library.
https://wg21.link/p2980r0
2023-10-15. Bettering our security with a bodily portions and items
library.
https://wg21.link/p2981r0
https://pint.readthedocs.io/en/stable/index.html
Spinal Faucet’s Stonehenge.
https://www.telegraph.co.uk/films/2020/05/01/tiny-stones-giant-laughs-story-behind-spinal-taps-stonehenge
temperatures practically 68F above common.
Lifetime.
https://quuxplusone.github.io/blog/2019/03/11/value-category-is-not-lifetime/
Centuries-Previous Swedish Shipwreck.
https://theworld.org/stories/2012-02-23/new-clues-emerge-centuries-old-swedish-shipwreck
https://www.bizjournals.com/eastbay/stories/2001/07/09/focus3.html