From 195b4a7c1ed346338c389daf7baf71f48daaaa5f Mon Sep 17 00:00:00 2001
From: Chip Hogg Several groups in the ISO C++ Committee reviewed the “P1935: A C++
-Approach to Physical Units” [P1935R2] proposal in Belfast 2019 and
+Approach to Physical Units” [P1935R2] proposal in Belfast 2019 and
Prague 2020. All those groups expressed interest in the potential
standardization of such a library and encouraged further work. The
authors also got valuable initial feedback that highly influenced the
-design of the V2 version of the [mp-units] library.Contents
@@ -549,7 +548,7 @@ Contents
standard
std::chrono
-types and
-std::ratio
std::chrono
types and
+std::ratio
Contents
library for quantities and units
double
double
Contents
than most of us imaginequantity
quantity
quantity_point
and
-PointOrigin
+quantity_point
and
+PointOrigin
quantity_point
zeroth_point_origin<QuantitySpec>
quantity_point
zeroth_point_origin<QuantitySpec>
Contents
explicit
is
-not explicit enoughexplicit
is not explicit
+enoughContents
fixed_string
fixed_string
symbol_text
symbol_text
text_encoding
text_encoding
Contents
space_before_unit_symbol
+space_before_unit_symbol
customization point
@@ -852,61 +851,81 @@
Contents
-
Dimension<T> concept
+Dimension<T> concept
QuantitySpec<T> concept
+QuantitySpec<T> concept
Unit<T>
+Unit<T>
concept
Reference<T>
+Reference<T>
concept
Representation<T>
+Representation<T>
concept
Quantity<T>
+Quantity<T>
concept
PointOrigin<T>
+PointOrigin<T>
concept
QuantityPoint<T>
+QuantityPoint<T>
concept
QuantityLike<T>
+QuantityLike<T>
conceptQuantityPointLike<T>
+QuantityPointLike<T>
conceptContents
-
-
quantity
is a numeric
+quantity
is a numeric
wrapper
-
Contents
1 Revision history
-1.1 Changes since [P3045R0]
+1.1 Changes since [P3045R0?]
qp.quantity_from_zero()
-does not work for user’s named origins anymore.qp.quantity_from()
-now works with other quantity points as well.basic_symbol_text
renamed to
-symbol_text
.symbol_text
now always stores
-char8_t
and
-char
-versions of symbols.qp.quantity_from_zero()
does
+not work for user’s named origins anymore.qp.quantity_from()
now works
+with other quantity points as well.basic_symbol_text
renamed to
+symbol_text
.symbol_text
now always
+stores char8_t
and
+char
versions of symbols.u8
) literal.[[nodiscard]]
-removed from basic_symbol_text
u8
) literal.[[nodiscard]]
removed from
+basic_symbol_text
Text output
chapter.mag<ratio{N, D}>
-replaced with mag_ratio<N, D>
-so the ratio
type becomes the
+mag<ratio{N, D}>
+replaced with
+mag_ratio<N, D>
so the
+ratio
type becomes the
implementation detail rather than the public interface of the
library2 Introduction
In the following years, the library’s authors focused on getting more feedback from the production about the design and developed version 2 of -the [mp-units] library that resolves the -issues raised by the users and Committee members. The features and -interfaces of this version are close to being the best we can get with -the current version of the C++ language standard.
-This paper is authored by the [mp-units] library developers, the +the [mp-units] library +that resolves the issues raised by the users and Committee members. The +features and interfaces of this version are close to being the best we +can get with the current version of the C++ language standard.
+This paper is authored by the [mp-units] library developers, the authors of other actively maintained similar libraries on the market, and other active members of the C++ physical quantities and units community who have worked on this subject for many years. We join our @@ -1056,10 +1088,11 @@
Note: This paper is incomplete and many chapters are still
missing. It is published to gather early feedback and possibly get
@@ -1067,32 +1100,28 @@ 2
This document consistently uses the official metrology vocabulary -defined in the [ISO/IEC Guide 99] and [JCGM -200:2012].
+defined in the [ISO/IEC Guide 99] +and [JCGM 200:2012].This change is purely additive. It does not change, fix, or break any of the existing tools in the C++ standard library.
std::chrono
-types and
-std::ratio
std::chrono
types and
+std::ratio
The only interaction of this proposal with the C++ standard
facilities is the compatibility mode with
-std::chrono
-types (duration
and
-time_point
) described in Interoperability
-with the
-std::chrono
+std::chrono
types
+(duration
and
+time_point
) described in Interoperability
+with the std::chrono
abstractions.
We should also mention the potential confusion of users with having two different ways to deal with time abstractions in the C++ standard library. If this proposal gets accepted:
std::chrono
-abstractions together with
-std::ratio
-should be used primarily to deal with calendars and threading
-facilities,std::chrono
abstractions
+together with std::ratio
should
+be used primarily to deal with calendars and threading facilities,fixed_string
fixed_string
While [SI -library] provides many useful features, such as type-safe -conversion between physical quantities as well as zero-overhead -computation for values of the same units, there are some shortcomings -which would require major rework. Instead of creating yet another -library, Dominik decided to join forces with the other authors of this -paper to push for standardizing support for more type-safety for -physical quantities. He hopes that this will eventually lead to a safer -and more robust C++ and open many more opportunities for the -language.
+eventually wrote [SI library] as a side +project. +While [SI library] provides +many useful features, such as type-safe conversion between physical +quantities as well as zero-overhead computation for values of the same +units, there are some shortcomings which would require major rework. +Instead of creating yet another library, Dominik decided to join forces +with the other authors of this paper to push for standardizing support +for more type-safety for physical quantities. He hopes that this will +eventually lead to a safer and more robust C++ and open many more +opportunities for the language.
Johel got interested in the units domain while writing his first
hundred lines of game development. He got up to opening the game window,
so this milestone was not reached until years later. Instead, he looked
for the missing piece of abstraction, called “pixel” in the GUI
framework, but modeled as an
-int
. He
-found out about [nholthaus/units], and got fascinated
+int
. He found out about [nholthaus/units], and got fascinated
with the idea of a library that succinctly allows expressing his
domain’s units (https://github.com/nholthaus/units/issues/124#issuecomment-390773279).
Johel became a contributor to [nholthaus/units] v3 from 2018 to 2020. +
Johel became a contributor to [nholthaus/units] v3 from 2018 to 2020.
He improved the interfaces and implementations by remodeling them after
-std::chrono::duration
.
-This included parameterizing the representation type with a template
+std::chrono::duration
. This
+included parameterizing the representation type with a template
parameter instead of a macro. He also improved the error messages by
mapping a list of types to an user-defined name.
By 2020, Johel had been aware of [mp-units] v0 quantity<dim_length, length, int>
,
+
By 2020, Johel had been aware of [mp-units] v0 quantity<dim_length, length, int>
,
put off by its verbosity. But then, he watched a talk by Mateusz Pusz on
-[mp-units]. It described how good error
+[mp-units]. It described how good error
messages was a stake in the ground for the library. Thanks to his
-experience in the domain, Johel was convinced that [mp-units] was the future.
Since 2020, Johel has been contributing to [mp-units]. He added
-quantity_point
, the generalization
-of std::chrono::time_point
,
-closing #1. He also added
-quantity_kind
, which explored the
-need of representing distinct quantities of the same dimension. To help
-guide its evolution, he’s been constantly pointing in the direction of
-[JCGM
-200:2012] as a source of truth. And more recently, to [ISO/IEC 80000], also helping interpret
+experience in the domain, Johel was convinced that [mp-units] was the future.
Since 2020, Johel has been contributing to [mp-units]. He added
+quantity_point
, the
+generalization of
+std::chrono::time_point
, closing
+#1. He also added quantity_kind
,
+which explored the need of representing distinct quantities of the same
+dimension. To help guide its evolution, he’s been constantly pointing in
+the direction of [JCGM 200:2012] as a
+source of truth. And more recently, to [ISO/IEC 80000], also helping interpret
it.
Computing systems engineering graduate. (C++) programmer since 2014. Lives at HEAD with C++Next and good practices. Performs in-depth code @@ -1260,16 +1287,17 @@
He soon realized that there was a much broader need for Aurora’s units library. No publicly available units library for C++14 or C++17 could match its ergonomics, developer experience, and performance. This -motivated him to create [Au] in 2022: a new, zero-dependency -units library, which was a drop-in replacement for Aurora’s original -units library, but offered far more composable interfaces, and was built -on simpler, stronger foundations. Once Au proved its value internally, -Chip migrated it to a separate repository and led the open-sourcing -process, culminating in its public release in 2023.
+motivated him to create [Au] in 2022: a new, +zero-dependency units library, which was a drop-in replacement for +Aurora’s original units library, but offered far more composable +interfaces, and was built on simpler, stronger foundations. Once Au +proved its value internally, Chip migrated it to a separate repository +and led the open-sourcing process, culminating in its public release in +2023.While Au provides excellent ergonomics and robustness for pre-C++20 users, Chip also believes the C++ community would benefit from a standard units library. For that reason, he has joined forces with the -[mp-units] project, contributing code and +[mp-units] project, contributing code and design ideas.
Nicolas graduated Summa Cum Laude from Northwestern University with a @@ -1282,18 +1310,18 @@
Nicolas became obsessed with dimensional analysis as a high school
JETS team member after learning that the $125M Mars Climate Orbiter was
destroyed due to a simple feet-to-meters miscalculation. He developed
-the widely adopted C++ [nholthaus/units] library based on the
+the widely adopted C++ [nholthaus/units] library based on the
findings of the 2002 white paper “Dimensional Analysis in C++” by Scott
Meyers. Astounded that no one smarter had already written such a
-library, he continued with units
2.0
-and 3.0 based on modern C++. Those libraries have been extensively
+library, he continued with units
+2.0 and 3.0 based on modern C++. Those libraries have been extensively
adopted in many fields, including modeling & simulation,
agriculture, and geodesy.
In 2023, recognizing the limits of
-units
, he joined forces with Mateusz
-Pusz in his effort to standardize his evolutionary dimensional analysis
-library, with the goal of providing the highest-quality dimensional
-analysis to all C++ users via the C++ standard library.
units
, he joined forces with
+Mateusz Pusz in his effort to standardize his evolutionary dimensional
+analysis library, with the goal of providing the highest-quality
+dimensional analysis to all C++ users via the C++ standard library.
Roth Michaels is a Principal Software Engineer at Native Instruments, a leading manufacturer of audio, and music, software and hardware. Working @@ -1304,8 +1332,9 @@
Before working for Native Instruments, Roth worked as a consultant in multiple industries using a variety of programming languages. He was involved with the Swift Evolution community in its early days before @@ -1314,12 +1343,12 @@
Holding a degree in music composition, Roth has over a decade of experience working with quantities and units of measure related to music, digital signal processing, analog audio, and acoustics. He has -joined the [mp-units] project as a domain expert in -these areas and to provide perspective on logarithmic and non-linear -quantity/unit relationships.
+joined the [mp-units] project +as a domain expert in these areas and to provide perspective on +logarithmic and non-linear quantity/unit relationships.Mateusz got interested in the physical units subject while -contributing to the [LK8000] Tactical +contributing to the [LK8000] Tactical Flight Computer Open Source project over 10 years ago. The project’s code was far from being “safe” in the C++ sense, and this is when Mateusz started to explore alternatives.
@@ -1328,9 +1357,9 @@Finally, with the availability of brand new Concepts TS in the gcc-7, -the [mp-units] project was created. It was -designed with safety and user experience in mind. After many years of -working on the project, the [mp-units] library is probably the most +the [mp-units] project +was created. It was designed with safety and user experience in mind. +After many years of working on the project, the [mp-units] library is probably the most modern and complete solution in the C++ market.
Through the last few years, Mateusz has put much effort into building a community around physical units. He provided many talks and workshops @@ -1344,7 +1373,7 @@
After designing and implementing several Domain-Specific Language (DSL) demonstrators dedicated to units of measurements in C++, he became @@ -1365,11 +1394,11 @@
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, standardized way to handle physical quantities @@ -1380,15 +1409,16 @@
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]. This is one of many -examples here. People tend to confuse units quite often. We see similar -errors occurring in various domains over the years:
+[Mars Orbiter]. +This is one of many examples here. People tend to confuse units quite +often. We see similar errors occurring in various domains over the +years:20 °C
above
-the average temperature was converted to
-68 °F
. The
-actual temperature increase was
-32 °F
, not
-68 °F
[The Guardian].20 °C
above the average
+temperature was converted to
+68 °F
. The actual temperature
+increase was 32 °F
, not
+68 °F
[The Guardian].
+The safety subject is so vast and essential by itself that we dedicated an entire Safety features @@ -1447,22 +1478,21 @@
Let’s imagine a world without
-std::string
-or
-std::vector
.
-Every vendor has their version of it, and of course, they are highly
-incompatible with each other. As a result, when someone needs to
-integrate software from different vendors, it turns out to be an
-unnecessarily arduous task.
Introducing std::chrono::duration
-and std::chrono::time_point
-improved the interfaces a lot, but time is only one of many quantities
-that we deal with in our software on a daily basis. We desperately need
-to be able to express more quantities and units in a standardized way so
+std::string
or
+std::vector
. Every vendor has
+their version of it, and of course, they are highly incompatible with
+each other. As a result, when someone needs to integrate software from
+different vendors, it turns out to be an unnecessarily arduous task.
Introducing
+std::chrono::duration
and
+std::chrono::time_point
improved
+the interfaces a lot, but time is only one of many quantities that we
+deal with in our software on a daily basis. We desperately need to be
+able to express more quantities and units in a standardized way so
different libraries get means to communicate with each other.
If Lockheed Martin and NASA could have used standardized vocabulary types in their interfaces, maybe they would not interpret pound-force -seconds as newton seconds, and the [Mars Orbiter] would not have crashed +seconds as newton seconds, and the [Mars Orbiter] would not have crashed during the Mars orbital insertion maneuver.
Mission and life-critical projects, or those for embedded devices, @@ -1500,11 +1530,11 @@
int
, to
-express quantity values, thus losing all semantic categorization. This
-often leads to safety issues caused by accidentally using values
-representing the wrong quantity or having an incorrect unit.
+float
or
+int
, to express quantity values,
+thus losing all semantic categorization. This often leads to safety
+issues caused by accidentally using values representing the wrong
+quantity or having an incorrect unit.
Many applications of a quantity and units library may need to operate on a combination of standard (e.g., SI) and domain-specific quantities @@ -1513,17 +1543,18 @@
Experience with writing ad hoc typed quantities without library
-support that can be combined with or converted to std::chrono::duration
-has shown the downside of bespoke solutions: If not all operations or
-conversions are handled, users will need to leave the safety of typed
-quantities to operate on primitive types.
std::chrono::duration
has shown
+the downside of bespoke solutions: If not all operations or conversions
+are handled, users will need to leave the safety of typed quantities to
+operate on primitive types.
The interfaces of the this library were designed with ease of extensibility in mind. Each definition of a dimension, quantity type, or unit typically takes only a single line of code. This is possible thanks to the extensive usage of C++20 class types as Non-Type Template Parameters (NTTP). For example, the following code presents how second -(a unit of time in the [SI]) and hertz (a unit of frequency in -the [SI]) can be defined:
+(a unit of time in the [SI]) and hertz (a unit of +frequency in the [SI]) can be defined:inline constexpr struct second : named_unit<"s", kind_of<isq::time>> {} second;
inline constexpr struct hertz : named_unit<"Hz", 1 / second, kind_of<isq::frequency>> {} hertz;
It also states that at this time, “in numeric programming,
programmers make heavy, near-exclusive, use of a language’s native
-numeric types (e.g.,
-double
)”.
+numeric types (e.g., double
)”.
Today, twenty-five years later, plenty of “Modern C++” production code
-bases still use
-double
to
+bases still use double
to
represent various quantities and units. It is high time to change
this.
Throughout the years, we have learned the best practices for handling @@ -1581,7 +1610,7 @@
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
. Code like
+below is a typical example here:
-double GlidePolar::MacCreadyAltitude(double MCREADY, double Distance, const double Bearing, @@ -1618,16 +1646,17 @@
in -pounds per square inch (psi) or millibars (mbar)? +Original code here.
There are several problems with such an approach: The abundance of -
+double
-parameters makes it easy to accidentally switch values and there is no -way of noticing such a mistake at compile-time. The code is not -self-documenting in what units the parameters are expected. Is -Distance
in meters or kilometers? Is -WindSpeed
in meters per second or -knots? Different code bases choose different ways to encode this -information, which may be internally inconsistent. A strong type system -would help answer these questions at the time the interface is written, -and the compiler would verify it at compile-time.double
parameters makes it easy +to accidentally switch values and there is no way of noticing such a +mistake at compile-time. The code is not self-documenting in what units +the parameters are expected. Is +Distance
in meters or +kilometers? IsWindSpeed
in +meters per second or knots? Different code bases choose different ways +to encode this information, which may be internally inconsistent. A +strong type system would help answer these questions at the time the +interface is written, and the compiler would verify it at +compile-time.7.2 The proliferation of magic numbers
There are a lot of constants and conversion factors involved in the @@ -1643,8 +1672,8 @@
287.06
287.06
in pounds per square inch +(psi) or millibars (mbar)?7.3 The proliferation of conversion macros
The lack of automated unit conversions often results in handwritten @@ -1678,13 +1707,13 @@
TOMETER converts. Also, macros have -the problem that they are not scoped to a namespace and thus can easily -clash with other macros or functions, especially if they have such -common names like
PI
or -RAD_TO_DEG
. A quick search through -open source C++ code bases reveals that, for example, the -RAD_TO_DEG
macro is defined in a +TOMETER
converts. Also, macros +have the problem that they are not scoped to a namespace and thus can +easily clash with other macros or functions, especially if they have +such common names likePI
or +RAD_TO_DEG
. A quick search +through open source C++ code bases reveals that, for example, the +RAD_TO_DEG
macro is defined in a multitude of different ways – sometimes even within the same repository:-#define RAD_TO_DEG (180 / PI) @@ -1704,7 +1733,7 @@
). +float
float
).7.4 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 @@ -1734,9 +1763,10 @@
Bearing -first and when a latitude or -
Distance
is in the front. +function takes latitude or +Bearing
first and when a +latitude orDistance
is in the +front.7.5 Lack of a conceptual framework
The previous points mean that the fundamental types can’t be @@ -1744,15 +1774,14 @@
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. +Arithmetic types such as
int
+anddouble
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.8 Design goals
The library facilities that we plan to propose in the upcoming papers is designed with the following goals in mind.
@@ -1815,7 +1844,7 @@std::chrono::duration. +case with
std::chrono::duration
. UDLs do not compose, have very limited scope and functionality, and are expensive to standardize.The user interface should have no preprocessor macros.
@@ -1823,7 +1852,7 @@9 Quick domain introduction
This chapter provides a very brief introduction to the quantities and -units domain. Please refer to [ISO/IEC 80000] and [SI] for more details.
+units domain. Please refer to [ISO/IEC 80000] and [SI] for more details. -Some quantity types are defined by [ISO/IEC 80000] as explicitly +
Some quantity types are defined by [ISO/IEC 80000] as explicitly non-negative. Those include quantities like width, thickness, diameter, and radius. However, it turns out that it is possible to have negative -values of quantities defined as non-negative. For example,
-1 * isq::diameter[mm]
-could represent a change in the diameter of some object. Also, a -subtraction4 * width[mm] - 6 * width[mm]
+values of quantities defined as non-negative. For example, +-1 * isq::diameter[mm]
could +represent a change in the diameter of some object. Also, a subtraction +4 * width[mm] - 6 * width[mm]
results in a negative value as the width of the second argument is larger than the first one.Non-negative quantities are not limited to those explicitly stated as -being non-negative in [ISO/IEC 80000]. Some quantities are -implicitly non-negative from their definition. The most obvious example -here might be scalar quantities specified as magnitudes of a vector -quantity. For example, speed is defined as the magnitude of velocity. -Again,
+being non-negative in [ISO/IEC 80000]. +Some quantities are implicitly non-negative from their definition. The +most obvious example here might be scalar quantities specified as +magnitudes of a vector quantity. For example, speed is defined as the +magnitude of velocity. Again, +-1 * speed[m/s]
-could represent a change in average speed between two measurements.-1 * speed[m/s]
could represent +a change in average speed between two measurements.This means that enforcing such constraints for quantity types might be impossible as those typically are used to represent a difference between two states or measurements. However, we could apply such @@ -4435,39 +4485,36 @@
< quantity
This 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 only library that -supports such an approach is [Pint]. Such a solution requires the +so they are selectively enabled when needed. Besides [mp-units], the only library that +supports such an approach is [Pint]. Such a solution requires the following operations to be exposed for quantity types (note that character refers to the algebraic structure of either scalar, vector and tensor):
-
- -
a + b
- -addition where both arguments should be of the same quantity kind and -character- -
a - b
- -subtraction where both arguments should be of the same quantity kind and -character- -
a % b
- -modulo where both arguments should be of the same quantity kind and -character- -
a * b
- -multiplication where one of the arguments has to be a scalar- -
a / b
- -division where the divisor has to be scalara ⋅ b
- dot product of two +- +
a + b
- addition where both +arguments should be of the same quantity kind and character- +
a - b
- subtraction where +both arguments should be of the same quantity kind and character- +
a % b
- modulo where both +arguments should be of the same quantity kind and character- +
a * b
- multiplication where +one of the arguments has to be a scalar- +
a / b
- division where the +divisor has to be scalar- -
a ⋅ b
- dot product of two vectorsa × b
- cross product of two +- -
a × b
- cross product of two vectors- -
|a|
-- magnitude of a vector- -
a ⊗ b
- tensor product of two -vectors or tensorsa ⋅ b
- inner product of two +- +
|a|
- magnitude of a +vector- +
a ⊗ b
- tensor product of +two vectors or tensors- -
a ⋅ b
- inner product of two tensors- -
a ⋅ b
- inner product of tensor -and vector- +
a : b
- -scalar product of two tensors- +
a ⋅ b
- inner product of +tensor and vectora : b
- scalar product of +two tensorsAdditionally, the library knows the expected quantity character, @@ -4481,10 +4528,11 @@
< <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 QuantityOf
Note:
+q1
and -q3
can be permitted to compile by -explicitly specializing theis_vector<T>
-trait for the representation type.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 @@ -4495,12 +4543,13 @@
120 * km / (2 * h) -to return
60 km / h
, -we have to agree with the fact that5 * km / (24 * h)
-returns0 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. +If we expect +
120 * km / (2 * h)
to return +60 km / h
, we have to agree with +the fact that5 * km / (24 * h)
+returns0 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:
static_assert(5 * h / (120 * min) == 0 * one);
This is why floating-point representation types are recommended as a @@ -4518,15 +4567,17 @@
14.2.2 Lack of safe numeric types
Integers can overflow on arithmetics. This has already caused some -expensive failures in engineering [Ariane flight V88].
+expensive failures in engineering [Ariane +flight V88].Integers can also be truncated during assignment to a narrower type.
Floating-point types may lose precision during assignment to a -narrower type. Conversion from
+narrower type. Conversion from +std::int64_t
-todouble
-may also lose precision.std::int64_t
to +double
may also lose +precision.If we had safe numeric types in the C++ standard library, they could -easily be used as a
quantity
+easily be used as aquantity
representation type in the physical quantities and units library, which would address these safety concerns.Also, having a type trait informing if a conversion from one type to @@ -4542,19 +4593,17 @@
We can also easily obtain a quantity with:
= 60 * km / h; quantity q
Such a solution is an industry standard and is implemented not only -in this library, but also is available for many years now in both [Boost.Units] and [Pint].
+in this library, but also is available for many years now in both [Boost.Units] and [Pint].We believe that is the correct thing to do. However, we want to make it straight in this paper that some potential issues are associated with such a syntax. Inexperienced users are often surprised by the results of the following expression:
-= 60 * km / 2 * h; quantity q
This looks like like
30 km/h
, +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:30 km⋅h
. In case we want to +divide60 km
by +2 h
, parentheses are needed:= 60 * km / (2 * h); quantity q
Another surprising issue may result from the following code:
template<typename T> @@ -4569,7 +4618,7 @@
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 provided +disallowing multiplying or dividing a quantity by a unit. The [mp-units] library initially provided such an approach, but with time, we decided this to not be user-friendly. Forcing the user to put the parenthesis around all derived units in quantity equations like the one below, was too verbose @@ -4648,23 +4697,24 @@
= isq::height(1 * m) / isq::length(10 * m) + isq::time(1 * us) / isq::time(1 * h);
quantity q3The 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
+equations is adimensionless
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
+below will compile forq1
was obtained -from the expression that only used units in the equation, which means -that the actual result of it is a quantity ofkind_of<dimensionless>
, +dimension one. Theq1
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 forq1
:q1
:-<si::metre / si::metre> ok1 = q1; quantity<(isq::length / isq::length)[m / m]> ok2 = q1; quantity<(isq::height / isq::length)[m / m]> ok3 = q1; quantity
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 isquantity<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.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 isquantity<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.
@@ -4697,10 +4747,10 @@
q3: 30 K q4: 30 °C
Even though [ISO/IEC 80000] provides dedicated -quantity types for thermodynamic temperature and Celsius -temperature, it explicitly states in the description of the first -one:
+Even though [ISO/IEC 80000] +provides dedicated quantity types for thermodynamic temperature +and Celsius temperature, it explicitly states in the +description of the first one:
Differences of thermodynamic temperatures or changes may be expressed either in kelvin, symbol K, or in degrees Celsius, symbol °C
@@ -4712,14 +4762,14 @@quantity is a differential -quantity type, it is okay to use any temperature unit for -those, and the results should differ only by the conversion factor. No -offset should be applied here to convert between the origins of -different unit scales. +
As the
quantity
is a +differential quantity type, it is okay to use any temperature +unit for those, and the results should differ only by the conversion +factor. No offset should be applied here to convert between the origins +of different unit scales.It is important to mention here that the existence of Celsius -temperature quantity type in [ISO/IEC 80000] is controversial.
-[ISO/IEC 80000] (part 1) says:
+temperature quantity type in [ISO/IEC 80000] is controversial. +[ISO/IEC 80000] (part 1) says:
The system of quantities presented in this document is named the International System of Quantities (ISQ), in all languages. This name @@ -4728,8 +4778,8 @@
[ISO/IEC Guide 99], a system of -quantities is a “set of quantities together with a set of +
According to the [ISO/IEC Guide 99], +a system of quantities is a “set of quantities together with a set of non-contradictory equations relating those quantities”. It also defines the system of units as “set of base units and derived units, together with their multiples and submultiples, defined in accordance with given @@ -4739,7 +4789,7 @@
[ISO/IEC 80000] (part 5) as: +[ISO/IEC 80000] (part 5) as:
temperature difference from the thermodynamic temperature of the ice point is called the Celsius temperature \(t\), which is defined by the quantity @@ -4753,14 +4803,15 @@
[mp-units] implementation clearly -distinguishes between systems of quantities and units and assumes that -the latter directly depends on the former, this quantity definition does -not enforce any units or offsets. It is defined as just a more -specialized quantity of the kind of thermodynamic temperature. -We have added the Celsius temperature quantity type for completeness and -to gain more experience with it. Still, maybe a good decision would be -to skip it in the standardization process not to confuse users. +
As [mp-units] +implementation clearly distinguishes between systems of quantities and +units and assumes that the latter directly depends on the former, this +quantity definition does not enforce any units or offsets. It is defined +as just a more specialized quantity of the kind of thermodynamic +temperature. We have added the Celsius temperature quantity type +for completeness and to gain more experience with it. Still, maybe a +good decision would be to skip it in the standardization process not to +confuse users.
After quoting the official definitions and terms and presenting how quantities work, let’s discuss quantity points. Those describe specific points and are measured relative to a provided origin:
@@ -4768,12 +4819,12 @@= si::zeroth_degree_Celsius + isq::Celsius_temperature(30. * deg_C);
quantity_point qp2
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.
0 K
), and the second one stores
+the value relative to the ice point being the beginning of the degree
+Celsius scale.
Thanks to the
-default_point_origin
used in the
-quantity_point
class template
+default_point_origin
used in the
+quantity_point
class template
definition and benefiting from the fact that units of temperature have
point origins provided in their definitions we can obtain exactly the
same quantity points with the following:
Using quantity_from(PointOrigin)
+
Using
+quantity_from(PointOrigin)
member function:
::println("qp1: {}, {}, {}",
std.quantity_from(si::zeroth_kelvin),
@@ -4809,8 +4861,9 @@ qp1.quantity_from(si::zeroth_kelvin).in(K),
.quantity_from(si::zeroth_degree_Celsius),
qp2.quantity_from(usc::zeroth_degree_Fahrenheit).in(deg_F)); qp2
qp2
Using quantity_from_zero()
-member function that will use the point origin defined for the current
+
Using
+quantity_from_zero()
member
+function that will use the point origin defined for the current
unit:
::println("qp1: {}, {}, {}", fmt.quantity_from_zero(), @@ -4827,15 +4880,15 @@ qp1
for a +zeroth_degree_Fahrenheit
zeroth_degree_Fahrenheit
for a quantity point using kelvins with the -zeroth_kelvin
origin: +zeroth_kelvin
origin:= qp1.quantity_from(usc::zeroth_degree_Fahrenheit).in(deg_C); quantity q
14.2.6 Structural types
-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: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; @@ -4859,8 +4912,9 @@
15This chapter will discuss the different options we have here.
Note: For now, there is no standardized way to handle formatted text input in the C++ standard library, so this paper does not propose -any approach to convert text to quantities. If [P1729R3] will be accepted by the LEWG, -then we will add a proper “Text input” chapter as well.
+any approach to convert text to quantities. If [P1729R3?] will be +accepted by the LEWG, then we will add a proper “Text input” chapter as +well.15.1 Symbols
The definitions of dimensions, units, prefixes, and constants require unique text symbols to be assigned for each entity. Those symbols can be @@ -4868,8 +4922,9 @@
15.1
15.1.1 Symbol definition examples
Note: The below code examples are based on the latest version of -the [mp-units] library and might not be the -final version proposed for standardization.
+the [mp-units] library +and might not be the final version proposed for +standardization.Dimensions:
inline constexpr struct dim_length : base_dimension<"L"> {} dim_length; inline constexpr struct dim_mass : base_dimension<"M"> {} dim_mass; @@ -4925,13 +4980,13 @@
Unicode provides only a minimal set of characters available as subscripts, which are often used to differentiate various constants and -quantities of the same kind. To workaround this issue, [mp-units] uses ’_’ character to specify +quantities of the same kind. To workaround this issue, [mp-units] uses ’_’ character to specify that the following characters should be considered a subscript of the symbol.
15.1.3 Symbols for quantity types
-Although the ISQ defined in [ISO/IEC 80000] provides symbols for each +
Although the ISQ defined in [ISO/IEC 80000] provides symbols for each quantity type, there is little use for them in the C++ code. In the -[mp-units] project, we never had a +[mp-units] project, we never had a request to provide such symbol definitions. Even though having them for completeness could be nice, they seem to not be required by the domain experts for their daily jobs. Also, it is worth noting that providing @@ -4941,14 +4996,14 @@
[ISO/IEC 80000] explicitly states: +definitions, and how those should be printed is exactly specified. [ISO/IEC 80000] explicitly states:
-The quantity symbols shall be written in italic (sloping) type, irrespective of the type used in the rest of the text.
Additionally, [ISO/IEC 80000] provides additional -requirements for printing quantities of vector and tensor -characters:
+Additionally, [ISO/IEC 80000] +provides additional requirements for printing quantities of vector and +tensor characters:
- vectors should be printed with a boldface type or have a right arrow above the letter symbol (e.g., a or \(\mathit{\overrightarrow{a}}\)),
@@ -4960,7 +5015,8 @@[ISO/IEC 80000] +states:
states: +types.The following principles for the printing of subscripts apply:
@@ -4991,7 +5047,7 @@
Symbol definition examples chapter, we proposed to use the ’_’ character instead, as stated in Lack of Unicode subscript characters. We could use the same practice here. -
Another challenge here might be related to the fact that [ISO/IEC 80000] often provides more than +
Another challenge here might be related to the fact that [ISO/IEC 80000] often provides more than one symbol for the same quantity. For example:
As shown above, symbols are provided as class NTTPs in the library. This means that the string type used for such a purpose has to satisfy the structural type requirements of the C++ language. One of such @@ -5090,11 +5146,12 @@
1
- provide at least read-only access to the contained storage.
Such a type does not need to expose a string-like interface. In case -its interface is immutable, we can easily wrap it with
-std::string_view
-to get such an interface for free.This type is being proposed separately in [P3094R0].
+its interface is immutable, we can easily wrap it with +std::string_view
to get such an +interface for free. +This type is being proposed separately in [P3094R0?].
15.1.5 -
+symbol_text
symbol_text
Many symbols of units, prefixes, and constants require using a Unicode character set. For example:
@@ -5122,9 +5179,9 @@
15 prefer to work with a basic literal character set. This is why all such entities should provide an alternative spelling in their definitions. -
This is where
+symbol_text
comes -into play. It is a simple wrapper over the two -fixed_string
objects:This is where
symbol_text
+comes into play. It is a simple wrapper over the two +fixed_string
objects:-template<std::size_t N, std::size_t M> struct symbol_text { <N> unicode_; // exposition only @@ -5167,8 +5224,8 @@ fixed_u8string
15
15.1.6 Symbols for derived entities
15.1.6.1 -
-text_encoding
ISQ and [SI] standards always specify symbols +
text_encoding
+ISQ and [SI] standards always specify symbols using Unicode encoding. This is why it is a default and primary target for text output. However, in some applications or environments, a standard ASCII-like text output using only the characters from the basic literal @@ -5183,23 +5240,26 @@
+15.1.6.2 Symbols of derived dimensions
15.1.6.2.1 -
-dimension_symbol_formatting
dimension_symbol_formatting
is a -data type describing the configuration of the symbol generation +dimension_symbol_formatting
+
dimension_symbol_formatting
+is a data type describing the configuration of the symbol generation algorithm.-struct dimension_symbol_formatting { = text_encoding::default_encoding; text_encoding encoding };
15.1.6.2.2
-dimension_symbol()
Returns a
std::string_view
+15.1.6.2.2 +
+dimension_symbol()
Returns a
std::string_view
with the symbol of a dimension for the provided configuration:-template<dimension_symbol_formatting fmt = dimension_symbol_formatting{}, typename CharT = char, Dimension D> consteval std::string_view dimension_symbol(D);
Note: It could be refactored to
+dimension_symbol(D, fmt)
-when [P1045R1] is available.Note: It could be refactored to +
dimension_symbol(D, fmt)
when +[P1045R1] is available.For example:
-static_assert(dimension_symbol<{.encoding = text_encoding::ascii}>(isq::power.dimension) == "L^2MT^-3");
15.1.6.2.3
+dimension_symbol_to()
15.1.6.2.3 +
dimension_symbol_to()
Inserts the generated dimension symbol into the output text iterator at runtime.
template<typename CharT = char, std::output_iterator<CharT> Out, Dimension D> @@ -5212,10 +5272,11 @@
L^2MT^-3
15.1.6.3 Symbols of derived units
15.1.6.3.1 -
-unit_symbol_formatting
+
unit_symbol_formatting
is a data -type describing the configuration of the symbol generation algorithm. It -contains three orthogonal fields, each with a default value.unit_symbol_formatting
+
unit_symbol_formatting
is a +data type describing the configuration of the symbol generation +algorithm. It contains three orthogonal fields, each with a default +value.-enum class unit_symbol_solidus : std::int8_t { // m/s; kg m⁻¹ s⁻¹ one_denominator, // m/s; kg/(m s) @@ -5234,24 +5295,26 @@ always,
= unit_symbol_solidus::default_denominator; unit_symbol_solidus solidus = unit_symbol_separator::default_separator; unit_symbol_separator separator };
unit_symbol_solidus
impacts how -the division of unit symbols is being presented in the text output. By -default, the ‘/’ will be printed if only one unit component is in the +-
unit_symbol_solidus
impacts +how the division of unit symbols is being presented in the text output. +By default, the ‘/’ will be printed if only one unit component is in the denominator. Otherwise, the exponent syntax will be used.-
unit_symbol_separator
specifies -how multiple multiplied units should be separated from each other. By -default, the space (’ ’) will be used as a separator.15.1.6.3.2
-unit_symbol()
Returns a
std::string_view
++
unit_symbol_separator
+specifies how multiple multiplied units should be separated from each +other. By default, the space (’ ’) will be used as a separator.15.1.6.3.2 +
+unit_symbol()
Returns a
std::string_view
with the symbol of a unit for the provided configuration:-template<unit_symbol_formatting fmt = unit_symbol_formatting{}, typename CharT = char, Unit U> consteval std::string_view unit_symbol(U);
Note: It could be refactored to
+unit_symbol(U, fmt)
-when [P1045R1] is available.Note: It could be refactored to +
unit_symbol(U, fmt)
when [P1045R1] is available.For example:
-static_assert(unit_symbol<{.solidus = unit_symbol_solidus::never, .separator = unit_symbol_separator::half_high_dot}>(kg * m / s2) == "kg⋅m⋅s⁻²");
15.1.6.3.3
+unit_symbol_to()
15.1.6.3.3 +
unit_symbol_to()
Inserts the generated unit symbol into the output text iterator at runtime.
-template<typename CharT = char, std::output_iterator<CharT> Out, Unit U> @@ -5264,39 +5327,39 @@
kg⋅m⋅s⁻²
15.2 -
-space_before_unit_symbol
+space_before_unit_symbol
customization pointThe [SI] says:
+The [SI] says:
The numerical value always precedes the unit and a space is always used to separate the unit from the number. … The only exceptions to this rule are for the unit symbols for degree, minute and second for plane -angle,
+angle,°
, -′
and -″
, respectively, for which no space -is left between the numerical value and the unit symbol.°
, +′
and +″
, respectively, for which no +space is left between the numerical value and the unit symbol.There are more units with such properties. For example, percent -(
+(%
) and per -mille(‰
).%
) and per +mille(‰
).To support the above, the library exposes -
+space_before_unit_symbol
+space_before_unit_symbol
customization point. By default, its value is -true
for all -the units. This means that a number and a unit will be separated by the -space in the output text. To change this behavior, a user should provide -a partial specialization for a specific unit:true
for all the units. This +means that a number and a unit will be separated by the space in the +output text. To change this behavior, a user should provide a partial +specialization for a specific unit:template<> inline constexpr bool space_before_unit_symbol<non_si::degree> = false;
The above works only for the default formatting or for the format -strings that use -
-%?
placement -field (std::format("{}", q)
-is equivalent tostd::format("{:%N%?%U}", q)
).In case a user provides custom format specification (e.g.,
+std::format("{:%N %U}", q)
), +strings that use%?
placement +field +(std::format("{}", q)
+is equivalent tostd::format("{:%N%?%U}", q)
).In case a user provides custom format specification (e.g.,
std::format("{:%N %U}", q)
), the library will always obey this specification for all the units (no matter what the actual value of the -space_before_unit_symbol
+space_before_unit_symbol
customization point is) and the separating space will always be used in this case.15.3 Output streams
@@ -5320,20 +5383,22 @@
::cout << "|" << std::setw(10) << 123 * m << "|\n"; // | 123 m| std::cout << "|" << std::setw(10) << std::left << 123 * m << "|\n"; // |123 m | std::cout << "|" << std::setw(10) << std::setfill('*') << 123 * m << "|\n"; // |123 m*****| std
Detailed formatting of any entity may be obtained with
+std::format()
-usage and then provided to the stream output if needed.Detailed formatting of any entity may be obtained with +
std::format()
usage and then +provided to the stream output if needed.Note: Custom stream manipulators may be provided to control a dimension and unit symbol output if requested by WG21.
15.4 Text formatting
The library provides custom formatters for -
+std::format
-facility, which allows fine-grained control over what and how it is -being printed in the text output.std::format
facility, which +allows fine-grained control over what and how it is being printed in the +text output.15.4.1 Controlling width, fill, and alignment
Formatting grammar for all the entities provides control over width, -fill, and alignment. The C++ standard grammar tokens
fill-and-align
-andwidth
are being used. They treat +fill, and alignment. The C++ standard grammar tokens +fill-and-align
and +width
are being used. They treat the entity as a contiguous text to be aligned. For example, here are a few examples of the quantity numerical value and symbol formatting:-::println("|{:0}|", 123 * m); // |123 m| @@ -5354,30 +5419,31 @@ std
-andfill-and-align
width
tokens are defined in the +- -
fill-and-align
and +width
tokens are defined in the 22.14.2.2 [format.string.std] chapter of the C++ standard specification,text-encoding
-token specifies the symbol text encoding: --
- -
U
(default) uses the -Unicode symbols defined by [ISO/IEC 80000] (e.g., -LT⁻²
),- +
A
forces non-standard -ASCII-only output (e.g.,LT^-2
).text-encoding
token +specifies the symbol text encoding: ++
- +
U
(default) uses the +Unicode symbols defined by [ISO/IEC 80000] (e.g., +LT⁻²
),A
forces non-standard +ASCII-only output (e.g., +LT^-2
).Dimension symbols of some quantities are specified to use Unicode -signs by the ISQ (e.g.,
+signs by the ISQ (e.g.,Θ
symbol for -the thermodynamic temperature dimension). The library follows -this by default. From the engineering point of view, sometimes Unicode -text might not be the best solution as terminals of many (especially -embedded) devices can output only letters from the basic literal -character set only. In such a case, the dimension symbol can be forced -to be printed using such characters thanks totext-encoding
-token:Θ
symbol +for the thermodynamic temperature dimension). The library +follows this by default. From the engineering point of view, sometimes +Unicode text might not be the best solution as terminals of many +(especially embedded) devices can output only letters from the basic +literal character set only. In such a case, the dimension symbol can be +forced to be printed using such characters thanks to +text-encoding
token:-::println("{}", isq::dim_thermodynamic_temperature); // Θ std::println("{:A}", isq::dim_thermodynamic_temperature); // O std::println("{}", isq::power.dimension); // L²MT⁻³ @@ -5394,53 +5460,54 @@ std
-andfill-and-align
width
tokens are defined in the +- -
fill-and-align
and +width
tokens are defined in the 22.14.2.2 [format.string.std] chapter of the C++ standard specification,- -
unit-symbol-solidus
-token specifies how the division of units should look like: --
- ‘1’ (default) outputs -
+/
only when -there is only one unit in the denominator, otherwise -negative exponents are printed (e.g., -m/s
, -kg m⁻¹ s⁻¹
)unit-symbol-solidus
token +specifies how the division of units should look like: ++
- ‘1’ (default) outputs
/
only +when there is only one unit in the denominator, +otherwise negative exponents are printed (e.g., +m/s
, +kg m⁻¹ s⁻¹
)- ‘a’ always uses solidus (e.g., -
+m/s
,kg/(m s)
)m/s
, +kg/(m s)
)- ‘n’ never prints solidus, which means that negative exponents are always used (e.g., -
+m s⁻¹
, -kg m⁻¹ s⁻¹
)m s⁻¹
, +kg m⁻¹ s⁻¹
)unit-symbol-separator
-token specifies how multiplied unit symbols should be separated: +unit-symbol-separator
token +specifies how multiplied unit symbols should be separated:
- ‘s’ (default) uses space as a separator (e.g., -
+kg m²/s²
)kg m²/s²
)- ‘d’ uses half-high dot -(
+(⋅
) as a separator (e.g., -kg⋅m²/s²
) -(requires the Unicode encoding)⋅
) as a separator (e.g., +kg⋅m²/s²
) (requires the Unicode +encoding)- ‘L’ is reserved for possible future localization use in case C++ standard library gets access to the ICU-like database.
Note: The intent of the above grammar was that the elements of -
+unit-spec
-can appear in any order as they have unique characters. Users shouldn’t -have to remember the order of those tokens to control the formatting of -a unit symbol.unit-spec
can appear in any +order as they have unique characters. Users shouldn’t have to remember +the order of those tokens to control the formatting of a unit +symbol.Unit symbols of some quantities are specified to use Unicode signs by -the [SI] (e.g., -
Ω
symbol for the resistance -quantity). The library follows this by default. From the engineering -point of view, sometimes Unicode text might not be the best solution as -terminals of many (especially embedded) devices can output only letters -from the basic literal character set only. In such a case, the unit -symbol can be forced to be printed using such characters thanks totext-encoding
+the [SI] (e.g., +Ω
symbol for the +resistance quantity). The library follows this by default. From +the engineering point of view, sometimes Unicode text might not be the +best solution as terminals of many (especially embedded) devices can +output only letters from the basic literal character set only. In such a +case, the unit symbol can be forced to be printed using such characters +thanks totext-encoding
token:-::println("{}", si::ohm); // Ω std::println("{:A}", si::ohm); // ohm @@ -5448,17 +5515,17 @@ std
::println("{:A}", us); // us std::println("{}", m / s2); // m/s² std::println("{:A}", m / s2); // m/s^2 std
Additionally, both [ISO/IEC 80000] and [SI] leave some freedom on how to print +
Additionally, both [ISO/IEC 80000] and +[SI] leave some freedom on how to print unit symbols. This is why two additional tokens were introduced.
-+
unit-symbol-solidus
-specifies how the division of units should look like. By default, -/
will be -used only when the denominator contains only one unit. However, with the -‘a’ or ‘n’ options, we can force the facility to print the -/
character -always (even when there are more units in the denominator), or never, in -which case a parenthesis will be added to enclose all denominator -units.
unit-symbol-solidus
specifies +how the division of units should look like. By default, +/
will be used only when the +denominator contains only one unit. However, with the ‘a’ or ‘n’ +options, we can force the facility to print the +/
character always (even when +there are more units in the denominator), or never, in which case a +parenthesis will be added to enclose all denominator units.::println("{}", m / s); // m/s std::println("{}", kg / m / s2); // kg m⁻¹ s⁻² std::println("{:a}", m / s); // m/s @@ -5466,24 +5533,24 @@ std
::println("{:n}", m / s); // m s⁻¹ std::println("{:n}", kg / m / s2); // kg m⁻¹ s⁻² std
Also, there are a few options to separate the units being multiplied. -[ISO/IEC 80000] (part 1) says:
+[ISO/IEC 80000] (part 1) says:-When symbols for quantities are combined in a product of two or more quantities, this combination is indicated in one of the following ways: -
+ab
, -a b
, -a · b
, -a × b
ab
, +a b
, +a · b
, +a × b
NOTE 1 In some fields, e.g., vector algebra, distinction is -made between
+made betweena ∙ b
and -a × b
.a ∙ b
and +a × b
.The library supports
-a b
and -a · b
only. Additionally, we decided -that the extraneous space in the latter case makes the result too -verbose, so we decided just to use the -·
symbol as a separator.The
unit-symbol-separator
+The library supports
+a b
and +a · b
only. Additionally, we +decided that the extraneous space in the latter case makes the result +too verbose, so we decided just to use the +·
symbol as a separator.The
unit-symbol-separator
token allows us to obtain the following outputs:@@ -5504,13 +5571,13 @@::println("{}", kg * m2 / s2); // kg m²/s² std::println("{:d}", kg * m2 / s2); // kg⋅m²/s² std
fill-and-align -and
width
tokens are defined in the +- -
fill-and-align
and +width
tokens are defined in the 22.14.2.2 [format.string.std] chapter of the C++ standard specification,placement-type
-token specifies which entity should be put and where: +- +
placement-type
token +specifies which entity should be put and where:
- ‘N’ inserts a default-formatted numerical value of the quantity,
@@ -5518,32 +5585,34 @@space_before_unit_symbol for this -unit,
space_before_unit_symbol
for +this unit,- ‘%’ just inserts ‘%’ character.
-defaults-specs
-token allows overwriting defaults for the underlying formatters with the -custom format string. Each override starts with a subentity identifier -(‘N’, ‘U’, or ‘D’) followed by the format string enclosed in square +defaults-specs
token allows +overwriting defaults for the underlying formatters with the custom +format string. Each override starts with a subentity identifier (‘N’, +‘U’, or ‘D’) followed by the format string enclosed in square brackets.15.4.4.1 Default formatting
-To format
+quantity
values, the -formatting facility usesquantity-format-spec
. -If left empty, the default formatting is applied. The same default -formatting is also applied to the output streams. This is why the -following code lines produce the same output:To format
quantity
values, +the formatting facility uses +quantity-format-spec
. If left +empty, the default formatting is applied. The same default formatting is +also applied to the output streams. This is why the following code lines +produce the same output:-::cout << "Distance: " << 123 * km << "\n"; std::cout << std::format("Distance: {}\n", 123 * km); std::cout << std::format("Distance: {:%N%?%U}\n", 123 * km); std
Please note that for some quantities the
{:%N %U}
-format may provide a different output than the default one, as some -units havespace_before_unit_symbol
+Please note that for some quantities the +
+{:%N %U}
format may provide a +different output than the default one, as some units have +space_before_unit_symbol
customization point explicitly set to -false
(e.g., -%
and -°
).false
(e.g., +%
and +°
).15.4.4.2 Quantity numerical value, unit symbol, or both?
Thanks to the grammar provided above, the user can easily decide to @@ -5566,17 +5635,17 @@
- +
placement-type
-greatly simplify element access to the elements of the quantity. Without -them the second case above would require the following:
placement-type
greatly +simplify element access to the elements of the quantity. Without them +the second case above would require the following:-const auto q = 120 * km / h; ::println("Speed:\n- number: {}\n- unit: {}\n- dimension: {}", std.numerical_value_ref_in(q.unit), q.unit, q.dimension); q
default-spec
-is crutial to provide formatting of user-defined representation types. -Initially, [mp-units] library was providing +
default-spec
is crutial to +provide formatting of user-defined representation types. Initially, +[mp-units] library was providing numerical value modifiers inplace of its format specification similarly -tostd::chrono::duration
+tostd::chrono::duration
formatter. However, it:
- worked only with fundamental arithmetic types and was not able to @@ -5591,30 +5660,29 @@
-The representation type used as a numerical value of a quantity must provide its own formatter specialization. It will be called by the quantity formatter with the format-spec provided by the user in the -
N
defaults specification. +N
defaults specification.In case we use C++ fundamental arithmetic types with our quantities the standard formatter specified in format.string.std will be used. The rest of this chapter assumes that it is the case and provides some usage examples.
-+
sign
token allows us to specify -how the value’s sign is being printed:
sign
token allows us to +specify how the value’s sign is being printed:::println("{0},{0::N[+]},{0::N[-]},{0::N[ ]}", 1 * m); // 1 m,+1 m,1 m, 1 m std::println("{0},{0::N[+]},{0::N[-]},{0::N[ ]}", -1 * m); // -1 m,-1 m,-1 m,-1 m std
where:
-
-- -
+
-indicates that a sign should be used for both non-negative and negative -numbers,- -
-
-indicates that a sign should be used for negative numbers and negative -zero only (this is the default behavior),- +
<space>
-indicates that a leading space should be used for non-negative numbers -other than negative zero, and a minus sign for negative numbers and -negative zero.- +
+
indicates that a sign +should be used for both non-negative and negative numbers,- +
-
indicates that a sign +should be used for negative numbers and negative zero only (this is the +default behavior),<space>
indicates that +a leading space should be used for non-negative numbers other than +negative zero, and a minus sign for negative numbers and negative +zero.+
precision
token is allowed only -for floating-point representation types:
precision
token is allowed +only for floating-point representation types:-::println("{::N[.0]}", 1.2345 * m); // 1 m std::println("{::N[.1]}", 1.2345 * m); // 1 m std::println("{::N[.2]}", 1.2345 * m); // 1.2 m @@ -5622,8 +5690,8 @@ std
::println("{::N[.0f]}", 1.2345 * m); // 1 m std::println("{::N[.1f]}", 1.2345 * m); // 1.2 m std::println("{::N[.2f]}", 1.2345 * m); // 1.23 m std
+
type
specifies how a value of the -representation type is being printed. For integral types:
type
specifies how a value of +the representation type is being printed. For integral types:::println("{::N[b]}", 42 * m); // 101010 m std::println("{::N[B]}", 42 * m); // 101010 m std::println("{::N[d]}", 42 * m); // 42 m @@ -5631,14 +5699,14 @@ std
::println("{::N[x]}", 42 * m); // 2a m std::println("{::N[X]}", 42 * m); // 2A m std
The above can be printed in an alternate version thanks to the -
+#
token:#
token:::println("{::N[#b]}", 42 * m); // 0b101010 m std::println("{::N[#B]}", 42 * m); // 0B101010 m std::println("{::N[#o]}", 42 * m); // 052 m std::println("{::N[#x]}", 42 * m); // 0x2a m std::println("{::N[#X]}", 42 * m); // 0X2A m std
For floating-point values, the -
+type
token works as follows:type
token works as follows:::println("{::N[a]}", 1.2345678 * m); // 1.3c0ca2a5b1d5dp+0 m std::println("{::N[.3a]}", 1.2345678 * m); // 1.3c1p+0 m std::println("{::N[A]}", 1.2345678 * m); // 1.3C0CA2A5B1D5DP+0 m @@ -5667,34 +5735,34 @@ std
for a -quantity point may mean many things. It may be an offset from the -mountain top, sea level, or maybe the center of Mars. Printing -42 m
42 m AMSL
-for altitudes above mean sea level is a much better solution, but the -library does not have enough information to print it that way by -itself. +42 m
for a quantity point may +mean many things. It may be an offset from the mountain top, sea level, +or maybe the center of Mars. Printing +42 m AMSL
for altitudes above +mean sea level is a much better solution, but the library does not have +enough information to print it that way by itself.15.6 Text output open questions
@@ -5705,10 +5773,12 @@
- Should we somehow provide text support for quantity points? What about temperatures?
- Are we OK with no text output support of quantity types?
-- How to name a non-Unicode accessor member function (e.g.,
-.ascii()
)? -The same name should- Should
+unit_symbol()
and -dimension_symbol()
-returnstd::string_view
-orbasic_fixed_string
?- How to name a non-Unicode accessor member function (e.g., +
+.ascii()
)? The same name +should- Should
unit_symbol()
and +dimension_symbol()
return +std::string_view
or +basic_fixed_string
?- What about the localization for units? Will we get something like ICU in the C++ standard? consistently be used in -
+text_encoding
and in the formatting -grammar.text_encoding
and in the +formatting grammar.- Do we care about ostreams enough to introduce custom manipulators to format dimensions and units?
-- +
std::chrono::duration
-uses ‘Q’ and ‘q’ for a number and a unit. In the grammar above, we -proposed using ‘N’ and ‘U’ for them, respectively. We also introduced -‘D’ for dimensions. Are we OK with this?std::chrono::duration
uses +‘Q’ and ‘q’ for a number and a unit. In the grammar above, we proposed +using ‘N’ and ‘U’ for them, respectively. We also introduced ‘D’ for +dimensions. Are we OK with this?- Are we OK with the usage of ’_’ for denoting a subscript identifier?
16.1 Conventions
16.1.1 New style of definitions
-The [mp-units] library decided to use a -rather unusual pattern to define entities, but it proved really -successful, and we got great feedback from users so far.
-Here is how we define metre and second [SI] base units:
+The [mp-units] library +decided to use a rather unusual pattern to define entities, but it +proved really successful, and we got great feedback from users so +far.
+Here is how we define metre and second [SI] +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;
Please note that the above reuses the same identifier for a type and @@ -5736,12 +5806,12 @@
16.1.3 Entities composability
Many physical units libraries (in C++ or any other programming language) assign strong types to library entities (e.g., derived units). -While
metre_per_second
as a type may -not look too scary, consider, for example, units of angular momentum. If -we followed this path, its coherent unit would look like -kilogram_metre_sq_per_second
. Now, -consider how many scaled versions of this unit you would predefine in -the library to ensure that all users are happy with your choice? How +Whilemetre_per_second
as a type +may not look too scary, consider, for example, units of angular +momentum. If we followed this path, its coherent unit would look like +kilogram_metre_sq_per_second
. +Now, consider how many scaled versions of this unit you would predefine +in the library to ensure that all users are happy with your choice? How expensive would it be from the implementation point of view? How expensive would it be to standardize?This is why, in this library, we put a strong requirement to make @@ -5759,8 +5829,9 @@
auto q = la_vector{1, 2, 3} * isq::angular_momentum[kg * m2 / s];
It is a much better solution. It is terse and easy to understand. Please also notice how easy it is to obtain any scaled version of such a -unit (e.g.,
+unit (e.g., +mg * square(mm) / min
) -without having to introduce hundreds of types to predefine them.mg * square(mm) / min
) without +having to introduce hundreds of types to predefine them.16.1.4 Value-based equations
This library is based on C++20, significantly improving user experience. One such improvement is the usage of value-based @@ -5770,9 +5841,8 @@
std::ratio. -
For example, below are a few definitions of the [SI] derived units showing the power of +equations for
+std::ratio
.For example, below are a few definitions of the [SI] derived units showing the power of C++20 extensions to Non-Type Template Parameters, which allow us to directly pass a result of the value-based unit equation to a class template definition:
@@ -5786,7 +5856,7 @@Quick domain introduction chapter. Below we describe the remaining ones.
16.2.1 Quantity character
-[ISO/IEC 80000] explicitly states that +
[ISO/IEC 80000] explicitly states that quantities (even of the same kind) may have different characters:
- scalar,
@@ -5794,14 +5864,15 @@quantity_character enumeration: +
quantity_character
+enumeration:enum class quantity_character { scalar, vector, tensor };
More information on quantity characters can be found in the [Character of a quantity] chapter.
16.2.2 Quantity specification
-Dimension is not enough to describe a quantity. This is why [ISO/IEC 80000] provides hundreds of +
Dimension is not enough to describe a quantity. This is why [ISO/IEC 80000] provides hundreds of named quantity types. It turns out that there are many more quantity -types in the ISQ than the named units in the [SI].
+types in the ISQ than the named units in the [SI].This is why the library introduces a quantity specification entity that stores:
@@ -5813,24 +5884,23 @@
isq::length, -
isq::mass
, -isq::time
, -isq::electric_current
, -isq::thermodynamic_temperature
, -isq::amount_of_substance
, -andisq::luminous_intensity
-are the specifications of base quantities in the ISQ.- -
isq::width
, -isq::height
, -isq::radius
, -andisq::position_vector
-are only a few of many quantities of a kind length specified in the -ISQ.- +
isq::area
, -isq::speed
, -isq::moment_of_force
-are only a few of many derived quantities provided in the ISQ.- +
isq::length
, +isq::mass
, +isq::time
, +isq::electric_current
, +isq::thermodynamic_temperature
, +isq::amount_of_substance
, and +isq::luminous_intensity
are the +specifications of base quantities in the ISQ.- +
isq::width
, +isq::height
, +isq::radius
, and +isq::position_vector
are only a +few of many quantities of a kind length specified in the ISQ.isq::area
, +isq::speed
, +isq::moment_of_force
are only a +few of many derived quantities provided in the ISQ.16.2.3 Unit
A unit is a concrete amount of a quantity that allows us to measure @@ -5838,24 +5908,23 @@
16.2.3
For example:
-
- -
si::second
, -si::metre
, -si::kilogram
, -si::ampere
, -si::kelvin
, -si::mole
, -and -si::candela
-are the base units of the [SI].- -
si::kilo<si::metre>
-is a prefixed unit of length.- -
si::radian
, -si::newton
, -andsi::watt
-are examples of named derived units within the [SI].- -
non_si::minute
is -an example of a scaled unit of time.si::si2019::speed_of_light_in_vacuum
+- +
si::second
, +si::metre
, +si::kilogram
, +si::ampere
, +si::kelvin
, +si::mole
, and +si::candela
are the base units +of the [SI].- +
si::kilo<si::metre>
is +a prefixed unit of length.- +
si::radian
, +si::newton
, and +si::watt
are examples of named +derived units within the [SI].- +
non_si::minute
is an example +of a scaled unit of time.si::si2019::speed_of_light_in_vacuum
is a physical constant standardized by the SI in 2019.Note: In this library, physical constants are also implemented as @@ -5865,8 +5934,8 @@
+bool) types -are treated as scalars.
bool
) types are treated as +scalars.16.2.5 Point origin
In the affine space theory, the point origin specifies where the “zero” of our measurement’s scale is.
@@ -5892,57 +5961,61 @@16.3
Note 2: Initially, C++20 was meant to use -
-CamelCase
for all the concept +CamelCase
for all the concept identifiers. Frustratingly, -CamelCase
concepts got dropped from -the C++ standard at the last moment before releasing C++20. Now, we are -facing the predictable consequences of running out of names. As long as -some concepts in the library could be easily named with a -standard_case
there are some that -are hard to distinguish from the corresponding type names, such as -Quantity
, -QuantityPoint
, -QuantitySpec
, or -Reference
. This is why we decided to -useCamelCase
consistently for all -the concept identifiers to make it clear when we are talking about a -type or concept identifier. However, we are aware that this might be a -temporary solution. In case the library gets standardized, we can expect -the LEWG to bikeshed/rename all of the concept identifiers to a -standard_case
, even if it will +CamelCase
concepts got dropped +from the C++ standard at the last moment before releasing C++20. Now, we +are facing the predictable consequences of running out of names. As long +as some concepts in the library could be easily named with a +standard_case
there are some +that are hard to distinguish from the corresponding type names, such as +Quantity
, +QuantityPoint
, +QuantitySpec
, or +Reference
. This is why we +decided to useCamelCase
+consistently for all the concept identifiers to make it clear when we +are talking about a type or concept identifier. However, we are aware +that this might be a temporary solution. In case the library gets +standardized, we can expect the LEWG to bikeshed/rename all of the +concept identifiers to a +standard_case
, even if it will result in a harder to understand code.16.3.1
-Dimension<T> concept
Dimension
concept matches a +16.3.1 +
+Dimension<T> concept
Dimension
concept matches a dimension of either a base or derived quantity:-
- Base dimensions are explicitly defined by the user by inheriting from the instantiation of a -
+base_dimension
class template. It -should be instantiated with a unique symbol identifier describing this -dimension in a specific system of quantities.base_dimension
class template. +It should be instantiated with a unique symbol identifier describing +this dimension in a specific system of quantities.- Derived dimensions are implicitly created by the library’s framework based on the quantity equation provided in the quantity specification.
16.3.1.1
DimensionOf<T, V>
+16.3.1.1 +
-DimensionOf<T, V>
concept
DimensionOf
concept is satisfied -when both arguments satisfy aDimension
+-
DimensionOf
concept is +satisfied when both arguments satisfy aDimension
concept and when they compare equal.16.3.2
-QuantitySpec<T> concept
+
QuantitySpec
concept matches all -the quantity specifications including:16.3.2 +
+QuantitySpec<T> concept
QuantitySpec
concept matches +all the quantity specifications including:
- Base quantities defined by a user by inheriting from the -
quantity_spec
class template +quantity_spec
class template instantiated with a base dimension argument.- Derived named quantities defined by a user by inheriting from the -
quantity_spec
class template +quantity_spec
class template instantiated with a result of a quantity equation passed as an argument.- Other named quantities forming a hierarchy of quantities of the same kind defined by a user by inheriting from the -
quantity_spec
class template +quantity_spec
class template instantiated with another “parent” quantity specification passed as an argument.- Quantity kinds describing a family of mutually comparable @@ -5950,19 +6023,20 @@
16.3.2.1
QuantitySpecOf<T, V>
+16.3.2.1 +
-QuantitySpecOf<T, V>
concept+
QuantitySpecOf
concept is -satisfied when both arguments satisfy aQuantitySpec
-concept and whenT
is implicitly -convertible toV
.
QuantitySpecOf
concept is +satisfied when both arguments satisfy aQuantitySpec
+concept and whenT
is implicitly +convertible toV
.Additionally:
-
- -
T
should not be a nested -quantity specification ofV
- either
+T
is quantity kind or -V
should not be a nested quantity -specification ofT
- +
T
should not be a nested +quantity specification ofV
- either
T
is quantity kind or +V
should not be a nested +quantity specification ofT
Those additional conditions are required to make the following work:
@@ -5971,140 +6045,147 @@static_assert(!ReferenceOf<isq::angular_measure[si::radian], dimensionless>); static_assert(ReferenceOf<one, isq::angular_measure>); static_assert(!ReferenceOf<dimensionless[one], isq::angular_measure>);
16.3.3
-Unit<T>
-concept
Unit
concept matches all the +16.3.3 +
+Unit<T>
concept
Unit
concept matches all the units in the library including:
- Base units defined by a user by inheriting from the -
named_unit
class template +named_unit
class template instantiated with a unique symbol identifier describing this unit in a specific system of units.- Named scaled units defined by a user by inheriting from the -
named_unit
class template +named_unit
class template instantiated with a unique symbol identifier and a product of multiplying another unit with some magnitude.- Prefixed units defined by a user by inheriting from the -
prefixed_unit
class template +prefixed_unit
class template instantiated with a prefix symbol, a magnitude, and a unit to be prefixed.- Derived named units defined by a user by inheriting from the -
named_unit
class template +named_unit
class template instantiated with a unique symbol identifier and a result of unit equation passed as an argument.- Derived unnamed units being a result of a unit equations on other units.
Note: Physical constants are also implemented as units.
-16.3.3.1
AssociatedUnit<T>
+16.3.3.1 +
-AssociatedUnit<T>
concept+
AssociatedUnit
concept describes -a unit with an associated quantity and is satisfied by:
AssociatedUnit
concept +describes a unit with an associated quantity and is satisfied by:-
- All units derived from a -
named_unit
class template -instantiated with a unique symbol identifier and aQuantitySpec
+named_unit
class template +instantiated with a unique symbol identifier and aQuantitySpec
of a quantity kind.- All units being a result of unit equations on other associated units.
All units in the [SI] have associated quantities. For -example, -
+si::second
-is specified to measure -isq::time
.All units in the [SI] have associated +quantities. For example, +
si::second
is specified to +measureisq::time
.Natural units typically do not have an associated quantity. For -example, if we assume
-c = 1
, -anatural::second
-unit can be used to measure both -time
and -length
. In such case, -speed
would have a unit of -one
.16.3.3.2
PrefixableUnit<T>
+example, if we assumec = 1
, a +natural::second
unit can be used +to measure bothtime
and +length
. In such case, +speed
would have a unit of +one
. +16.3.3.2 +
-PrefixableUnit<T>
concept
PrefixableUnit
concept is +-
PrefixableUnit
concept is satisfied by all units derived from a -named_unit
class template for which -a customization pointunit_can_be_prefixed<T{}>
-was not explicitly set to -false
. Such -units can be passed as an argument to a -prefixed_unit
class template.All units in the [SI] can be prefixed with SI-defined -prefixes.
+named_unit
class template for +which a customization point +unit_can_be_prefixed<T{}>
+was not explicitly set tofalse
. +Such units can be passed as an argument to a +prefixed_unit
class +template. +All units in the [SI] can be prefixed with +SI-defined prefixes.
Some off-system units like -
+non_si::day
-can’t be prefixed. To enforce that, the following has to be -provided:non_si::day
can’t be prefixed. +To enforce that, the following has to be provided:-template<> inline constexpr bool unit_can_be_prefixed<non_si::day> = false;
16.3.3.3
-UnitOf<T, V>
-concept
UnitOf
concept is satisfied for -all unitsT
matching anAssociatedUnit
+16.3.3.3 +
+UnitOf<T, V>
concept-
UnitOf
concept is satisfied +for all unitsT
matching anAssociatedUnit
concept with an associated quantity type implicitly convertible to -V
.Additionally, the kind of
-V
and -the kind of quantity type associated with -T
must be the same, or the quantity -type associated withT
may not be -derived from the kind ofV
.This condition is required to make
-dimensionless[si::radian]
-invalid as -si::radian
-should be only used forisq::angular_measure
, -which is a nested quantity kind within the dimensionless quantities -tree.16.3.3.4
UnitCompatibleWith<T, V1, V2>
+V
. +Additionally, the kind of
+V
+and the kind of quantity type associated with +T
must be the same, or the +quantity type associated withT
+may not be derived from the kind of +V
.This condition is required to make +
+dimensionless[si::radian]
+invalid assi::radian
should be +only used for +isq::angular_measure
, which is a +nested quantity kind within the dimensionless quantities tree.16.3.3.4
-UnitCompatibleWith<T, V1, V2>
concept-
UnitCompatibleWith
concept is -satisfied for all unitsT
when:-
- -
V1
is aUnit
,- -
V2
is aQuantitySpec
,- -
T
and -V1
are defined in terms of the same -reference unit,- if
+T
is anAssociatedUnit
-it should satisfyUnitOf<V2>
.+
UnitCompatibleWith
concept is +satisfied for all unitsT
+when:+
-- +
V1
is aUnit
,- +
V2
is aQuantitySpec
,- +
T
and +V1
are defined in terms of the +same reference unit,- if
T
is anAssociatedUnit
+it should satisfyUnitOf<V2>
.16.3.4
-Reference<T>
-concept-
Reference
concept is satisfied by -all quantity reference types. Such types provide all the -meta-information required to create aQuantity
.A
-Reference
can either be:-
- An
-AssociatedUnit
.- The instantiation of a
+reference
-class template with aQuantitySpec
-passed as the first template argument and aUnit
passed -as the second one.16.3.4 +
+Reference<T>
concept+
Reference
concept is +satisfied by all quantity reference types. Such types provide all the +meta-information required to create aQuantity
.A
+Reference
can either +be:+
-- An
+AssociatedUnit
.- The instantiation of a +
reference
class template with a +QuantitySpec
+passed as the first template argument and aUnit
+passed as the second one.16.3.4.1
ReferenceOf<T, V>
+16.3.4.1 +
-ReferenceOf<T, V>
concept
ReferenceOf
concept is satisfied -by referencesT
which have a -quantity specification that satisfiesQuantitySpecOf<V>
+-
ReferenceOf
concept is +satisfied by referencesT
which +have a quantity specification that satisfiesQuantitySpecOf<V>
concept. |16.3.5
Representation<T>
+16.3.5 +
-Representation<T>
concept
Representation
concept +-
Representation
concept constraints a type of a number that stores the value of a quantity.16.3.5.1
RepresentationOf<T, Ch>
+16.3.5.1 +
-RepresentationOf<T, Ch>
concept
RepresentationOf
concept is -satisfied by allRepresentation
++
RepresentationOf
concept is +satisfied by allRepresentation
types that are of a specified quantity character -Ch
.Ch
.A user can declare a custom representation type to be of a specific character by providing the specialization with -
+true
for one -or more of the following variable templates:true
for one or more of the +following variable templates:-
- -
is_scalar<T>
- -
is_vector<T>
- +
is_tensor<T>
- +
is_scalar<T>
- +
is_vector<T>
is_tensor<T>
If we want to use scalar types to also express vector quantities (e.g., ignoring the “direction” of the vector) the following definition @@ -6112,63 +6193,69 @@
template<class T> requires is_scalar<T> inline constexpr bool is_vector<T> = true;
16.3.6
-Quantity<T>
-concept-
Quantity
concept matches every -quantity in the library and is satisfied by all types being or deriving -from an instantiation of aquantity
-class template.16.3.6.1
-QuantityOf<T, V>
-concept-
QuantityOf
concept is satisfied -by all the quantities for which aQuantitySpecOf<V>
-istrue
.16.3.7
-PointOrigin<T>
-concept+
PointOrigin
concept matches all -quantity point origins in the library. It is satisfied by either:16.3.6 +
+Quantity<T>
concept+
Quantity
concept matches +every quantity in the library and is satisfied by all types being or +deriving from an instantiation of a +quantity
class template.16.3.6.1 +
+QuantityOf<T, V>
concept+
QuantityOf
concept is +satisfied by all the quantities for which aQuantitySpecOf<V>
+istrue
.16.3.7 +
+PointOrigin<T>
concept
PointOrigin
concept matches +all quantity point origins in the library. It is satisfied by +either:-
- All types derived from an -
absolute_point_origin
class +absolute_point_origin
class template.- All types derived from a -
relative_point_origin
class +relative_point_origin
class template.16.3.7.1
PointOriginFor<T, V>
+16.3.7.1 +
-PointOriginFor<T, V>
concept
PointOriginFor
concept is -satisfied by allPointOrigin
+-
PointOriginFor
concept is +satisfied by allPointOrigin
types that have quantity type implicitly convertible from quantity -specificationV
, which means that -V
must satisfyQuantitySpecOf<T::quantity_spec>
.For example,
+specificationsi::ice_point
can -serve as a point origin for points ofisq::Celsius_temperature
-because this quantity type implicitly converts toisq::thermodynamic_temperature
.V
, which means +thatV
must satisfyQuantitySpecOf<T::quantity_spec>
. +For example,
si::ice_point
+can serve as a point origin for points of +isq::Celsius_temperature
because +this quantity type implicitly converts to +isq::thermodynamic_temperature
.However, if we define -
mean_sea_level
in the following +mean_sea_level
in the following way:inline constexpr struct mean_sea_level : absolute_point_origin<isq::altitude> {} mean_sea_level;
then it can’t be used as a point origin for points of -
+isq::length
-or -isq::width
-as none of them is implicitly convertible toisq::altitude
:isq::length
or +isq::width
as none of them is +implicitly convertible to +isq::altitude
:-
- not every length is an altitude,
- width is not compatible with altitude.
16.3.8
-QuantityPoint<T>
-concept
QuantityPoint
concept is +16.3.8 +
+QuantityPoint<T>
concept-
QuantityPoint
concept is satisfied by all types being either a specialization or derived from -quantity_point
class template.16.3.8.1
QuantityPointOf<T, V>
+quantity_point
class +template. +16.3.8.1 +
-QuantityPointOf<T, V>
concept
QuantityPointOf
concept is ++
QuantityPointOf
concept is satisfied by all the quantity points -T
that match the following value -V
:T
that match the following value +V
:-
@@ -6177,7 +6264,7 @@
-V
+V
Condition @@ -6186,44 +6273,46 @@-
- QuantitySpec
The quantity point quantity specification satisfies QuantitySpecOf<V>
++ QuantitySpec
The quantity point quantity specification satisfies QuantitySpecOf<V>
concept.- - PointOrigin
The point and +V
have -the same absolute point origin.+ PointOrigin
The point and V
+have the same absolute point origin.16.3.9
-QuantityLike<T>
-concept
QuantityLike
concept provides +16.3.9 +
+QuantityLike<T>
concept
QuantityLike
concept provides interoperability with other libraries and is satisfied by a type -T
for which an instantiation of -quantity_like_traits
type trait +T
for which an instantiation of +quantity_like_traits
type trait yields a valid type that provides:-
-- Static data member
reference
-that matches theReference
+- Static data member
-reference
+that matches theReference
concept,rep
type that matchesRepresentationOf
+- -
rep
type that matchesRepresentationOf
concept with the character provided in -reference
.- +
to_numerical_value(T)
-static member function returning a raw value of the quantity packed in -eitherconvert_explicitly
or -convert_implicitly
wrapper that +reference
.- -
to_numerical_value(T)
static +member function returning a raw value of the quantity packed in either +convert_explicitly
or +convert_implicitly
wrapper that enables implicit conversion in the latter case.from_numerical_value(rep)
-static member function returningT
-packed in eitherconvert_explicitly
-orconvert_implicitly
wrapper that +from_numerical_value(rep)
+static member function returning +T
packed in either +convert_explicitly
or +convert_implicitly
wrapper that enables implicit conversion in the latter case.For example, this is how support for
+std::chrono::seconds
-can be provided:For example, this is how support for +
std::chrono::seconds
can be +provided:-template<> struct quantity_like_traits<std::chrono::seconds> { static constexpr auto reference = si::second; @@ -6242,39 +6331,42 @@
= 42s; quantity q ::chrono::seconds dur = 42 * s; std
16.3.10
QuantityPointLike<T>
+16.3.10 +
-QuantityPointLike<T>
concept
QuantityPointLike
concept +
QuantityPointLike
concept provides interoperability with other libraries and is satisfied by a -typeT
for which an instantiation of -quantity_point_like_traits
type +typeT
for which an +instantiation of +quantity_point_like_traits
type trait yields a valid type that provides:-
-- Static data member
reference
-that matches theReference
+- Static data member
-reference
+that matches theReference
concept.- Static data member
point_origin
-that matches thePointOrigin
+- Static data member +
-point_origin
that matches thePointOrigin
concept.rep
type that matchesRepresentationOf
+- -
rep
type that matchesRepresentationOf
concept with the character provided in -reference
.- +
to_quantity(T)
-static member function returning the -quantity
being the offset of the -point from the origin packed in either -convert_explicitly
or -convert_implicitly
wrapper that +reference
.- -
to_quantity(T)
static member +function returning thequantity
+being the offset of the point from the origin packed in either +convert_explicitly
or +convert_implicitly
wrapper that enables implicit conversion in the latter case.from_quantity(quantity<reference, rep>)
-static member function returningT
-packed in eitherconvert_explicitly
-orconvert_implicitly
wrapper that +from_quantity(quantity<reference, rep>)
+static member function returning +T
packed in either +convert_explicitly
or +convert_implicitly
wrapper that enables implicit conversion in the latter case.For eample, this is how support for a
+std::chrono::time_point
-ofstd::chrono::seconds
-can be provided:For eample, this is how support for a +
std::chrono::time_point
of +std::chrono::seconds
can be +provided:template<typename C> struct quantity_point_like_traits<std::chrono::time_point<C, std::chrono::seconds>> { using T = std::chrono::time_point<C, std::chrono::seconds>; @@ -6305,34 +6397,46 @@
-
The operations exposed by such a library should include at least:
-
- multiplication (e.g.
-newton * metre
),- division (e.g.
-metre / second
),- power (e.g.
+pow<2>(metre)
-orpow<1, 2>(metre * metre)
).- multiplication +(e.g.
+newton * metre
),- division +(e.g.
+metre / second
),- power +(e.g.
pow<2>(metre)
or +pow<1, 2>(metre * metre)
).To improve the usability of the library, we also recommend adding:
-
- square root (e.g.
-sqrt(metre * metre)
-as equivalent topow<1, 2>(metre * metre)
),- cubic root (e.g.
-cbrt(metre * metre * metre)
-as equivalent topow<1, 3>(metre * metre * metre)
),- inversion (e.g.
+inverse(second)
-as equivalent to -one / second
).- square root +(e.g.
+sqrt(metre * metre)
as +equivalent to +pow<1, 2>(metre * metre)
),- cubic root +(e.g.
+cbrt(metre * metre * metre)
+as equivalent topow<1, 3>(metre * metre * metre)
),- inversion +(e.g.
inverse(second)
as +equivalent to +one / second
).Additionally, for units only, to improve the readability of the code, it makes sense to expose the following:
-
- square power (e.g.
-square(metre)
-is equivalent topow<2>(metre)
),- cubic power (e.g.
+cubic(metre)
-is equivalent topow<3>(metre)
).- square power +(e.g.
+square(metre)
is +equivalent to +pow<2>(metre)
),- cubic power +(e.g.
cubic(metre)
is equivalent +topow<3>(metre)
).The above two functions could also be considered for dimensions and -quantity types. However,
+quantity types. However, +cubic(length)
-does not seem to make much sense, and probablypow<3>(length)
-should be preferred instead.cubic(length)
does not seem to +make much sense, and probably +pow<3>(length)
should be +preferred instead.16.5 Expression templates
Modern C++ physical quantities and units libraries use opaque types to improve the user experience while analyzing compile-time errors or @@ -6351,15 +6455,15 @@
metre / second * second
- +
metre * metre / metre
- +
metre / second * second
metre * metre / metre
Both of them should result in a type equivalent to -
metre
. A general-purpose library +metre
. A general-purpose library will probably result with the types similar to the below:-
- -
mul<div<metre, second>, second>
- +
div<mul<metre, metre>, metre>
- +
mul<div<metre, second>, second>
div<mul<metre, metre>, metre>
Comparing such types for equivalence would not only be very expensive at compile-time but would also be really confusing to the users @@ -6392,48 +6496,48 @@
-[mp-units] +[mp-units]
- - N⋅m
- derived_unit<metre, newton>
+ UnitProduct<Meters, Newtons>
+ N⋅m
+ derived_unit<metre, newton>
UnitProduct<Meters, Newtons>
- - 1/s
- derived_unit<one, per<second>>
+ Pow<Seconds, -1>
+ 1/s
+ derived_unit<one, per<second>>
Pow<Seconds, -1>
- - km/h
- derived_unit<kilo_<metre>, per<hour>>
+ UnitProduct<Kilo<Meters>, Pow<Hours, -1>>
+ km/h
+ derived_unit<kilo_<metre>, per<hour>>
UnitProduct<Kilo<Meters>, Pow<Hours, -1>>
- - kg⋅m²/(s³⋅K)
- derived_unit<kilogram, pow<metre, 2>, per<kelvin, power<second, 3>>>
+ UnitProduct<Pow<Meters, 2>, Kilo<Grams>, Pow<Seconds, -3>, Pow<Kelvins, -1>>
+ kg⋅m²/(s³⋅K)
+ derived_unit<kilogram, pow<metre, 2>, per<kelvin, power<second, 3>>>
UnitProduct<Pow<Meters, 2>, Kilo<Grams>, Pow<Seconds, -3>, Pow<Kelvins, -1>>
- - m²/m
- metre
+ Meters
+ m²/m
+ metre
Meters
- - km/m
- derived_unit<kilo_<metre>, per<metre>>
+ UnitProduct<Pow<Meters, -1>, Kilo<Meters>>
+ km/m
+ derived_unit<kilo_<metre>, per<metre>>
UnitProduct<Pow<Meters, -1>, Kilo<Meters>>
- @@ -6442,11 +6546,11 @@- m/m
- one
+ UnitProduct<>
+ m/m
+ one
UnitProduct<>
derived_ +consistently uses the
derived_
prefix for types representing derived units, dimensions, and quantity specifications. Those are instantiated first with the contents of the numerator followed by the entities of the denominator (if present) -enclosed in theper<...>
+enclosed in theper<...>
expression template.16.5.2 Identities
The arithmetics on units, dimensions, and quantity types require a @@ -6455,13 +6559,14 @@
16. expression template on multiplication.
We chose the following names here:
-
-- -
one
in the domain of units,- -
dimension_one
in the domain of -dimensions,- +
dimensionless
in the domain of -quantity types.- +
one
in the domain of +units,- +
dimension_one
in the domain +of dimensions,dimensionless
in the domain +of quantity types.The above names were selected based on the following quote from [ISO/IEC 80000]:
+The above names were selected based on the following quote from [ISO/IEC 80000]:
A quantity whose dimensional exponents are all equal to zero has the dimensional product denoted A0B0C0… = @@ -6497,58 +6602,59 @@
- - A * B
+ A, B
+ A * B
A, B
- - B * A
+ A, B
+ B * A
A, B
- - A * A
+ power<A, 2>
+ A * A
power<A, 2>
- - {identity} * A
+ A
+ {identity} * A
A
- - A * {identity}
+ A
+ A * {identity}
A
- - A / B
+ A, per<B>
+ A / B
A, per<B>
- - A / A
+ {identity}
+ A / A
{identity}
- - A / {identity}
+ A
+ A / {identity}
A
- - {identity} / A
+ {identity}, per<A>
+ {identity} / A
{identity}, per<A>
- - pow<2>(A)
+ power<A, 2>
+ pow<2>(A)
power<A, 2>
- - pow<2>({identity})
+ {identity}
+ pow<2>({identity})
{identity}
- - sqrt(A)
-orpow<1, 2>(A)
+ power<A, 1, 2>
+ sqrt(A)
+orpow<1, 2>(A)
power<A, 1, 2>
- @@ -6565,7 +6671,7 @@- sqrt({identity})
-orpow<1, 2>({identity})
+ {identity}
+ sqrt({identity})
+or +pow<1, 2>({identity})
{identity}
This is probably the most important of all the steps, as it allows comparing types and enables the rest of the simplification rules. Units and dimensions have unique symbols, but ordering quantity types -might not be that trivial. Although the ISQ defined in [ISO/IEC 80000] provides symbols for each +might not be that trivial. Although the ISQ defined in [ISO/IEC 80000] provides symbols for each quantity, there is little use for them in the C++ code. This is caused by the fact that such symbols use a lot of characters that are not available with the Unicode encoding. Most of the limitations correspond @@ -6602,20 +6708,20 @@
- - A, A
+ power<A, 2>
+ A, A
power<A, 2>
- - A, power<A, 2>
+ power<A, 3>
+ A, power<A, 2>
power<A, 3>
- - power<A, 1, 2>, power<A, 2>
+ power<A, 5, 2>
+ power<A, 1, 2>, power<A, 2>
power<A, 5, 2>
- @@ -6636,35 +6742,34 @@- power<A, 1, 2>, power<A, 1, 2>
+ A
+ power<A, 1, 2>, power<A, 1, 2>
A
- - A, per<A>
+ {identity}
+ A, per<A>
{identity}
- - power<A, 2>, per<A>
+ A
+ power<A, 2>, per<A>
A
- - power<A, 3>, per<A>
+ power<A, 2>
+ power<A, 3>, per<A>
power<A, 2>
- - A, per<power<A, 2>>
+ {identity}, per<A>
+ A, per<power<A, 2>>
{identity}, per<A>
It is important to notice here that only the elements with exactly the same type are being simplified. This means that, for example, -
m/m
results -inone
, but -km/m
will -not be simplified. The resulting derived unit will preserve both symbols -and their relative magnitude. This allows us to properly print symbols -of some units or constants that require such behavior. For example, the -Hubble constant is expressed in -km⋅s⁻¹⋅Mpc⁻¹
, where both -km
and -Mpc
are units of +m/m
results in +one
, but +km/m
will not be simplified. The +resulting derived unit will preserve both symbols and their relative +magnitude. This allows us to properly print symbols of some units or +constants that require such behavior. For example, the Hubble constant +is expressed inkm⋅s⁻¹⋅Mpc⁻¹
, +where bothkm
and +Mpc
are units of length.- @@ -6717,18 +6822,22 @@
Repacking
In case an expression uses two results of some other operations, the @@ -6686,28 +6791,28 @@
- - X * B
+ A
+ X * B
A
- - X * A
+ power<A, 2>, per<B>
+ X * A
power<A, 2>, per<B>
- - X * X
+ power<A, 2>, per<power<B, 2>>
+ X * X
power<A, 2>, per<power<B, 2>>
- - X / X
+ {identity}
+ X / X
{identity}
- - X / A
+ {identity}, per<B>
+ X / A
{identity}, per<B>
- - X / B
+ A, per<power<B, 2>>
+ X / B
A, per<power<B, 2>>
Unit symbols are not guaranteed to be unique in the project. For -example, someone may use
+example, someone may use +"s"
as a -symbol for a count of samples, which, when used in a unit expression -with seconds, would cause fatal consequences (e.g.,sample * second
-would yields²
, orsample / second
-would result inone
)."s"
as a symbol for a +count of samples, which, when used in a unit expression with seconds, +would cause fatal consequences (e.g., +sample * second
would yield +s²
, or +sample / second
would result in +one
).Some units would provide worse text output if the ordering step used -type identifiers rather than unit symbols. For example,
+type identifiers rather than unit symbols. For example,si::metre * si::second * cgs::second
-would result ins m s
, ornewton * metre
-would result inm N
, which is not -how we typically spell this unit. However, for the sake of consistency, -we may also consider changing the algorithm used for ordering to be -based on type identifiers.si::metre * si::second * cgs::second
+would result ins m s
, or +newton * metre
would result in +m N
, which is not how we +typically spell this unit. However, for the sake of consistency, we may +also consider changing the algorithm used for ordering to be based on +type identifiers.16.5.5 Expression templates in action
Thanks to all of the steps described above, a user may write the code @@ -6744,21 +6853,20 @@
acceleration1 +
- -
acceleration1
quantity<reference<derived_quantity_spec<isq::speed, per<isq::time>>, derived_unit<si::kilo_<si::metre>, per<non_si::hour, si::second>>>{}, double>
+
acceleration2
acceleration2
quantity<reference<isq::acceleration, derived_unit<si::metre, per<power<si::second, 2>>>>{}, double>>
16.6 Physical constants
In most libraries, physical constants are implemented as constant -(possibly -
+(possiblyconstexpr
) -quantity values. Such an approach has some disadvantages, often -resulting in longer compilation times and a loss of precision.constexpr
) quantity +values. Such an approach has some disadvantages, often resulting in +longer compilation times and a loss of precision.16.6.1 Simplifying constants in an equation
When dealing with equations involving physical constants, they often @@ -6816,10 +6924,9 @@
permittivity of vacuum = 1 μ₀⁻¹ c⁻² = 8.85419e-12 F/m
As we can clearly see, all the calculations above were just about multiplying and dividing the number -
+1
with the -rest of the information provided as a compile-time type. Only when a -user wants a specific SI unit as a result, the unit ratios are lazily -resolved.1
with the rest of the +information provided as a compile-time type. Only when a user wants a +specific SI unit as a result, the unit ratios are lazily resolved.Another similar example can be an equation for total energy:
<isq::mechanical_energy> auto total_energy(QuantityOf<isq::momentum> auto p, QuantityOf<isq::mass> auto m, @@ -6873,11 +6980,235 @@ QuantityOf
16.7 Quantity references +
16.7 Magnitudes
+A very common operation is to multiply an existing unit by a factor, +creating a new, scaled unit. For example, the unit foot can be +multiplied by 3, producing the unit yard.
+The process also works in reverse; the ratio between any two units of +the same dimension is a well-defined number. For example, the ratio +between one foot and one inch is 12.
+In principle, this scaling factor can be any positive real number. In +[mp-units] and [Au], +we have used the term “magnitude” to refer to this scaling factor. (This +should not be confused with other uses of the term, such as the +logarithmic “magnitude” unit commonly used in astronomy).
+In the library implementation, each unit is associated with a +magnitude. However, for most units, the magnitude is a fully +encapsulated implementation detail, not a user-facing value.
+This is because the notion of “the” magnitude of a unit is not +generally meaningful: it has no physically observable consequence. What +is meaningful is the ratio of magnitudes between two +units of the same quantity kind. We could associate the foot, +say, with any magnitude \(m_f\) that we +like — but once we make that choice, we must assign \(3m_f\) to the yard, and \(m_f/12\) to the inch. Separately +and independently, we can assign any magnitude \(m_s\) to the second, because it’s an +independent dimension — but once we make that choice, it fixes the +magnitude for derived units, and we must assign, say, \((5280 m_f) / (3600 m_s)\) to the mile +per hour.
+16.7.1 Requirements and +representation
+A magnitude is a positive real number. The best way to +represent it depends on how we will use it.
+To derive our requirements, note that magnitudes must support every +operation which units do. Units are closed under products and +rational powers. Therefore, our magnitude representation must +support these operations natively and robustly; this is the most basic +requirement. We must also support certain irrational “ratios”, +such as the factor of \(\frac{\pi}{180}\) between degrees +and radians.
+The usual approach, +
+std::ratio
, fails to satisfy +these requirements in multiple ways.+
+- It is not closed under rational powers (rather infamously, in the +case of 21/2).
+- It cannot represent irrational factors such as \(\pi\).
+- It is too vulnerable to overflow when raised to powers.
+This motivates us to search for a better representation.
+16.7.1.1 Vector spaces, dimensions, +and prime numbers
+The quickest way to find this better representation is via a quick +detour.
+Consider the dimensions of units, such as length, or speed. +All of the above requirements apply to dimensions, too — but in this +case, we already have a very good representation. We start by singling +out a set of dimensions to act as “base” dimensions: for example, the SI +uses length, mass, time, electric current, temperature, amount of +substance, and luminous intensity. Other dimensions are formed by taking +products and rational powers of these. Our choice of base dimensions +must satisfy two properties: spanning (i.e., every dimension +can be expressed as some product-of-powers of base dimensions), +and independence (i.e., no dimension can be expressed +by multiple products-of-powers of base dimensions).
+We call this scheme the “vector space” representation, because it +satisfies all of the axioms of a vector space. Choosing the base +dimensions is equivalent to choosing a set of basis vectors. +Multiplying dimensions is equivalent to vector addition. And +raising dimensions to rational powers is equivalent to scalar +multiplication.
++ +
++ + + ++ +Dimension concept ++ +Corresponding vector space concept ++ +Base dimensions +Basis vectors ++ +Multiplication +Vector addition ++ +Raising to rational power +Scalar multiplication ++ + +Null dimension (“dimensionless”) +Zero vector +This viewpoint lets us derive insights from our intuitions about +vectors. For example, just as we’re free to make a change of +basis, we could also choose a different set of base +dimensions: an SI-like system could treat charge as fundamental +rather than electrical current, and would still produce all the same +results.
+Returning to our magnitude representation problem, it should be clear +that a vector space solution would meet all of our requirements — +if we can find a suitable choice of basis.
+To begin our search, note that each magnitude must have a unique +representation. This means that every combination of our basis elements +must produce a unique value. Prime numbers have this property! Take any +arbitrarily large (but finite) collection of primes, raise each prime to +some chosen exponent, and compute the product: the result can’t be +expressed by any other collection of exponents.
+Already, this lets us represent every positive real number which +
+std::ratio
can represent, by +breaking the numerator and denominator into their prime factorizations. +But we can go further, and handle irrational factors such as \(\pi\) by introducing them as new basis +vectors. \(\pi\) cannot be represented +by the product of powers of any finite collection of primes, +which means that it is “linearly independent” in the sense of our vector +space representation.This completes our recipe for basis construction. First, any prime +number is automatically a basis element. Next, any time we encounter a +magnitude that can’t be expressed in terms of the existing basis +elements, then it is necessarily independent, which means we can simply +add it as a new basis element. It’s true that, for reasons of +real analysis, this approach can’t rigorously represent every positive +real number simultaneously. However, it does provide an +approach that works in practice for the finite set of +magnitudes that we can use in real computer programs.
+On the C++ implementation side, we use variadic templates to define +our magnitude. Each element is a basis number raised to some rational +power (which may be omitted or abbreviated as appropriate).
+16.7.2 Advantages and +disadvantages
+This representation has tradeoffs relative to other approaches, such +as
+std::ratio
and other related +types.The core advantage is, of course, its ability to satisfy the +requirements. Several real-world operations that are impossible for +
+ +std::ratio
are effortless with +vector space magnitudes. Here are some examples, using Astronomical +Units (au), meters (m), degrees (deg), and radians (rad).+
+ ++ + ++ + + + + + ++ +Unit ratio ++ +std::ratio
+representation ++ +vector space representation ++ +\(\left(\frac{\text{au}}{\text{m}}\right)\) ++ std::ratio<149'597'870'700>
+ magnitude<power_v<2, 2>(), 3, power_v<5, 2>(), 73, 877, 7789>
+ +\(\left(\frac{\text{au}}{\text{m}}\right)^2\) +Unrepresentable (overflow) ++ magnitude<power_v<2, 4>(), power_v<3, 2>(), power_v<5, 4>(), power_v<73, 2>(), power_v<877, 2>(), power_v<7789, 2>()>
+ +\(\sqrt{\frac{\text{au}}{\text{m}}}\) +Unrepresentable ++ magnitude<2, power_v<3, 1, 2>(), 5, power_v<73, 1, 2>(), power_v<877, 1, 2>(), power_v<7789, 1, 2>()>
+ + +\(\left(\frac{\text{rad}}{\text{deg}}\right)\) +Unrepresentable ++ magnitude<power_v<2, 2>(), power_v<3, 2>(), power_v<3.14159265358979323851e+0l, -1>(), 5>
One disadvantage of the vector space magnitudes is that the type +names are more verbose. As seen in the table above, users would have to +perform arithmetic to decode which number is being represented. We +expect that we can mitigate this with the same strategy we used to clean +up the unit type names: hide them via opaque types with more +human-friendly names.
+The other disadvantage is that it incurs a dependency on the ability +to compute prime factorizations at compile time, as we explore in the +next section.
+16.7.3 Compile-time factorization
+End users don’t construct magnitudes directly. If they want to +implement, say, the “astronomical unit” referenced above, they would +write something like
+mag<149'597'870'700>
. +The library would automatically expand this tomagnitude<power_v<2, 2>(), 3, power_v<5, 2>(), 73, 877, 7789>
. +To do so requires the ability to factor the number +149'597'870'700
at +compile time.This turns out to be challenging in many practical cases. For +example, the proton mass involves a factor of \(1,672,621,923,695\), which has a large +prime factor: \(334,524,384,739\). The +usual method of factorization, trial division, requires many iterations +to discover that this factor is prime — so many, in fact, that every +major compiler will assume that it’s an infinite loop, and will +terminate the compilation!
+One of us wrote the paper [P3133R0?] to explore a +possible solution to this problem: a new standard library function, +
+std::first_factor(uint64_t)
. +This function would be required to return a result at compile time for +any 64-bit integer — possibly with the help of compiler built-ins, if it +could not be done any other way. The feedback to this paper showed that +this wasn’t actually a hard-blocker: it turns out that every practical +case we have could be satisfied by a fast primality checker. Still, we +plan to continue investigating this avenue, both because it would make +the standard units library implementation much easier, and because this +function would be widely useful in many other domains.16.8 Quantity references
Note: We know that probably the term “reference” will not survive too long in the Committee, but we couldn’t find a better name for it in -the [mp-units] library (https://github.com/mpusz/mp-units/issues/486).
-[ISO/IEC Guide 99] says:
+the [mp-units] library +(https://github.com/mpusz/mp-units/issues/486). +[ISO/IEC Guide 99] +says:
quantity - property of a phenomenon, body, or substance, where the property has a magnitude that can be expressed as a @@ -6887,53 +7218,52 @@
The above would work (assuming we are dealing with the quantity of speed), but it’s not ideal. If the result of -
+q1 / q2
is -not expressed in -m / s
, we’ll -incur an extra unit conversion. Even if it is in -m / s
, it’s -cumbersome to repeat the unit in a context where it makes no -difference.q1 / q2
is not expressed in +m / s
, we’ll incur an extra unit +conversion. Even if it is in +m / s
, it’s cumbersome to repeat +the unit in a context where it makes no difference.We could avoid repeating the unit, and guarantee there won’t be an extra conversion, by writing:
if(auto q = q1 / q2; q != q.zero()) // ...
But that is a bit inconvenient, and inexperienced users could be unaware of this technique and its rationale.
-16.8.2.5.1 Named comparison +
16.9.2.5.1 Named comparison functions
-For the above reasons, the [mp-units] library provides dedicated +
For the above reasons, the [mp-units] library provides dedicated interfaces to compare against zero that follow the naming convention of named comparison functions in the C++ Standard Library:
-
- -
is_eq_zero
- -
is_neq_zero
- -
is_lt_zero
- -
is_gt_zero
- -
is_lteq_zero
- +
is_gteq_zero
- +
is_eq_zero
- +
is_neq_zero
- +
is_lt_zero
- +
is_gt_zero
- +
is_lteq_zero
is_gteq_zero
Thanks to them, to save typing and not pay for unneeded conversions, our check could be implemented as follows:
if (is_neq_zero(q1 / q2)) // ...
Those functions will work with any type -
+T
that exposes a -zero()
-member function returning something comparable to -T
. Thanks to that, we can use them -not only with quantities but also withstd::chrono::duration
-or any other type that exposes such an interface.T
that exposes a +zero()
member function returning +something comparable toT
. +Thanks to that, we can use them not only with quantities but also with +std::chrono::duration
or any +other type that exposes such an interface.This approach has a downside, though: it produces a set of new APIs which users must learn. Nor are these six the only such functions that -will need to exist: for example,
+will need to exist: for example, +max
-andmin
are perfectly reasonable to -use with0
-regardless of the units, but supporting them under this strategy would -require adding a new utility function for each — and coming up with a -name for those functions.max
and +min
are perfectly reasonable to +use with0
regardless of the +units, but supporting them under this strategy would require adding a +new utility function for each — and coming up with a name for those +functions.It also introduces small opportunities for error and diffs that are harder to review, because we’re replacing a pattern that uses an -operator (say,
+operator (say,a > 0
) -with a named function call (say,is_gt_zero(a)
).a > 0
) with a +named function call (say, +is_gt_zero(a)
).These pitfalls motivate us to consider other approaches as well.
-16.8.2.5.2 -
-Zero
typeThe [Au] library takes a different approach +
16.9.2.5.2 +
+Zero
typeThe [Au] library takes a different approach to this problem. It provides an empty type, -
+Zero
, which represents a value of -exactly0
-(in any units). It also provides an instance -ZERO
of this type. Every quantity is -implicitly constructible from -Zero
.Zero
, which represents a value +of exactly0
(in any units). It +also provides an instanceZERO
+of this type. Every quantity is implicitly constructible from +Zero
.Consider this example legacy (i.e., pre-units-library) code:
if (speed_squared_m2ps2 > 0) { /* ... */ }
When users upgrade to a units library, they will replace the raw -number
+numberspeed_squared_m2ps2
with a -strongly typed quantity -speed_squared
. Unfortunately, this -replacement won’t compile, because quantities can’t be constructed from -raw numeric values such as -0
. They can -fix this problem by using the instance, -ZERO
, which encodes its value in the -type:speed_squared_m2ps2
with +a strongly typed quantity +speed_squared
. Unfortunately, +this replacement won’t compile, because quantities can’t be constructed +from raw numeric values such as +0
. They can fix this problem by +using the instance,ZERO
, which +encodes its value in the type:if (speed_squared > ZERO) { /* ... */ }
This has significant advantages. It preserves the form of the code, making the transition less error prone than replacement with a function -such as
-is_gt_zero
. It also reduces -the number of new comparison APIs a user must learn: -Zero
handles them all.+
Zero
has one downside: it will -not work when passed across generic quantity interfaces. -Zero
’s value comes in situations +such asis_gt_zero
. It also +reduces the number of new comparison APIs a user must learn: +Zero
handles them all.
Zero
has one downside: it +will not work when passed across generic quantity interfaces. +Zero
’s value comes in situations where the surrounding context makes it unambiguous which quantity type it should construct. While it converts to any specific quantity type, it is not itself a quantity. This could confuse users.This downside manifests in several different ways. Here are some examples:
-
-While refactoring the [mp-units] code to try out this approach, +
- -
While refactoring the [mp-units] code to try out this approach, we found out a perfectly reasonable place where we could not replace the -numerical value -
+numerical value0
with -ZERO
:0
with +ZERO
:= mean_sea_level + 0 * si::metre; // OK msl_altitude alt = mean_sea_level + ZERO; // Compile-time error msl_altitude alt
This would not work because the -
mean_sea_level
is an absolute point -origin that stores the information about the quantity type but not its -value or unit, whichmsl_altitude
-needs, but none of whichZERO
-has.- +
Callsites passing
ZERO
can -add friction when refactoring a concrete interface to be more +mean_sea_level
is an absolute +point origin that stores the information about the quantity type but not +its value or unit, which +msl_altitude
needs, but none of +whichZERO
has.Callsites passing
ZERO
+can add friction when refactoring a concrete interface to be more generic.namespace v1 { void foo(quantity<si::metre> q); } namespace v2 { void foo(QuantityOf<isq::length> auto q); } @@ -7402,18 +7728,18 @@
In practice, this issue will be discovered at the point of refactoring, so it mainly affects library authors, not their clients. They can handle this by adding an overload for -
+Zero
, if appropriate. However, this -wouldn’t scale well for APIs with multiple parameters where users would -want to passZero
.Zero
, if appropriate. However, +this wouldn’t scale well for APIs with multiple parameters where users +would want to pass +Zero
.For completeness, we mention that -
+Zero
works for addition but not +Zero
works for addition but not multiplication. When multiplying, we do not know what units (or even what dimension!) is desired for the result. However, this is not a problem in practice because users would not be motivated to write this in the first place, as simple multiplication with -0
(including -any necessary units, if the result has a different dimension) would -work.0
(including any necessary +units, if the result has a different dimension) would work.@@ -7421,55 +7747,57 @@= 1 * m / s; quantity q1 = q1 + 0 * m / s; // OK quantity q2 = q1 * (0 * s); // OK quantity q3
= q1 + ZERO; // OK quantity q2 = q1 * ZERO; // Compile-time error quantity q3
The main concern with the
-Zero
-feature is that novices might be tempted to replace every numeric value -0
with the -instanceZERO
, becoming confused -when it doesn’t work. We could address this with easy-to-read -documentation that clarifies its use cases and mental models.16.8.2.5.3 Summary (comparison +
The main concern with the +
+Zero
feature is that novices +might be tempted to replace every numeric value +0
with the instance +ZERO
, becoming confused when it +doesn’t work. We could address this with easy-to-read documentation that +clarifies its use cases and mental models.16.9.2.5.3 Summary (comparison against zero)
Overall, these two approaches — special functions, and a -
-Zero
type — represent two local +Zero
type — represent two local optima in design space. Each has its strengths and weaknesses; each makes different tradeoffs. It’s currently an open question as to which approach would be best suited for a quantity type in the standard library.16.8.2.6 Other maths
+16.9.2.6 Other maths
This chapter scoped only on the -
quantity
type’s operators. However, -there are many named math functions provided in the [mp-units] library. Among others, we can +quantity
type’s operators. +However, there are many named math functions provided in the [mp-units] library. Among others, we can find there the following:-
-- -
pow()
, -sqrt()
, and -cbrt()
,- -
exp()
,- -
abs()
,- -
epsilon()
,- -
fma()
, -fmod()
,- -
floor()
, -ceil()
, -round()
,- -
inverse()
,- -
hypot()
,- -
sin()
, -cos()
, -tan()
,- +
asin()
, -acos()
, -atan()
, -atan2()
.- +
pow()
, +sqrt()
, and +cbrt()
,- +
exp()
,- +
abs()
,- +
epsilon()
,- +
fma()
, +fmod()
,- +
floor()
, +ceil()
, +round()
,- +
inverse()
,- +
hypot()
,- +
sin()
, +cos()
, +tan()
,asin()
, +acos()
, +atan()
, +atan2()
.In the library, we can also find the
+<mp-units/random.h>
-header file with all the pseudo-random number generators.In the library, we can also find the +
<mp-units/random.h>
header +file with all the pseudo-random number generators.We plan to provide a separate paper on those in the future.
-16.8.3 Dimensionless quantities
+16.9.3 Dimensionless quantities
The quantities we discussed so far always had some specific type and physical dimension. However, this is not always the case. While performing various computations, we sometimes end up with so-called -“dimensionless” quantities, which [ISO/IEC Guide 99] correctly defines as +“dimensionless” quantities, which [ISO/IEC Guide 99] correctly defines as quantities of dimension one:
@@ -7482,7 +7810,7 @@
16.8.3.1 Dividing two quantities of +
16.9.3.1 Dividing two quantities of the same kind
Dividing two quantities of the same kind always results in a quantity of dimension one. However, depending on what type of quantities we @@ -7490,42 +7818,41 @@
Dividing two quantities of the same dimension always results in a quantity with the dimension being -
-dimension_one
. This is often +dimension_one
. This is often different for other physical units libraries, which may return a raw representation type for such cases. A raw value is also always returned -from the division of twostd::chrono::duration
+from the division of two +std::chrono::duration
values.In the initial design of the [mp-units] library, the resulting type of +
In the initial design of the [mp-units] library, the resulting type of division of two quantities was their common representation type (just -like
+likestd::chrono::duration
):std::chrono::duration
):static_assert(std::is_same_v<decltype(10 * km / (5 * km)), int>);
The reasoning behind it was not providing a false impression of a -strong
+strongquantity
type for something -that looks and feels like a regular number. Also, all of the mathematic -and trigonometric functions were working fine out of the box with such -representation types, so we did not have to rewrite -sin()
, -cos()
, -exp()
, and -others.quantity
type for +something that looks and feels like a regular number. Also, all of the +mathematic and trigonometric functions were working fine out of the box +with such representation types, so we did not have to rewrite +sin()
, +cos()
, +exp()
, and others.However, the feedback we got from the production usage was that such an approach is really bad for generic programming. It is hard to handle the result of the two quantities’ division (or multiplication) as it might be either a quantity or a fundamental type. If we want to raise such a result to some power, we must use -
+units::pow
-orstd::pow
-depending on the resulting type -(units::pow
-takes the power as template arguments). Those are only a few issues -related to such an approach.units::pow
or +std::pow
depending on the +resulting type (units::pow
takes +the power as template arguments). Those are only a few issues related to +such an approach.Moreover, suppose we divide quantities of the same dimension, but with units of significantly different magnitudes. In such case, we may end up with a really small or a huge floating-point value, which may result in losing lots of precision. Returning a dimensionless quantity from such cases allows us to benefit from all the properties of scaled units and is consistent with the rest of the library.
-16.8.3.1.1 Dividing quantities of +
16.9.3.1.1 Dividing quantities of the same type
First, let’s analyze what happens if we divide two quantities of the same type:
@@ -7536,52 +7863,53 @@static_assert(q.dimension == dimension_one); static_assert(q.unit == one);
In case we would like to print its value, we would see a raw value of -
-4
in the -output with no unit being printed.16.8.3.1.2 Dividing quantities of +
4
in the output with no unit +being printed.16.9.3.1.2 Dividing quantities of different types
We can divide quantities of the same dimension and unit but of different quantity types:
constexpr QuantityOf<dimensionless> auto q = isq::work(200 * J) / isq::heat(50 * J);
Again we end up with -
+dimension_one
and -one
, but this time:dimension_one
and +one
, but this time:static_assert(q.quantity_spec == isq::work / isq::heat);
As shown above, the result is not of a -
dimensionless
type anymore. Instead, -we get a quantity type derived from the performed quantity equation. -According to the [ISO/IEC 80000], work divided by +dimensionless
type anymore. +Instead, we get a quantity type derived from the performed quantity +equation. According to the [ISO/IEC 80000], work divided by heat is the recipe for the thermodynamic efficiency quantity, thus:-static_assert(implicitly_convertible(q.quantity_spec, isq::efficiency_thermodynamics));
Please note that the quantity of
isq::efficiency_thermodynamics
-is of a kinddimensionless
, so it is -implicitly convertible to -dimensionless
and satisfies theQuantityOf<dimensionless>
+Please note that the quantity of +
-isq::efficiency_thermodynamics
+is of a kinddimensionless
, so +it is implicitly convertible to +dimensionless
and satisfies the +QuantityOf<dimensionless>
concept.16.8.3.1.3 Dividing quantities of +
16.9.3.1.3 Dividing quantities of different units
Now, let’s see what happens when we divide two quantities of the same type but different units:
constexpr QuantityOf<dimensionless> auto q = isq::height(4 * km) / isq::height(2 * m);
This time we get a quantity of -
+dimensionless
type with a -dimension_one
as its dimension. +dimensionless
type with a +dimension_one
as its dimension. However, the resulting unit is not -one
anymore:one
anymore:static_assert(q.unit == mag_power<10, 3> * one);
In case we would print the text output of this quantity, we would not -see a raw value of -
+see a raw value of2000
, but -2 km/m
.2000
, but +2 km/m
.First, it may look surprising, but this is actually consistent with the division of quantities of different dimensions. For example, if we -divide
+divide4 * km / (2 * s)
, -we do not expectkm
to be “expanded” -tom
before the division, right? We -would expect the result of2 * (km / s)
, -which is exactly what we get when we divide quantities of the same -kind.4 * km / (2 * s)
, we do +not expectkm
to be “expanded” +tom
before the division, right? +We would expect the result of +2 * (km / s)
, which is exactly +what we get when we divide quantities of the same kind.This is a compelling feature that allows us to express huge or tiny ratios without the need for big and expensive representation types. With this, we can easily define things like a Hubble’s @@ -7590,58 +7918,61 @@
inline constexpr struct hubble_constant : <{u8"H₀", "H_0"}, mag_ratio<701, 10> * si::kilo<si::metre> / si::second / si::mega<parsec>> { named_unit} hubble_constant;
16.8.3.2 Counts of things
+16.9.3.2 Counts of things
Another important use case for dimensionless quantities is to provide strong types for counts of things. For example:
-
- [ISO/IEC 80000] (part 3) provides a -rotation quantity defined as the number of revolutions,
-- [ISO/IEC 80000] (part 6) provides a -number of turns in a winding quantity,
-- [ISO/IEC 80000] (part 13) provides a -Hamming distance quantity defined as the number of digit -positions in which the corresponding digits of two words of the same -length are different.
+- [ISO/IEC 80000] +(part 3) provides a rotation quantity defined as the number of +revolutions,
+- [ISO/IEC 80000] +(part 6) provides a number of turns in a winding quantity,
+- [ISO/IEC 80000] +(part 13) provides a Hamming distance quantity defined as the +number of digit positions in which the corresponding digits of two words +of the same length are different.
Thanks to assigning strong names to such quantities, they can be used in the quantity equation of other quantities. For example, -rotational frequency is defined by
-rotation / duration
.16.8.3.3 Lack of convertibility +rotational frequency is defined by +
rotation / duration
. +16.9.3.3 Lack of convertibility from fundamental types
As stated before, the division of two quantities of the same kind results in a quantity of dimension one. Even though it does not have a specific physical dimension it still uses units with various ratios such -as
+asone
, -percent
, -radian
, -degree
, etc. It is essential to be -explicit about which unit we want to use for such a quantity.one
, +percent
, +radian
, +degree
, etc. It is essential to +be explicit about which unit we want to use for such a quantity.However, in some cases, this might look like an overkill. In the Basic quantity equations chapter one of the lines looks as follows:
static_assert(10 * km / (5 * km) == 2 * one);
Another example could be subtracting a value -
+1
from the -dimensionless quantity that we can find in Storage tank example:1
from the dimensionless +quantity that we can find in Storage tank +example:-const QuantityOf<isq::time> auto fill_time_left = (height / fill_level - 1 * one) * fill_time;
Some physical quantities and units libraries (e.g. [Boost.Units]) provide implicit +
Some physical quantities and units libraries (e.g. [Boost.Units]) provide implicit conversions from the values of representation types to such quantities.
With such support, the above examples would look in the following way:
static_assert(10 * km / (5 * km) == 2);
-const QuantityOf<isq::time> auto fill_time_left = (height / fill_level - 1) * fill_time;
Such simplification might look tempting, and the [mp-units] initially provided a special +
Such simplification might look tempting, and the [mp-units] initially provided a special support that allowed the above to compile. However, in the V2 version of the library, it was removed. There are a few reasons for that:
- +a unit
Such support has sense only for quantities of dimension one with -a unit
one
. In the case of all the -other units, the specific unit should still be provided.one
. In the case of all +the other units, the specific unit should still be provided.- +
If we provide implicit conversions from the representation type to a quantity of dimension one with a unit -
one
and we start depending on such a -feature, we might end up with compile-time errors after refactoring the -unit in a type of such a quantity. For example:one
and we start depending on +such a feature, we might end up with compile-time errors after +refactoring the unit in a type of such a quantity. For example:@@ -7686,18 +8017,18 @@
Please also note that we will still need to use explicit units to create a quantity for some cases. For example, in the following code, -
+asin(-1)
-would use the overload from the<math>
-header of the C++ standard library rather than the one for quantities -provided in<mp-units/math.h>
-header file:asin(-1)
would use the overload +from the<math>
header of +the C++ standard library rather than the one for quantities provided in +<mp-units/math.h>
header +file:(asin(-1 * one), AlmostEquals(-90. * deg)); REQUIRE_THAT
The authors of this paper are not opposed to providing support for conversions from the raw value to the dimensionless quantity of a unit -
+one
. If WG21 groups decide that is a -good direction, we will provide additional interfaces to the library to -support such a scenario. If we choose to go this path, two questions -should be answered first:one
. If WG21 groups decide that +is a good direction, we will provide additional interfaces to the +library to support such a scenario. If we choose to go this path, two +questions should be answered first:-
Should such a conversion be explicit or implicit?
We may be tempted to stay on the safe side and choose explicit @@ -7707,8 +8038,8 @@
It would be strange to support arithmetics against the raw value if the quantity type can’t be implicitly constructed from it.Should we support converting from the dimensionless quantity with -unit
+unitone
to the raw value? And if -yes, should it be implicit or explicit?one
to the raw value? And +if yes, should it be implicit or explicit?It could probably be better to allow implicit conversions to work with legacy interfaces or to benefit from regular math-related functions that work on fundamental types. Otherwise, we will need to type @@ -7717,13 +8048,13 @@
which really is not a great improvement over:auto res = sin(q.numerical_value_in(one));
16.8.3.4 Predefined units of the +
16.9.3.4 Predefined units of the dimensionless quantity
As we observed above, the most common unit for dimensionless -quantities is
-one
. It has the ratio -of1
and -does not output any textual symbol.A unit
+one
is special in the +quantities isone
. It has the +ratio of1
and does not output +any textual symbol.A unit
@@ -7731,89 +8062,96 @@one
is special in the entire type system of units as it is considered to be an identity operand in the unit expression templates. This means that, for example:< static_assert(one * si::metre == si::metre); static_assert(si::metre / si::metre == one);
The same is also true for -
-dimension_one
and -dimensionless
in the domains of +dimension_one
and +dimensionless
in the domains of dimensions and quantity specifications, respectively.Besides the unit
one
, there are a -few other scaled units predefined in the library for usage with +Besides the unit
one
, there +are a few other scaled units predefined in the library for usage with dimensionless quantities:-inline constexpr struct percent : named_unit<"%", mag_ratio<1, 100> * one> {} percent; inline constexpr struct per_mille : named_unit<{u8"‰", "%o"}, mag_ratio<1, 1000> * one> {} per_mille; inline constexpr struct parts_per_million : named_unit<"ppm", mag_ratio<1, 1'000'000> * one> {} parts_per_million; inline constexpr auto ppm = parts_per_million;
16.8.3.5 Angular quantities
+16.9.3.5 Angular quantities
Special, often controversial, examples of dimensionless quantities are the angular measure and solid angular measure -quantities that are defined in [ISO/IEC 80000] (part 3) to be the result -of a division of
-arc_length / radius
-andarea / pow<2>(radius)
-respectively. Moreover, [ISO/IEC 80000] also explicitly states -that both can be expressed in the unit -one
. This means that bothisq::angular_measure
-andisq::solid_angular_measure
+quantities that are defined in [ISO/IEC 80000] (part 3) to be the result +of a division of +arc_length / radius
and +area / pow<2>(radius)
+respectively. Moreover, [ISO/IEC 80000] also +explicitly states that both can be expressed in the unit +one
. This means that both +isq::angular_measure
and +isq::solid_angular_measure
should be of a kind of -dimensionless
.On the other hand, [ISO/IEC 80000] also specifies that the -unit
+radian
can be used for -angular measure, and the unit -steradian
can be used for solid -angular measure. Those should not be mixed or used to express other -types of dimensionless quantities. We should not be able to measure:dimensionless
. +On the other hand, [ISO/IEC 80000] also +specifies that the unit
radian
+can be used for angular measure, and the unit +steradian
can be used for +solid angular measure. Those should not be mixed or used to +express other types of dimensionless quantities. We should not be able +to measure:-
- basic dimensionless quantity in radians or steradians,
- angular measure in steradians,
- solid angular measure in radians.
This means that both
isq::angular_measure
-andisq::solid_angular_measure
+This means that both +
isq::angular_measure
and +isq::solid_angular_measure
should also be quantity kinds by themselves.Note: Many people claim that angle being a dimensionless quantity is a bad idea. There are proposals submitted to make an angle a base -quantity and
-rad
to become a base -unit in both [SI] and [ISO/IEC 80000].16.8.3.6 Nested quantity kinds
+quantity andrad
to become a +base unit in both [SI] and [ISO/IEC 80000]. +16.9.3.6 Nested quantity kinds
Angular quantities are not the only ones with such a “strange” behavior. A similar case is the storage capacity quantity -specified in [ISO/IEC 80000] (part 13) that again -allows expressing it in both
+specified in [ISO/IEC 80000] +(part 13) that again allows expressing it in both +one
and -bit
units.one
and +bit
units.Those cases make dimensionless quantities an exceptional tree in the library. This is the only quantity hierarchy that contains more than one quantity kind in its tree:
To provide such support in the library, we provided an -
is_kind
specifier that can be +is_kind
specifier that can be appended to the quantity specification:inline constexpr struct angular_measure : quantity_spec<dimensionless, arc_length / radius, is_kind> {} angular_measure; inline constexpr struct solid_angular_measure : quantity_spec<dimensionless, area / pow<2>(radius), is_kind> {} solid_angular_measure; inline constexpr struct storage_capacity : quantity_spec<dimensionless, is_kind> {} storage_capacity;
With the above, we can constrain -
radian
, -steradian
, and -bit
to be allowed for usage with +radian
, +steradian
, and +bit
to be allowed for usage with specific quantity kinds only:inline constexpr struct radian : named_unit<"rad", metre / metre, kind_of<isq::angular_measure>> {} radian; inline constexpr struct steradian : named_unit<"sr", square(metre) / square(metre), kind_of<isq::solid_angular_measure>> {} steradian; inline constexpr struct bit : named_unit<"bit", one, kind_of<storage_capacity>> {} bit;
This still allows the usage of -
-one
(possibly scaled) for such +one
(possibly scaled) for such quantities which is exactly what we wanted to achieve.16.9 Interoperability with other +
16.10 Interoperability with other libraries
It is easy to cooperate with similar entities of other libraries. No matter if we want to provide interoperability with a simple home-grown strongly typed wrapper type (e.g., -
-Meter
, -Timestamp
, …) or with a feature-rich -quantities and units library, we have to provide specializations of:-
- a
-quantity_like_traits
for -externalquantity
-like type,- a
quantity_point_like_traits
for -externalquantity_point
-like +Meter
, +Timestamp
, …) or with a +feature-rich quantities and units library, we have to provide +specializations of: ++
-- a
+quantity_like_traits
for +externalquantity
-like +type,- a
quantity_point_like_traits
+for externalquantity_point
-like type.16.9.1 Specifying a conversion +
16.10.1 Specifying a conversion kind
Before we delve into the template specialization details, let’s first decide if we want the conversions to happen implicitly or if explicit @@ -7842,44 +8180,50 @@
convert_explicitly<T>. -Otherwise,
convert_implicitly<T>
+inconvert_explicitly<T>
. +Otherwise, +convert_implicitly<T>
should be used. -16.9.2 Quantities conversions
+16.10.2 Quantities conversions
For example, let’s assume that some company has its own -
+Meter
strong-type wrapper:Meter
strong-type wrapper:-struct Meter { int value; };
As every usage of
Meter
is at -least as good and safe as the usage ofquantity<si::metre, int>
, +As every usage of
-Meter
is at +least as good and safe as the usage of +quantity<si::metre, int>
, and as there is no significant runtime performance penalty, we would -like to allow the conversion tomp_units::quantity
-to happen implicitly.On the other hand, the
+like to allow the conversion to +quantity
-type is much safer than theMeter
, -and that is why we would prefer to see the opposite conversions stated -explicitly in our code.mp_units::quantity
to happen +implicitly. +On the other hand, the +
quantity
type is much safer than +theMeter
, and that is why we +would prefer to see the opposite conversions stated explicitly in our +code.To enable such interoperability, we must define a partial -specialization of the
quantity_like_traits<T>
+specialization of the +quantity_like_traits<T>
type trait. Such specialization should provide:-
-- static data member
reference
+- static data member
-reference
that provides the quantity reference (e.g., unit),rep
type that specifies the +- -
rep
type that specifies the underlying storage type,- -
to_numerical_value(T)
-static member function returning a quantity’s raw value of -rep
type packed in either -convert_explicitly
or -convert_implicitly
wrapper.- +
from_numerical_value(rep)
-static member function returningT
-packed in eitherconvert_explicitly
-orconvert_implicitly
wrapper.- +
to_numerical_value(T)
static +member function returning a quantity’s raw value of +rep
type packed in either +convert_explicitly
or +convert_implicitly
wrapper.from_numerical_value(rep)
+static member function returning +T
packed in either +convert_explicitly
or +convert_implicitly
wrapper.For example, for our
+Meter
type, -we could provide the following:For example, for our
Meter
+type, we could provide the following:-template<> struct std::quantity_like_traits<Meter> { static constexpr auto reference = si::metre; @@ -7887,7 +8231,7 @@
static constexpr convert_implicitly<rep> to_numerical_value(Meter m) { return m.value; } static constexpr convert_explicitly<Meter> from_numerical_value(rep v) { return Meter{v}; } };
After that, we can check that the
QuantityLike
+After that, we can check that the
QuantityLike
concept is satisfied:static_assert(QuantityLike<Meter>);
and we can write the following:
@@ -7961,19 +8305,17 @@(1) -Truncation of value while converting from meters to kilometers. -
-
(2)
-Conversion of -double
to -int
is not -value-preserving.-
(3)
-Truncation of value while converting from millimeters to meters.16.9.3 Quantity points +
+
(1)
Truncation of value while +converting from meters to kilometers.+
(2)
Conversion of +double
to +int
is not value-preserving.+
(3)
Truncation of value while +converting from millimeters to meters.16.10.3 Quantity points conversions
To play with quantity point conversions, let’s assume that we have a -
Timestamp
strong type in our +Timestamp
strong type in our codebase, and we would like to start using this library to work with this abstraction.-struct Timestamp { @@ -7983,31 +8325,33 @@
type and the -quantity_point
class template we -need to provide the following in the partial specialization of thequantity_point_like_traits<T>
+Timestamp
type and the +quantity_point
class template we +need to provide the following in the partial specialization of thequantity_point_like_traits<T>
type trait:-
-- static data member
reference
+- static data member
-reference
that provides the quantity point reference (e.g., unit),- static data member
-point_origin
-that specifies the absolute point, which is the beginning of our -measurement scale for our points,rep
type that specifies the +- static data member +
+point_origin
that specifies the +absolute point, which is the beginning of our measurement scale for our +points,- -
rep
type that specifies the underlying storage type,- -
to_quantity(T)
-static member function returning the -quantity
being the offset of the -point from the origin packed in either -convert_explicitly
or -convert_implicitly
wrapper,- +
from_quantity(quantity<reference, rep>)
-static member function returningT
-packed in eitherconvert_explicitly
-orconvert_implicitly
wrapper.- +
to_quantity(T)
static member +function returning thequantity
+being the offset of the point from the origin packed in either +convert_explicitly
or +convert_implicitly
wrapper,from_quantity(quantity<reference, rep>)
+static member function returning +T
packed in either +convert_explicitly
or +convert_implicitly
wrapper.For example, for our
+Timestamp
-type, we could provide the following:For example, for our +
Timestamp
type, we could provide +the following:-template<> struct std::quantity_point_like_traits<Timestamp> { static constexpr auto reference = si::second; @@ -8024,7 +8368,7 @@
return Timestamp(q.numerical_value_ref_in(si::second)); } };
After that, we can check that the
QuantityPointLike
+After that, we can check that the
QuantityPointLike
concept is satisfied:static_assert(mp_units::QuantityPointLike<Timestamp>);
and we can write the following:
@@ -8045,37 +8389,37 @@// explicit conversion (Timestamp(qp)); print}
16.9.4 Interoperability with the -
+std::chrono
-abstractions16.10.4 Interoperability with the +
std::chrono
abstractionsIn the C++ standard library, we have two types that handle quantities and model the affine space. Those are:
-
std::chrono::duration
+- -
std::chrono::duration
- specifies quantities of time,std::chrono::time_point
+std::chrono::time_point
- specifies quantity points of time.This library comes with built-in interoperability with those types thanks to:
- partial specializations of -
-quantity_like_traits
and -quantity_point_like_traits
that +quantity_like_traits
and +quantity_point_like_traits
that provide support for implicit conversions between types in both directions,- -
chrono_point_origin<Clock>
-point origin forstd
clocks,to_chrono_duration
and -to_chrono_time_point
dedicated +- +
chrono_point_origin<Clock>
+point origin forstd
+clocks,to_chrono_duration
and +to_chrono_time_point
dedicated conversion functions that result in types exactly representing this library’s abstractions.It is important to note here that only a -
+quantity_point
that useschrono_point_origin<Clock>
+quantity_point
that uses +chrono_point_origin<Clock>
as its origin can be converted to the -std::chrono
-abstractions:std::chrono
abstractions:-inline constexpr struct ts_origin : relative_point_origin<chrono_point_origin<system_clock> + 1 * h> {} ts_origin; inline constexpr struct my_origin : absolute_point_origin<my_origin, isq::time> {} my_origin; @@ -8093,12 +8437,14 @@
< {1 * s}; quantity_point qp5auto tp5 = to_chrono_time_point(qp5); // Compile-time Error (2)
-
(1)
-my_origin
is not defined in terms of -chrono_point_origin<Clock>
.+
(2)
-zeroth_point_origin
is not defined -in terms ofchrono_point_origin<Clock>
.+
(1)
+my_origin
is not defined in +terms of +chrono_point_origin<Clock>
.
(2)
+zeroth_point_origin
is not +defined in terms of +chrono_point_origin<Clock>
.Here is an example of how interoperability described in this chapter can be used in practice:
using namespace std::chrono; @@ -8122,13 +8468,13 @@
<
: 2023-11-18 13:20:54 UTC Takeoff: 2023-11-18 15:07:01 MST Landing
17 Teachability
-Through the last years [mp-units] library proved to be very +
Through the last years [mp-units] library proved to be very intuitive to both novices in the domain and non-C++ experts. Thanks to the user-friendly multiply syntax, support for CTAD, excellent readability of generated types in compiler error messages, and simplicity of systems definitions, this library makes it easy to do the first steps in the dimensional analysis domain.
-If someone is new to the domain, a concise introduction of Systems of units, the [SI], and the US Customary System (and +
If someone is new to the domain, a concise introduction of Systems of units, the [SI], and the US Customary System (and how it relates to SI) might be needed.
After that, every new user, even a C++ newbie, should have no problems with understanding the topics of the Unit-based quantities (simple @@ -8172,7 +8518,7 @@
17.
According to our experience, the most common pitfall in using quantity types might be related to the names chosen for them by the -[ISO/IEC 80000] (e.g., length). +[ISO/IEC 80000] (e.g., length). It might be good to describe what length means when we say “every height is a length” and what it means when we describe a box of length, width, and height @@ -8192,7 +8538,7 @@
1 confusing passages.
19 References
-+-[Ariane flight V88] Ariane flight V88.@@ -8277,9 +8623,6 @@https://en.wikipedia.org/wiki/Ariane_flight_V8819 [P1045R1] David Stone. 2019-09-27. constexpr Function Parameters.
https://wg21.link/p1045r1-[P1729R3] Elias Kosunen, Victor Zverovich. 2023-10-12. Text Parsing.https://wg21.link/p1729r3-[P1930R0] Vincent Reverdy. 2019-10-07. Towards a standard unit systems library.-https://wg21.link/p1930r0@@ -8291,39 +8634,6 @@19
https://wg21.link/p2509r0-[P2980R1] Mateusz Pusz, Dominik Berner, Johel Ernesto Guerrero Peña, -Charles Hogg, Nicolas Holthaus, Roth Michaels, Vincent Reverdy. -2023-11-28. A motivation, scope, and plan for a quantities and units -library.-https://wg21.link/p2980r1--[P2981R1] Mateusz Pusz, Dominik Berner, Johel Ernesto Guerrero Peña. -2023-11-09. Improving our safety with a physical quantities and units -library.-https://wg21.link/p2981r1--[P2982R1] Mateusz Pusz, Chip Hogg. 2023-11-09. `std::quantity` as a -numeric type.-https://wg21.link/p2982r1--[P2993R0] Luke Valenty. 2024-03-21. Constrained Numbers.-https://wg21.link/p2993r0--[P3003R0] Johel Ernesto Guerrero Peña. 2023-10-14. The design of a -library of number concepts.-https://wg21.link/p3003r0--[P3045R0] Mateusz Pusz, Dominik Berner, Johel Ernesto Guerrero Peña, -Charles Hogg, Nicolas Holthaus, Roth Michaels, Vincent Reverdy. -2024-02-15. Quantities and units library.-https://wg21.link/p3045r0--[P3094R0] Mateusz Pusz. 2024-02-05. std::basic_fixed_string.-https://wg21.link/p3094r0--[P3133R0] Chip Hogg. 2024-02-14. Fast first-factor finding function.https://wg21.link/p3133r0-[Pint] Pint: makes units easy.diff --git a/src/3045R1_quantities_and_units_library.md b/src/3045R1_quantities_and_units_library.md index bb4efc5..740bf44 100644 --- a/src/3045R1_quantities_and_units_library.md +++ b/src/3045R1_quantities_and_units_library.md @@ -5887,6 +5887,153 @@ m = 5.34799e-27 kg E = 8.01088e-10 J ``` +## Magnitudes + +A very common operation is to multiply an existing unit by a factor, creating a new, scaled unit. +For example, the unit _foot_ can be multiplied by 3, producing the unit _yard_. + +The process also works in reverse; the ratio between any two units of the same dimension is +a well-defined number. For example, the ratio between one foot and one inch is 12. + +In principle, this scaling factor can be any positive real number. In [@MP-UNITS] and [@AU], we +have used the term "magnitude" to refer to this scaling factor. (This should not be confused with +other uses of the term, such as the logarithmic "magnitude" unit commonly used in astronomy). + +In the library implementation, each unit is associated with a magnitude. However, for most units, +the magnitude is a fully encapsulated implementation detail, not a user-facing value. + +This is because the notion of "the" magnitude of a unit is not generally meaningful: it has no +physically observable consequence. What _is_ meaningful is the _ratio_ of magnitudes between two +units of the same quantity kind. We could associate the _foot_, say, with any magnitude $m_f$ that +we like --- but once we make that choice, we must assign $3m_f$ to the _yard_, and $m_f/12$ to the +_inch_. Separately and independently, we can assign any magnitude $m_s$ to the second, because it's +an independent dimension --- but once we make that choice, it fixes the magnitude for derived units, +and we must assign, say, $(5280 m_f) / (3600 m_s)$ to the _mile per hour_. + +### Requirements and representation + +A magnitude is a positive real number. The best way to _represent_ it depends on how we will _use_ +it. + +To derive our requirements, note that magnitudes must support every operation which units do. Units +are closed under _products_ and _rational powers_. Therefore, our magnitude representation must +support these operations natively and robustly; this is the most basic requirement. We must also +support certain _irrational_ "ratios", such as the factor of $\frac{\pi}{180}$ between _degrees_ and +_radians_. + +The usual approach, `std::ratio`, fails to satisfy these requirements in multiple ways. + +- It is not closed under rational powers (rather infamously, in the case of 21/2). +- It cannot represent irrational factors such as $\pi$. +- It is too vulnerable to overflow when raised to powers. + +This motivates us to search for a better representation. + +#### Vector spaces, dimensions, and prime numbers + +The quickest way to find this better representation is via a quick detour. + +Consider the _dimensions_ of units, such as length, or speed. All of the above requirements apply +to dimensions, too --- but in this case, we already have a very good representation. We start by +singling out a set of dimensions to act as "base" dimensions: for example, the SI uses length, mass, +time, electric current, temperature, amount of substance, and luminous intensity. Other dimensions +are formed by taking products and rational powers of these. Our choice of base dimensions must +satisfy two properties: _spanning_ (i.e., every dimension can be expressed as _some_ +product-of-powers of base dimensions), and _independence_ (i.e., _no_ dimension can be expressed by +_multiple_ products-of-powers of base dimensions). + +We call this scheme the "vector space" representation, because it satisfies all of the axioms of +a vector space. Choosing the base dimensions is equivalent to choosing a set of _basis vectors_. +Multiplying dimensions is equivalent to _vector addition_. And raising dimensions to rational +powers is equivalent to _scalar multiplication_. + +| Dimension concept | Corresponding vector space concept | +|-------------------|------------------------------------| +| Base dimensions | Basis vectors | +| Multiplication | Vector addition | +| Raising to rational power | Scalar multiplication | +| Null dimension ("dimensionless") | Zero vector | + +This viewpoint lets us derive insights from our intuitions about vectors. For example, just as +we're free to make a _change of basis_, we could also choose a different set of _base dimensions_: +an SI-like system could treat charge as fundamental rather than electrical current, and would still +produce all the same results. + +Returning to our magnitude representation problem, it should be clear that a vector space solution +would meet all of our requirements --- _if_ we can find a suitable choice of basis. + +To begin our search, note that each magnitude must have a unique representation. This means that +every combination of our basis elements must produce a unique value. Prime numbers have this +property! Take any arbitrarily large (but finite) collection of primes, raise each prime to some +chosen exponent, and compute the product: the result can't be expressed by any other collection of +exponents. + +Already, this lets us represent every positive real number which `std::ratio` can represent, by +breaking the numerator and denominator into their prime factorizations. But we can go further, and +handle irrational factors such as $\pi$ by introducing them as new basis vectors. $\pi$ cannot be +represented by the product of powers of _any_ finite collection of primes, which means that it is +"linearly independent" in the sense of our vector space representation. + +This completes our recipe for basis construction. First, any prime number is automatically a basis +element. Next, any time we encounter a magnitude that can't be expressed in terms of the existing +basis elements, then it is necessarily independent, which means we can simply add it as a _new_ +basis element. It's true that, for reasons of real analysis, this approach can't rigorously +represent every positive real number simultaneously. However, it _does_ provide an approach that +works in _practice_ for the finite set of magnitudes that we can use in real computer programs. + +On the C++ implementation side, we use variadic templates to define our magnitude. Each element is +a basis number raised to some rational power (which may be omitted or abbreviated as appropriate). + +### Advantages and disadvantages + +This representation has tradeoffs relative to other approaches, such as `std::ratio` and other +related types. + +The core advantage is, of course, its ability to satisfy the requirements. Several real-world +operations that are impossible for `std::ratio` are effortless with vector space magnitudes. Here +are some examples, using Astronomical Units (au), meters (m), degrees (deg), and radians (rad). + + + +| Unit ratio | `std::ratio` representation | vector space representation | +|----------------------------------------------|-------------------------------|-------------------------------------------------------------------------------------------------------------------------| +| $\left(\frac{\text{au}}{\text{m}}\right)$ | `std::ratio<149'597'870'700>` | `magnitudehttps://pint.readthedocs.io/en/stable/index.html(), 3, power_v<5, 2>(), 73, 877, 7789>` | +| $\left(\frac{\text{au}}{\text{m}}\right)^2$ | Unrepresentable (overflow) | `magnitude (), power_v<3, 2>(), power_v<5, 4>(), power_v<73, 2>(), power_v<877, 2>(), power_v<7789, 2>()>` | +| $\sqrt{\frac{\text{au}}{\text{m}}}$ | Unrepresentable | `magnitude<2, power_v<3, 1, 2>(), 5, power_v<73, 1, 2>(), power_v<877, 1, 2>(), power_v<7789, 1, 2>()>` | +| $\left(\frac{\text{rad}}{\text{deg}}\right)$ | Unrepresentable | `magnitude (), power_v<3, 2>(), power_v<3.14159265358979323851e+0l, -1>(), 5>` | + + + +One disadvantage of the vector space magnitudes is that the type names are more verbose. As seen in +the table above, users would have to perform arithmetic to decode which number is being represented. +We expect that we can mitigate this with the same strategy we used to clean up the unit type names: +hide them via opaque types with more human-friendly names. + +The other disadvantage is that it incurs a dependency on the ability to compute prime factorizations +at compile time, as we explore in the next section. + +### Compile-time factorization + +End users don't construct magnitudes directly. If they want to implement, say, the "astronomical +unit" referenced above, they would write something like `mag<149'597'870'700>`. The library would +automatically expand this to `magnitude (), 3, power_v<5, 2>(), 73, 877, 7789>`. To do +so requires the ability to factor the number `149'597'870'700` at compile time. + +This turns out to be challenging in many practical cases. For example, the proton mass involves +a factor of $1,672,621,923,695$, which has a large prime factor: $334,524,384,739$. The usual +method of factorization, trial division, requires many iterations to discover that this factor is +prime --- so many, in fact, that every major compiler will assume that it's an infinite loop, and +will terminate the compilation! + +One of us wrote the paper [@P3133R0] to explore a possible solution to this problem: a new standard +library function, `std::first_factor(uint64_t)`. This function would be required to return a result +at compile time for any 64-bit integer --- possibly with the help of compiler built-ins, if it could +not be done any other way. The feedback to this paper showed that this wasn't actually +a hard-blocker: it turns out that every practical case we have could be satisfied by a fast +primality checker. Still, we plan to continue investigating this avenue, both because it would make +the standard units library implementation much easier, and because this function would be widely +useful in many other domains. + ## Quantity references _Note: We know that probably the term "reference" will not survive too long in the Committee,