Constant¶
Constant is a family of template types, each of which represents a single specific quantity value.
Recall that the usual way to represent quantity values is with a Quantity<Unit, Rep>
type. This holds a numeric variable of type Rep inside, letting it represent
many possible quantity values. By contrast, Constant is an empty class and has no “rep”: the
single value it can represent is fully encoded in its type. This makes it an example of
a monovalue type.
Because the value is always fully known at compile time, we do not need to use a heuristic like the
overflow safety surface to determine which conversions are
allowed. Instead, we can achieve a perfect conversion policy: we allow converting to any Quantity
that can represent the value exactly, and disallow all other conversions.
The main use of Constant is to multiply and divide raw numbers or Quantity values. When we do
this, the constant is applied symbolically, and affects the units of the resulting quantity.
For example, multiplying a duration in seconds by a constant representing the speed of light
produces a length, measured in units of light-seconds. Notably, the underlying stored numeric
value does not change: whether a duration of 5 seconds, or a length of 5 light-seconds, we
still store 5 under the hood.
This approach means that if subsequent operations cancel out the constant, this cancellation is both exact and has zero runtime cost.
Built-in constants included with Au¶
Au includes a number of built-in constants. Each constant is in its own include file, in the folder
"au/constant" — for example, "au/constant/speed_of_light.hh".
The constant object itself is in the top-level au:: namespace, and uses an UPPERCASE naming
convention, as with all other constants in the library — for example, au::SPEED_OF_LIGHT.
We spell out the constant name in full to avoid ambiguity. However, this can be overly verbose. We encourage users to define their own copy of each constant, with a more usable name — for example:
This “copy” is essentially free, because the constant is a monovalue type, and therefore empty.
Here are the constants that we include with Au:
| Name | Symbol | Value | Include (under "au/constants/") |
Object name (under au:: namespace) |
|---|---|---|---|---|
| Avogadro constant | N_A | 6.022\,140\,76 \times 10^{23}\,\, \text{mol}^{-1} | avogadro_constant.hh |
AVOGADRO_CONSTANT |
| Boltzmann constant | k_B | 1.380\,649 \times 10^{-23}\,\, \text{J} / \text{K} | boltzmann_constant.hh |
BOLTZMANN_CONSTANT |
| Cesium hyperfine transition frequency | \Delta \nu_{Cs} | 9\,192\,631\,770\,\, \text{Hz} | cesium_hyperfine_transition_frequency.hh |
CESIUM_HYPERFINE_TRANSITION_FREQUENCY |
| Elementary charge | e | 1.602\,176\,634 \times 10^{-19}\,\, \text{C} | elementary_charge.hh |
ELEMENTARY_CHARGE |
| Luminous efficacy of light at 540\,\, \text{THz} | K_{cd} | 683\,\, \text{lm} / \text{W} | luminous_efficacy_540_terahertz.hh |
LUMINOUS_EFFICACY_540_TERAHERTZ |
| Planck constant | h | 6.626\,070\,15 \times 10^{-34}\,\, \text{J} \cdot \text{s} | planck_constant.hh |
PLANCK_CONSTANT |
| Reduced Planck constant | \hbar | 1.054\,571\,817 \times 10^{-34}\,\, \text{J} \cdot \text{s} | reduced_planck_constant.hh |
REDUCED_PLANCK_CONSTANT |
| Speed of light | c | 299\,792\,458\,\, \text{m} / \text{s} | speed_of_light.hh |
SPEED_OF_LIGHT |
| Standard Gravity | g_0 | 9.806\,65\,\, \text{m} / \text{s}^2 | standard_gravity.hh |
STANDARD_GRAVITY |
Our policy is to include only exactly defined constants with the library. This rules out many useful constants, such as the universal gravitational constant G, the new (post-2019) permeability of free space \mu_0, and so on. For these, we can’t reasonably provide values that will satisfy all users at all times. However, defining custom constants for your own project is straightforward, as we explain in the next section, and in our how-to guide for custom constants.
Constructing Constant¶
Constant encodes all information about the value in its type. Moreover, it has only a single
template parameter, which is a unit. Therefore, the first step is to encode your
quantity as a unit — that is, to define the unit “U” such that your quantity has a value of
“1 U”.
To do this, follow the usual instructions for creating new units. Note
that you can use a much simpler definition that omits most of the optional features. The only
important ones are those labeled [1] (the strong type definition) and [2] (the unit label).
Having defined your unit, you can pass an instance to the make_constant function. If the unit you
defined above is called YourUnits, and the constant is called YOUR_CONSTANT, then the constant
definition will look like this:
Finally, note that the argument to make_constant() is a unit
slot, so you can pass “unit-like” alternatives such as
QuantityMaker or SymbolFor instances as well.
Full worked example: speed of light
Let’s look at an example of defining a constant for the speed of light. Both the name of the
instance and the label will be c.
Ad hoc constants¶
You can obtain many of the benefits of Constant even if you don’t formally define a new unit.
Because make_constant has a unit slot API, you can pass an ad hoc expression to it. For example:
The main advantage of doing this is its conciseness: the constant definition is a single, readable
line. The built constant also has all of the multiplication and division operators types that
Constant supports, as well as its perfect conversion policy to any Quantity type.
The only disadvantage is that an ad hoc Constant gets an ad hoc unit symbol, instead of a simple,
concise symbol. The example below demonstrates the difference.
Example: ad hoc constants get ad hoc labels
Let’s take the following speed, and express it in terms of the speed of light, using a constant:
First, an ad hoc constant:
constexpr auto c = make_constant(meters / second * mag<299'792'458>());
std::cout << v.as(c) << std::endl;
// Output:
// "9.69257e-08 [299792458 m / s]"
Note how the program is correct, and the label is accurate, but clunky: it prints
[299792458 m / s] instead of a concise label such as c.
By contrast, we can use a fully defined constant. This is generally a little more effort, but
some constants, such as SPEED_OF_LIGHT, are included out of the box:
// Found in `"au/constants/speed_of_light.hh"`:
std::cout << v.as(SPEED_OF_LIGHT) << std::endl;
// Output:
// "9.69257e-08 c"
There’s no difference in the program that gets executed, but the printed label is a lot easier to understand.
Constant and unit slots¶
Constant can be passed to any API that takes a unit slot.
Converting to Quantity¶
Constant can be converted to any Quantity type of the same dimension.
By default, this conversion policy is perfect. This means that it permits converting to any
Quantity that can represent the value exactly, and disallows all other conversions. Users can
also override this policy by choosing the “coerce” variant of any API (say, using .coerce_as()
instead of .as()).
Finally, it’s important to appreciate that Constant has no rep, no underlying numeric type.
Therefore, every Quantity conversion API requires an explicit template parameter to specify the
desired rep.
.as<T>()¶
This function expresses the constant as a Quantity in “units of this constant”. Therefore, the
underlying stored value will be T{1}, and the rep will be T.
.as<T>(unit)¶
This function expresses the constant as a Quantity in the requested unit, using a rep of T. It
has a perfect conversion policy, which means that it compiles if and only if the constant’s value in
the requested unit can be exactly represented in the type T.
The argument unit is a unit slot API, so it accepts a unit
instance, quantity maker instance, or any other instance compatible with a unit slot.
.as<T>(unit, policy)¶
This function expresses the constant as a Quantity in the requested unit, using a rep of T. As
usual, Constant has perfect knowledge at compile time about whether the conversion will cause
overflow or truncation. However, if the policy argument instructs it to ignore either of these
problems, then it will not perform the corresponding safety check.
The argument unit is a unit slot API, so it accepts a unit
instance, quantity maker instance, or any other instance compatible with a unit slot.
The argument policy is a conversion risk policy. In this
context, instead of applying to the risk of a problem, it applies to the actual existence of
that problem. For example: you would not need to provide ignore(TRUNCATION_RISK) unless the
specific conversion will actually cause truncation.
Tip
This overload is typically only useful when you know that a specific conversion is actually lossy, but you want it anyway. Any non-lossy conversion would already be allowed with the simpler overload, which should be preferred.
Additionally, the conversion risks are not equally useful. Use cases for
ignore(TRUNCATION_RISK) are straightforward to imagine: for example, you may simply want
a less precise version of the context. Use cases for ignore(OVERFLOW_RISK) are very hard to
imagine, as they will produce a grossly incorrect result with no physical relationship to the
actual value.
.coerce_as<T>(unit)¶
Warning
We plan to deprecate APIs with coerce in their name in the 0.6.0 release (#481). For
a Constant c, instead of c.coerce_as<T>(unit), prefer c.as<T>(unit, policy), where
policy is the desired conversion risk policy.
This function expresses the constant as a Quantity in the requested unit, using a rep of T. It
is similar to .as<T>(unit), except that it will ignore the safety checks that
prevent truncation and overflow.
Warning
Because .as<T>(unit) has a perfect conversion policy, we know that this function either
produces the exact same result (in which case you could simply call .as<T>(unit)), or it
produces a result which is guaranteed to be lossy. Therefore, be very judicious in using
this function.
.in<T>(unit)¶
This function produces a raw numeric value, of type T, holding the value of the constant in the
requested unit. It has a perfect conversion policy, which means that it compiles if and only if the
constant’s value in the requested unit can be exactly represented in the type T.
The argument unit is a unit slot API, so it accepts a unit
instance, quantity maker instance, or any other instance compatible with a unit slot.
.in<T>(unit, policy)¶
This function produces a raw numeric value, of type T, holding the value of the constant in the
requested unit. As usual, Constant has perfect knowledge at compile time about whether the
conversion will cause overflow or truncation. However, if the policy argument instructs it to
ignore either of these problems, then it will not perform the corresponding safety check.
The argument unit is a unit slot API, so it accepts a unit
instance, quantity maker instance, or any other instance compatible with a unit slot.
The argument policy is a conversion risk policy. In this
context, instead of applying to the risk of a problem, it applies to the actual existence of
that problem. For example: you would not need to provide ignore(TRUNCATION_RISK) unless the
specific conversion will actually cause truncation.
Tip
This overload is typically only useful when you know that a specific conversion is actually lossy, but you want it anyway. Any non-lossy conversion would already be allowed with the simpler overload, which should be preferred.
Additionally, the conversion risks are not equally useful. Use cases for
ignore(TRUNCATION_RISK) are straightforward to imagine: for example, you may simply want
a less precise version of the context. Use cases for ignore(OVERFLOW_RISK) are very hard to
imagine, as they will produce a grossly incorrect result with no physical relationship to the
actual value.
.coerce_in<T>(unit)¶
Warning
We plan to deprecate APIs with coerce in their name in the 0.6.0 release (#481). For
a Constant c, instead of c.coerce_in<T>(unit), prefer c.in<T>(unit, policy), where
policy is the desired conversion risk policy.
This function produces a raw numeric value, of type T, holding the value of the constant in the
requested unit. It is similar to .in<T>(unit), except that it will ignore the
safety checks that prevent truncation and overflow.
Warning
Because .in<T>(unit) has a perfect conversion policy, we know that this function either
produces the exact same result (in which case you could simply call .in<T>(unit)), or it
produces a result which is guaranteed to be lossy. Therefore, be very judicious in using
this function.
Implicit Quantity conversion¶
Constant will implicitly convert to any Quantity type which passes the safety checks on
truncation and overflow. Essentially: any time .as<T>(unit) produces a result, that
same result can be obtained via implicit conversion.
This provides great flexibility and confidence in passing Constant values to APIs that take
Quantity.
Note
The fact that Constant has a perfect conversion policy means that we can use it with APIs
where the corresponding Quantity would not work, because Quantity is forced to use the
overflow safety surface, which is a more conservative
heuristic.
For example, suppose you have an API accepting Quantity<UnitQuotientT<Meters, Seconds>, int>,
and a constant c representing the speed of light.
You will be able to pass c to this API, because the constant-to-quantity conversion operation
knows the exact value at compile time, and can verify that it fits in an int.
By contrast, you would not be able to pass c.as<int>() (which is a Quantity). Even though
it would work for this specific value (which is 1), this quantity-to-quantity conversion is
too dangerous for int in general.
Operations¶
Comparison †¶
Two Constant instances can be compared if they have the same dimension. The result indicates
which constant is larger.
† This feature is subject to the same compile-time arithmetic
limitations as Magnitude ordering, because
the comparison is built on Magnitude comparison.
Syntax:
For instances c1 and c2:
c1 == c2c1 != c2c1 < c2c1 > c2c1 <= c2c1 >= c2
Rounding †¶
Constant can be passed to the rounding functions. The _as functions return a new Constant,
while the _in functions return a raw numeric value of an explicitly specified type.
† This feature is subject to the same compile-time arithmetic
limitations as Magnitude rounding, because
the computation is built on Magnitude rounding.
Syntax:
For a Constant instance c and a unit u:
_as functions (return a Constant with a unit quantity-equivalent to u):
round_as(u, c)— roundscto the nearest integer in unitufloor_as(u, c)— roundscdown to the largest integer not greater thancin unituceil_as(u, c)— roundscup to the smallest integer not less thancin unituint_round_as(u, c)— same asround_as(u, c)forConstantinputsint_floor_as(u, c)— same asfloor_as(u, c)forConstantinputsint_ceil_as(u, c)— same asceil_as(u, c)forConstantinputs
_in functions (return a raw numeric value; explicit Rep required):
round_in<T>(u, c)— roundscto the nearest integer in unitu, returning typeTfloor_in<T>(u, c)— roundscdown, returning typeTceil_in<T>(u, c)— roundscup, returning typeTint_round_in<T>(u, c)— same asround_in<T>(u, c)forConstantinputsint_floor_in<T>(u, c)— same asfloor_in<T>(u, c)forConstantinputsint_ceil_in<T>(u, c)— same asceil_in<T>(u, c)forConstantinputs
Note
Pay attention to the peculiar pattern of presence and absence of the explicit Rep in these
functions. In particular, an explicit rep is required for the _in functions, but
forbidden for the _as functions!
This is a simple consequence of the fact that Constant has no rep. The type itself
represents only a single, specific value (that is, it is a monovalue
type).
Here is the pattern to remember:
-
The
_infunctions require an explicit rep because the inputConstanthas no rep, so we would have no idea what rep to use “by default”. -
The
_asfunctions forbid an explicit rep because the output is anotherConstant, and thus no rep would even be possible.
Example: using int_ceil_in for array dimensions
Suppose you have a constant representing a key length in bits, and you need to determine the
number of bytes required to store it. Let’s say the key length is not an even multiple of
8 bits, so you couldn’t just write MAX_KEY_LEN.in<std::size_t>(bytes): the Constant would
detect the truncation, and produce a compile-time error! What we really need is the smallest
number of whole bytes that can hold this many bits. We can achieve this with int_ceil_in:
constexpr auto MAX_KEY_LEN = make_constant(bits * mag<250>());
constexpr auto bytes_needed = int_ceil_in<std::size_t>(bytes, MAX_KEY_LEN);
std::array<uint8_t, bytes_needed> key_storage; // Array of 32 bytes
In this particular case, we could just as well have used ceil_in as int_ceil_in: for
Constant, they are identical. However, using int_ceil_in makes us agnostic as to whether
MAX_KEY_LEN is a Constant or a Quantity. Recall that the ceil_in family of functions
for Quantity are based on std::ceil, which is not constexpr compatible. The int_ceil_in
family, by contrast, is constexpr for all its inputs, and uses only integer-domain math.
Multiplication and division¶
Each of the following operations with a Constant consists in multiplying or dividing with some
other family of types.
Raw numeric type T¶
Multiplying or dividing Constant<Unit> with a raw numeric type T produces a Quantity whose rep
is T, and whose unit is derived from Unit.
In the following table, we will use x to represent the value that was stored in the input of type
T.
| Operation | Resulting Type | Underlying Value | Notes |
|---|---|---|---|
Constant<Unit> * T |
Quantity<Unit, T> |
x |
|
Constant<Unit> / T |
Quantity<Unit, T> |
T{1} / x |
Disallowed for integral T |
T * Constant<Unit> |
Quantity<Unit, T> |
x |
|
T / Constant<Unit> |
Quantity<UnitInverseT<Unit>, T> |
x |
Quantity<U, R>¶
Multiplying or dividing Constant<Unit> with a Quantity<U, R> produces a Quantity whose rep is
R, and whose unit is derived from Unit and U.
In the following table, we will use x to represent the underlying value in the input quantity —
that is, if the input quantity was q, then x is q.in(U{}).
| Operation | Resulting Type | Underlying Value | Notes |
|---|---|---|---|
Constant<Unit> * Quantity<U, R> |
Quantity<UnitProductT<Unit, U>, R> |
x |
|
Constant<Unit> / Quantity<U, R> |
Quantity<UnitQuotientT<Unit, U>, R> |
R{1} / x |
Disallowed for integral R |
Quantity<U, R> * Constant<Unit> |
Quantity<UnitProductT<U, Unit>, R> |
x |
|
Quantity<U, R> / Constant<Unit> |
Quantity<UnitQuotientT<U, Unit>, R> |
x |
Constant<U>¶
Constants compose: the product or quotient of two Constant instances is a new Constant instance.
| Operation | Resulting Type |
|---|---|
Constant<Unit> * Constant<U> |
Constant<UnitProductT<Unit, U>> |
Constant<Unit> / Constant<U> |
Constant<UnitQuotientT<Unit, U>> |
QuantityMaker<U>¶
Multiplying or dividing Constant<Unit> with a QuantityMaker<U> produces a new QuantityMaker
whose unit is derived from Unit and U.
| Operation | Resulting Type |
|---|---|
Constant<Unit> * QuantityMaker<U> |
QuantityMaker<UnitProductT<Unit, U>> |
Constant<Unit> / QuantityMaker<U> |
QuantityMaker<UnitQuotientT<Unit, U>> |
QuantityMaker<U> * Constant<Unit> |
QuantityMaker<UnitProductT<U, Unit>> |
QuantityMaker<U> / Constant<Unit> |
QuantityMaker<UnitQuotientT<U, Unit>> |
SingularNameFor<U>¶
Multiplying or dividing Constant<Unit> with a SingularNameFor<U> produces a new
SingularNameFor whose unit is derived from Unit and U.
| Operation | Resulting Type |
|---|---|
Constant<Unit> * SingularNameFor<U> |
SingularNameFor<UnitProductT<Unit, U>> |
Constant<Unit> / SingularNameFor<U> |
SingularNameFor<UnitQuotientT<Unit, U>> |
SingularNameFor<U> * Constant<Unit> |
SingularNameFor<UnitProductT<U, Unit>> |
SingularNameFor<U> / Constant<Unit> |
SingularNameFor<UnitQuotientT<U, Unit>> |
Magnitude<BPs...>¶
Multiplying or dividing Constant<Unit> with a Magnitude produces a new Constant which is
scaled by that magnitude.
In the following table, let m be an instance of Magnitude<BPs...>.
| Operation | Resulting Type |
|---|---|
Constant<Unit> * Magnitude<BPs...> |
Constant<decltype(Unit{} * m)> |
Constant<Unit> / Magnitude<BPs...> |
Constant<decltype(Unit{} / m)> |
Magnitude<BPs...> * Constant<Unit> |
Constant<decltype(Unit{} * m)> |
Magnitude<BPs...> / Constant<Unit> |
Constant<decltype(UnitInverseT<Unit>{} * m)> |
Zero¶
Multiplying Constant<Unit> with Zero produces Zero.
| Operation | Resulting Type |
|---|---|
Constant<Unit> * Zero |
Zero |
Zero * Constant<Unit> |
Zero |
QuantityPointMaker<U> (deleted)¶
Multiplying or dividing Constant<Unit> with a QuantityPointMaker<U> is explicitly deleted,
because quantity points do not support multiplication.
QuantityPoint<U, R> (deleted)¶
Multiplying or dividing Constant<Unit> with a QuantityPoint<U, R> is explicitly deleted,
because quantity points do not support multiplication.