2023-06-21 10:55:18 +02:00
|
|
|
# Value Conversions
|
|
|
|
|
|
|
|
|
|
## Value-preserving conversions
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
auto q1 = 5 * km;
|
2023-08-23 16:46:15 +02:00
|
|
|
std::cout << q1.in(m) << '\n';
|
2023-06-21 10:55:18 +02:00
|
|
|
quantity<si::metre, int> q2 = q1;
|
|
|
|
|
```
|
|
|
|
|
|
2023-12-26 11:07:21 +01:00
|
|
|
The second line above converts the current quantity to the one expressed in meters and prints its
|
|
|
|
|
contents. The third line converts the quantity expressed in kilometers into the one measured
|
|
|
|
|
in meters.
|
2023-06-21 10:55:18 +02:00
|
|
|
|
|
|
|
|
In case a user would like to perform an opposite transformation:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
auto q1 = 5 * m;
|
2023-08-23 16:46:15 +02:00
|
|
|
std::cout << q1.in(km) << '\n';
|
2023-06-21 10:55:18 +02:00
|
|
|
quantity<si::kilo<si::metre>, int> q2 = q1;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Both conversions will fail to compile.
|
|
|
|
|
|
|
|
|
|
There are two ways to make the above work. The first solution is to use a floating-point
|
|
|
|
|
representation type:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
auto q1 = 5. * m;
|
2023-08-23 16:46:15 +02:00
|
|
|
std::cout << q1.in(km) << '\n';
|
2023-06-21 10:55:18 +02:00
|
|
|
quantity<si::kilo<si::metre>> q2 = q1;
|
|
|
|
|
```
|
|
|
|
|
|
2023-09-13 09:00:21 +02:00
|
|
|
or
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
auto q1 = 5 * m;
|
|
|
|
|
std::cout << value_cast<double>(q1).in(km) << '\n';
|
|
|
|
|
quantity<si::kilo<si::metre>> q2 = q1; // double by default
|
|
|
|
|
```
|
|
|
|
|
|
2023-08-31 18:57:39 +02:00
|
|
|
!!! important
|
|
|
|
|
|
|
|
|
|
The **mp-units** library follows [`std::chrono::duration`](https://en.cppreference.com/w/cpp/chrono/duration)
|
|
|
|
|
logic and treats floating-point types as value-preserving.
|
2023-06-21 10:55:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
## Value-truncating conversions
|
|
|
|
|
|
|
|
|
|
The second solution is to force a truncating conversion:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
auto q1 = 5 * m;
|
|
|
|
|
std::cout << value_cast<km>(q1) << '\n';
|
2023-09-13 10:44:50 +02:00
|
|
|
quantity<si::kilo<si::metre>, int> q2 = q1.force_in(km);
|
2023-06-21 10:55:18 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This explicit cast makes it clear that something unsafe is going on. It is easy to spot in code
|
|
|
|
|
reviews or while chasing a bug in the source code.
|
|
|
|
|
|
2023-09-13 10:44:50 +02:00
|
|
|
!!! note
|
|
|
|
|
|
|
|
|
|
`q.force_in(U)` is just a shortcut to run `value_cast<U>(q)`. There is no difference in behavior
|
|
|
|
|
between those two interfaces. `q.force_in(U)` was added for consistency with `q.in(U)` and
|
|
|
|
|
`q.force_numerical_value_in(U)`.
|
|
|
|
|
|
2023-06-21 10:55:18 +02:00
|
|
|
Another place where this cast is useful is when a user wants to convert a quantity with
|
2023-12-26 11:07:21 +01:00
|
|
|
a floating-point representation to the one using an integral one. Again, this is a truncating
|
2023-06-21 10:55:18 +02:00
|
|
|
conversion, so an explicit cast is needed:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
quantity<si::metre, int> q3 = value_cast<int>(3.14 * m);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
!!! info
|
|
|
|
|
|
2023-12-26 11:07:21 +01:00
|
|
|
It is often OK to use an integral as a representation type, but in general, floating-point
|
2023-06-21 10:55:18 +02:00
|
|
|
types provide better precision and are privileged in the library as they are considered
|
|
|
|
|
to be value-preserving.
|
2023-12-19 18:19:22 +01:00
|
|
|
|
|
|
|
|
In some cases, a unit and a representation type should be changed simultaneously. Moreover,
|
|
|
|
|
sometimes, the order of doing those operations matters. In such cases, the library provides
|
|
|
|
|
the `value_cast<U, Rep>(q)` which always returns the most precise result:
|
|
|
|
|
|
|
|
|
|
=== "C++23"
|
|
|
|
|
|
|
|
|
|
```cpp
|
2024-09-05 10:06:43 +02:00
|
|
|
inline constexpr struct dim_currency final : base_dimension<"$"> {} dim_currency;
|
|
|
|
|
inline constexpr struct currency final : quantity_spec<dim_currency> {} currency;
|
2023-12-19 18:19:22 +01:00
|
|
|
|
2024-09-05 10:06:43 +02:00
|
|
|
inline constexpr struct us_dollar final : named_unit<"USD", kind_of<currency>> {} us_dollar;
|
|
|
|
|
inline constexpr struct scaled_us_dollar final : named_unit<"USD_s", mag_power<10, -8> * us_dollar> {} scaled_us_dollar;
|
2023-12-19 18:19:22 +01:00
|
|
|
|
|
|
|
|
namespace unit_symbols {
|
|
|
|
|
|
2024-09-05 10:06:43 +02:00
|
|
|
inline constexpr auto USD = us_dollar;
|
|
|
|
|
inline constexpr auto USD_s = scaled_us_dollar;
|
2023-12-19 18:19:22 +01:00
|
|
|
|
|
|
|
|
} // namespace unit_symbols
|
|
|
|
|
|
2024-01-08 10:30:45 +01:00
|
|
|
using Price = quantity_point<currency[us_dollar]>;
|
|
|
|
|
using Scaled = quantity_point<currency[scaled_us_dollar], zeroth_point_origin<currency>, std::int64_t>;
|
2023-12-19 18:19:22 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
=== "C++20"
|
|
|
|
|
|
|
|
|
|
```cpp
|
2024-09-05 10:06:43 +02:00
|
|
|
inline constexpr struct dim_currency final : base_dimension<"$"> {} dim_currency;
|
|
|
|
|
inline constexpr struct currency final : quantity_spec<currency, dim_currency> {} currency;
|
2023-12-19 18:19:22 +01:00
|
|
|
|
2024-09-05 10:06:43 +02:00
|
|
|
inline constexpr struct us_dollar final : named_unit<"USD", kind_of<currency>> {} us_dollar;
|
|
|
|
|
inline constexpr struct scaled_us_dollar final : named_unit<"USD_s", mag_power<10, -8> * us_dollar> {} scaled_us_dollar;
|
2023-12-19 18:19:22 +01:00
|
|
|
|
|
|
|
|
namespace unit_symbols {
|
|
|
|
|
|
2024-09-05 10:06:43 +02:00
|
|
|
inline constexpr auto USD = us_dollar;
|
|
|
|
|
inline constexpr auto USD_s = scaled_us_dollar;
|
2023-12-19 18:19:22 +01:00
|
|
|
|
|
|
|
|
} // namespace unit_symbols
|
|
|
|
|
|
2024-01-08 10:30:45 +01:00
|
|
|
using Price = quantity_point<currency[us_dollar]>;
|
|
|
|
|
using Scaled = quantity_point<currency[scaled_us_dollar], zeroth_point_origin<currency>, std::int64_t>;
|
2023-12-19 18:19:22 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
=== "Portable"
|
|
|
|
|
|
|
|
|
|
```cpp
|
2024-09-05 10:06:43 +02:00
|
|
|
inline constexpr struct dim_currency final : base_dimension<"$"> {} dim_currency;
|
2023-12-19 18:19:22 +01:00
|
|
|
QUANTITY_SPEC(currency, dim_currency);
|
|
|
|
|
|
2024-09-05 10:06:43 +02:00
|
|
|
inline constexpr struct us_dollar final : named_unit<"USD", kind_of<currency>> {} us_dollar;
|
|
|
|
|
inline constexpr struct scaled_us_dollar final : named_unit<"USD_s", mag_power<10, -8> * us_dollar> {} scaled_us_dollar;
|
2023-12-19 18:19:22 +01:00
|
|
|
|
|
|
|
|
namespace unit_symbols {
|
|
|
|
|
|
2024-09-05 10:06:43 +02:00
|
|
|
inline constexpr auto USD = us_dollar;
|
|
|
|
|
inline constexpr auto USD_s = scaled_us_dollar;
|
2023-12-19 18:19:22 +01:00
|
|
|
|
|
|
|
|
} // namespace unit_symbols
|
|
|
|
|
|
2024-01-08 10:30:45 +01:00
|
|
|
using Price = quantity_point<currency[us_dollar]>;
|
|
|
|
|
using Scaled = quantity_point<currency[scaled_us_dollar], zeroth_point_origin<currency>, std::int64_t>;
|
2023-12-19 18:19:22 +01:00
|
|
|
```
|
2024-02-27 09:43:41 +01:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
using namespace unit_symbols;
|
|
|
|
|
Price price{12.95 * USD};
|
|
|
|
|
Scaled spx = value_cast<USD_s, std::int64_t>(price);
|
|
|
|
|
```
|
2024-05-12 11:13:00 +02:00
|
|
|
|
2024-07-04 22:05:40 +01:00
|
|
|
As a shortcut, instead of providing a unit and a representation type to `value_cast`, you may also
|
|
|
|
|
provide a `Quantity` type directly, from which unit and representation type are taken. However,
|
|
|
|
|
`value_cast<Quantity>`, still only allows for changes in unit and representation type, but not
|
|
|
|
|
changing the type of the quantity. For that, you will have to use a `quantity_cast` instead.
|
|
|
|
|
|
|
|
|
|
Overloads are also provided for instances of `quantity_point`. All variants of `value_cast<...>(q)`
|
|
|
|
|
that apply to instances of `quantity` have a corresponding version applicable to `quantity_point`,
|
|
|
|
|
where the `point_origin` remains untouched, and the cast changes how the "offset" from the origin
|
|
|
|
|
is represented. Specifically, for any `quantity_point` instance `qp`, all of the following
|
|
|
|
|
equivalences hold:
|
2024-05-12 11:13:00 +02:00
|
|
|
|
2024-06-03 20:43:23 +02:00
|
|
|
```cpp
|
2024-07-04 22:05:40 +01:00
|
|
|
static_assert(value_cast<Rep>(qp) == quantity_point{value_cast<Rep>(qp.quantity_from(qp.point_origin)), qp.point_origin});
|
|
|
|
|
static_assert(value_cast<U>(qp) == quantity_point{value_cast<U>(qp.quantity_from(qp.point_origin)), qp.point_origin});
|
|
|
|
|
static_assert(value_cast<U, Rep>(qp) == quantity_point{value_cast<U, Rep>(qp.quantity_from(qp.point_origin)), qp.point_origin});
|
|
|
|
|
static_assert(value_cast<Q>(qp) == quantity_point{value_cast<Q>(qp.quantity_from(qp.point_origin)), qp.point_origin});
|
2024-06-03 20:43:23 +02:00
|
|
|
```
|
|
|
|
|
|
2024-07-04 22:05:40 +01:00
|
|
|
Furthermore, there is one additional overload `value_cast<ToQP>(qp)`. This overload permits to
|
|
|
|
|
additionally replace the `point_origin` with another compatible one, while still representing
|
|
|
|
|
the same point in the affine space. Thus, it is roughly equivalent to
|
2024-06-03 20:43:23 +02:00
|
|
|
`value_cast<ToQP::unit, ToQP::rep>(qp).point_for(ToQP::point_origin)`.
|
2024-05-12 11:13:00 +02:00
|
|
|
In contrast to a separate `value_cast` followed by `point_for` (or vice-versa), the combined
|
2024-07-04 22:05:40 +01:00
|
|
|
`value_cast` tries to choose the order of the individual conversion steps in a way to avoid both
|
|
|
|
|
overflow and unnecessary loss of precision. Overflow is a risk because the change of origin point
|
2024-05-12 11:13:00 +02:00
|
|
|
may require an addition of a potentially large offset (the difference between the origin points),
|
|
|
|
|
which may well be outside the range of one or both quantity types.
|
2024-07-04 22:05:40 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
## Value conversions summary
|
|
|
|
|
|
2024-09-24 09:02:48 +02:00
|
|
|
The table below provides all the value conversion functions that may be run on `x` being the
|
2024-07-04 22:05:40 +01:00
|
|
|
instance of either `quantity` or `quantity_point`:
|
|
|
|
|
|
2024-09-13 21:38:59 +02:00
|
|
|
| Forcing | Representation | Unit | Member function | Non-member function |
|
|
|
|
|
|:-------:|:--------------:|:----:|--------------------|------------------------------------------------|
|
|
|
|
|
| No | Same | `u` | `x.in(u)` | |
|
|
|
|
|
| No | `T` | Same | `x.in<T>()` | |
|
|
|
|
|
| No | `T` | `u` | `x.in<T>(u)` | |
|
|
|
|
|
| Yes | Same | `u` | `x.force_in(u)` | `value_cast<u>(x)` |
|
|
|
|
|
| Yes | `T` | Same | `x.force_in<T>()` | `value_cast<T>(x)` |
|
|
|
|
|
| Yes | `T` | `u` | `x.force_in<T>(u)` | `value_cast<u, T>(x)` or `value_cast<T, u>(x)` |
|