Merge branch 'master' of github.com:mpusz/units

This commit is contained in:
Mateusz Pusz
2024-09-17 17:14:26 -06:00
21 changed files with 414 additions and 83 deletions

View File

@@ -143,6 +143,20 @@ jobs:
std_format_support: "True",
conan-config: "",
}
- {
name: "Clang-18 on Apple M1 (arm64)",
os: macos-14,
compiler:
{
type: CLANG,
version: 18,
cc: "/opt/homebrew/opt/llvm@18/bin/clang-18",
cxx: "/opt/homebrew/opt/llvm@18/bin/clang++",
},
lib: "libc++",
cxx_modules: "False",
std_format_support: "True"
}
- {
name: "Apple Clang 15",
os: macos-13,
@@ -196,8 +210,8 @@ jobs:
shell: bash
run: |
sudo apt install -y g++-${{ matrix.config.compiler.version }}
- name: Install Clang
if: matrix.config.compiler.type == 'CLANG'
- name: Install Clang with apt
if: matrix.config.compiler.type == 'CLANG' && matrix.config.os != 'macos-14'
shell: bash
working-directory: ${{ env.HOME }}
run: |
@@ -205,8 +219,13 @@ jobs:
chmod +x llvm.sh
sudo ./llvm.sh ${{ matrix.config.compiler.version }}
sudo apt install -y clang-tools-${{ matrix.config.compiler.version }}
- name: Install Clang using homebrew
if: matrix.config.compiler.type == 'CLANG' && matrix.config.os == 'macos-14'
shell: bash
run: |
brew install llvm@18
- name: Install Libc++
if: matrix.config.compiler.type == 'CLANG' && matrix.config.lib == 'libc++'
if: matrix.config.compiler.type == 'CLANG' && matrix.config.lib == 'libc++' && matrix.config.os != 'macos-14'
shell: bash
run: |
sudo apt install -y libc++-${{ matrix.config.compiler.version }}-dev libc++abi-${{ matrix.config.compiler.version }}-dev libunwind-${{ matrix.config.compiler.version }}-dev

View File

@@ -136,6 +136,20 @@ jobs:
cxx_modules: "False",
std_format_support: "True"
}
- {
name: "Clang-18 on Apple M1 (arm64)",
os: macos-14,
compiler:
{
type: CLANG,
version: 18,
cc: "/opt/homebrew/opt/llvm@18/bin/clang-18",
cxx: "/opt/homebrew/opt/llvm@18/bin/clang++",
},
lib: "libc++",
cxx_modules: "False",
std_format_support: "True"
}
- {
name: "Apple Clang 15",
os: macos-14,
@@ -188,8 +202,8 @@ jobs:
shell: bash
run: |
sudo apt install -y g++-${{ matrix.config.compiler.version }}
- name: Install Clang
if: matrix.config.compiler.type == 'CLANG'
- name: Install Clang with apt
if: matrix.config.compiler.type == 'CLANG' && matrix.config.os != 'macos-14'
shell: bash
working-directory: ${{ env.HOME }}
run: |
@@ -197,8 +211,13 @@ jobs:
chmod +x llvm.sh
sudo ./llvm.sh ${{ matrix.config.compiler.version }}
sudo apt install -y clang-tools-${{ matrix.config.compiler.version }}
- name: Install Clang using homebrew
if: matrix.config.compiler.type == 'CLANG' && matrix.config.os == 'macos-14'
shell: bash
run: |
brew install llvm@18
- name: Install Libc++
if: matrix.config.compiler.type == 'CLANG' && matrix.config.lib == 'libc++'
if: matrix.config.compiler.type == 'CLANG' && matrix.config.lib == 'libc++' && matrix.config.os != 'macos-14'
shell: bash
run: |
sudo apt install -y libc++-${{ matrix.config.compiler.version }}-dev libc++abi-${{ matrix.config.compiler.version }}-dev libunwind-${{ matrix.config.compiler.version }}-dev

View File

@@ -83,7 +83,7 @@ static_assert(10 * km / (5 * km) == 2 * one);
static_assert(1000 / (1 * s) == 1 * kHz);
```
_Try it on the [Compiler Explorer](https://godbolt.org/z/8acPeq743)._
_Try it on the [Compiler Explorer](https://godbolt.org/z/fT1r4sohs)._
This library heavily uses C++20 features (concepts, classes as NTTPs, ...). Thanks to
them the user gets a powerful but still easy to use interfaces and all unit conversions

View File

@@ -13,14 +13,29 @@
The table below provides the minimum compiler version required to compile the code using a specific
C++ feature:
| C++ Feature | C++ version | gcc | clang | apple-clang | MSVC |
|-----------------------------------------------------------|:-----------:|:----:|:-----:|:-----------:|:----:|
| **Minimum support** | 20 | 12 | 16 | 15 | None |
| **`std::format`** | 20 | 13 | 17 | None | None |
| **C++ modules** | 20 | None | 17 | None | None |
| **`import std;`** | 23 | None | 18 | None | None |
| **Static `constexpr` variables in `constexpr` functions** | 23 | 13 | 17 | None | None |
| **Explicit `this` parameter** | 23 | 14 | 18 | None | None |
| C++ Feature | C++ version | gcc | clang | apple-clang | MSVC |
|-----------------------------------------------------------|:-----------:|:----:|:-----:|:-----------:|:-----------------------------------------:|
| **Minimum support** | 20 | 12 | 16 | 15 | 194 :bug:{ title="BEWARE of MSVC Bugs!" } |
| **`std::format`** | 20 | 13 | 17 | None | 194 |
| **C++ modules** | 20 | None | 17 | None | None |
| **`import std;`** | 23 | None | 18 | None | None |
| **Static `constexpr` variables in `constexpr` functions** | 23 | 13 | 17 | None | None |
| **Explicit `this` parameter** | 23 | 14 | 18 | None | None |
??? note "MSVC bugs"
MSVC still has a poor C++20 conformance. We had to make many workarounds to our codebase to
make it compile on this compiler. Usage of such nasty preprocessor macros degrade the
readability and maintainability of our code. This is why we've applied those patches to
the main library code but not to unit tests and examples. Those still do not compile on MSVC.
Here is a list of the most important MSVC bugs:
- [Discrepancy in Behavior of operator*= and operator* for Multiplying int and double at compile time](https://developercommunity.visualstudio.com/t/Discrepancy-in-Behavior-of-operator-an/10732445)
- [Syntax error when using non-type template parameters in templated class member function](https://developercommunity.visualstudio.com/t/Syntax-error-when-using-non-type-templat/10729428)
- [Type always preferred over value when using qualified identifiers](https://developercommunity.visualstudio.com/t/Type-always-prefered-over-value-when-usi/10729382)
Please upvote them so they get a higher fixing priority at Microsoft.
!!! important

View File

@@ -56,7 +56,7 @@ Here is a small example of operations possible on scalar quantities:
static_assert(1000 / (1 * s) == 1 * kHz);
```
!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/8acPeq743)"
!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/fT1r4sohs)"
This library requires some C++20 features ([concepts and constraints](https://en.cppreference.com/w/cpp/language/constraints),

View File

@@ -2,12 +2,11 @@
tags:
- CGS System
- International System
- Text Formatting
---
# `avg_speed`
!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/TnqGa4sdn)"
!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/v9c5T6bc4)"
Let's continue the previous example. This time, our purpose will not be to showcase as many
library features as possible, but we will scope on different interfaces one can provide
@@ -18,28 +17,28 @@ First, we either import a module or include all the necessary header files and i
the identifiers from the `mp_units` namespace:
```cpp title="avg_speed.cpp" linenums="1"
--8<-- "example/avg_speed.cpp:28:42"
--8<-- "example/avg_speed.cpp:28:46"
```
Next, we define two functions calculating average speed based on quantities of fixed units
and integral and floating-point representation types, respectively, and a third function
that we introduced in the [previous example](hello_units.md):
```cpp title="avg_speed.cpp" linenums="16"
--8<-- "example/avg_speed.cpp:44:58"
```cpp title="avg_speed.cpp" linenums="20"
--8<-- "example/avg_speed.cpp:48:62"
```
We also added a simple utility to print our results:
```cpp title="avg_speed.cpp" linenums="31"
--8<-- "example/avg_speed.cpp:60:66"
```cpp title="avg_speed.cpp" linenums="35"
--8<-- "example/avg_speed.cpp:64:70"
```
Now, let's analyze how those three utility functions behave with different sets of arguments.
First, we are going to use quantities of SI units and integral representation:
```cpp title="avg_speed.cpp" linenums="38"
--8<-- "example/avg_speed.cpp:68:82"
```cpp title="avg_speed.cpp" linenums="42"
--8<-- "example/avg_speed.cpp:72:86"
```
The above provides the following output:
@@ -61,8 +60,8 @@ representation types (the resulting speed is `108 km/h`).
The second scenario is really similar to the previous one, but this time, function arguments
have floating-point representation types:
```cpp title="avg_speed.cpp" linenums="53"
--8<-- "example/avg_speed.cpp:84:95"
```cpp title="avg_speed.cpp" linenums="57"
--8<-- "example/avg_speed.cpp:88:99"
```
Conversion from floating-point to integral representation types is
@@ -82,8 +81,8 @@ Average speed of a car that makes 220 km in 2 h is 110 km/h.
Next, let's do the same for integral and floating-point representations, but this time
using international mile:
```cpp title="avg_speed.cpp" linenums="65"
--8<-- "example/avg_speed.cpp:97:129"
```cpp title="avg_speed.cpp" linenums="69"
--8<-- "example/avg_speed.cpp:101:132"
```
One important difference here is the fact that as it is not possible to make a lossless conversion
@@ -108,8 +107,8 @@ Please note how the first and third results get truncated using integral represe
In the end, we repeat the scenario for CGS units:
```cpp title="avg_speed.cpp" linenums="97"
--8<-- "example/avg_speed.cpp:131:161"
```cpp title="avg_speed.cpp" linenums="101"
--8<-- "example/avg_speed.cpp:134:165"
```
Again, we observe `value_cast` being used in the same places and consistent truncation errors
@@ -129,6 +128,6 @@ Average speed of a car that makes 2.2e+07 cm in 7200 s is 110 km/h.
The example file ends with a simple `main()` function:
```cpp title="avg_speed.cpp" linenums="128"
--8<-- "example/avg_speed.cpp:163:"
```cpp title="avg_speed.cpp" linenums="133"
--8<-- "example/avg_speed.cpp:167:"
```

View File

@@ -6,32 +6,32 @@ tags:
# `hello_units`
!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/bT4GGPbef)"
!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/MYn5qjPzh)"
This is a really simple example showcasing the features of the **mp-units** library.
First, we either import the `mp_units` module or include the headers for:
- an International System of Quantities (ISQ)
- an International System of units (SI)
- units derived from the International Yard and Pound
- text formatting and stream output support
- an International System of Quantities (ISQ),
- an International System of units (SI),
- units derived from the International Yard and Pound,
- text formatting and stream output support.
```cpp title="hello_units.cpp" linenums="1"
--8<-- "example/hello_units.cpp:28:41"
--8<-- "example/hello_units.cpp:28:45"
```
Also, to shorten the definitions, we "import" all the symbols from the `mp_units` namespace.
```cpp title="hello_units.cpp" linenums="14"
--8<-- "example/hello_units.cpp:42:43"
```cpp title="hello_units.cpp" linenums="18"
--8<-- "example/hello_units.cpp:46:47"
```
Next, we define a simple function that calculates the average speed based on the provided
arguments of length and time:
```cpp title="hello_units.cpp" linenums="15"
--8<-- "example/hello_units.cpp:44:47"
```cpp title="hello_units.cpp" linenums="19"
--8<-- "example/hello_units.cpp:48:51"
```
The above function template takes any quantities implicitly convertible to `isq::length`
@@ -45,37 +45,37 @@ that its quantity type is implicitly convertible to `isq::speed`.
type is beneficial for users of such a function as it provides more information
of what to expect from a function than just using `auto`.
```cpp title="hello_units.cpp" linenums="19"
--8<-- "example/hello_units.cpp:49:52"
```cpp title="hello_units.cpp" linenums="23"
--8<-- "example/hello_units.cpp:53:56"
```
The above lines explicitly opt into using unit symbols from two systems of units.
As this introduces a lot of short identifiers into the current scope, it is not done
implicitly while including a header file.
```cpp title="hello_units.cpp" linenums="23"
--8<-- "example/hello_units.cpp:54:60"
```cpp title="hello_units.cpp" linenums="27"
--8<-- "example/hello_units.cpp:58:64"
```
- Lines `23` & `24` create a quantity of kind `isq::length / isq::time` with the numbers
- Lines `27` & `28` create a quantity of kind `isq::length / isq::time` with the numbers
and units provided. Such quantities can be converted or assigned to any other quantity
with a matching kind.
- Line `25` calls our function template with quantities of kind `isq::length` and
- Line `29` calls our function template with quantities of kind `isq::length` and
`isq::time` and number and units provided.
- Line `26` explicitly provides quantity types of the quantities passed to a function template.
- Line `30` explicitly provides quantity types of the quantities passed to a function template.
This time, those will not be quantity kinds anymore and will have
[more restrictive conversion rules](../framework_basics/simple_and_typed_quantities.md#quantity_cast-to-force-unsafe-conversions).
- Line `27` changes the unit of a quantity `v3` to `m / s` in a
- Line `31` changes the unit of a quantity `v3` to `m / s` in a
[value-preserving way](../framework_basics/value_conversions.md#value-preserving-conversions)
(floating-point representations are considered to be value-preserving).
- Line `28` does a similar operation, but this time, it would also succeed for
- Line `32` does a similar operation, but this time, it would also succeed for
[value-truncating cases](../framework_basics/value_conversions.md#value-truncating-conversions)
(if that was the case).
- Line `29` does a [value-truncating conversion](../framework_basics/value_conversions.md#value-truncating-conversions)
- Line `33` does a [value-truncating conversion](../framework_basics/value_conversions.md#value-truncating-conversions)
of changing the underlying representation type from `double` to `int`.
```cpp title="hello_units.cpp" linenums="30"
--8<-- "example/hello_units.cpp:62"
```cpp title="hello_units.cpp" linenums="34"
--8<-- "example/hello_units.cpp:66"
```
The above presents [various ways to print a quantity](../framework_basics/text_output.md).
@@ -86,3 +86,6 @@ Both stream insertion operations and `std::format` facilities are supported.
`MP_UNITS_STD_FMT` is used for compatibility reasons. If a specific compiler
does not support `std::format` or a user prefers to use the `{fmt}` library, this macro
will resolve to `fmt` namespace. Otherwise, the `std` namespace will be used.
More about it can be found in the [Wide Compatibility](../use_cases/wide_compatibility.md#mp_units_std_fmt)
chapter.

View File

@@ -0,0 +1,72 @@
---
tags:
- Affine Space
- Embedded
- Text Formatting
---
# `hw_voltage`
!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/jjod7hvsd)"
As it was stated in [The Affine Space](../framework_basics/the_affine_space.md) chapter,
every measurement can (and probably should) be modelled as a `quantity_point`. This is
a perfect example of such a use case.
This example implements a simplified scenario of measuring voltage read from hardware through
a mapped 16-bits register. The actual voltage range of [-10 V, 10 V] is mapped to [-32767, 32767]
on hardware. Translation of the value requires not only scaling of the value but also applying
of an offset.
First we include all the dependencies:
```cpp title="hw_voltage.cpp" linenums="1"
--8<-- "example/hw_voltage.cpp:28:44"
```
Next, we specify the real measurement voltage range to be in the range of [-10, 10]:
```cpp title="hw_voltage.cpp" linenums="18"
--8<-- "example/hw_voltage.cpp:46:49"
```
and provide a storage type and special values for the hardware representation:
```cpp title="hw_voltage.cpp" linenums="22"
--8<-- "example/hw_voltage.cpp:51:57"
```
Finally, we define a quantity point origin, an offset unit that scales the value and uses this
origin to offset the zero of the sale, and a dedicated quantity point alias using those:
```cpp title="hw_voltage.cpp" linenums="29"
--8<-- "example/hw_voltage.cpp:61:67"
```
Now, when everything is ready, we can simulate mapping of our hardware register, and provide
a helper function that will read the value and construct a quantity point from the obtained copy:
```cpp title="hw_voltage.cpp" linenums="36"
--8<-- "example/hw_voltage.cpp:70:78"
```
We also provide a simple print helper for our quantity points:
```cpp title="hw_voltage.cpp" linenums="45"
--8<-- "example/hw_voltage.cpp:80:84"
```
In the main function we simulate setting of 3 values by our hardware. Each of them is read
and printed in the voltage unit used on the hardware as well as in the standard SI unit:
```cpp title="hw_voltage.cpp" linenums="50"
--8<-- "example/hw_voltage.cpp:86:"
```
The above program results with the following text output:
```text
0 hwV (-10 V)
32767 hwV ( 0 V)
65534 hwV ( 10 V)
```

View File

@@ -6,7 +6,7 @@ tags:
# `si_constants`
!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/MevcK8vYT)"
!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/eGqbW5d8K)"
The next example presents all the seven defining constants of the SI system. We can observe
how [Faster-than-lightspeed Constants](../framework_basics/faster_than_lightspeed_constants.md)
@@ -22,15 +22,20 @@ the simplicity of this example, we
to be able to express vector quantities with simple scalar types.
```cpp title="si_constants.cpp" linenums="14"
--8<-- "example/si_constants.cpp:42:"
--8<-- "example/si_constants.cpp:42:44"
```
The main part of the example prints all of the SI-defining constants. While analyzing the output of
this program (provided below), we can easily notice that a direct printing of the quantity provides
just a value `1` with a proper constant symbol. This is the main power of the
[Faster-than-lightspeed Constants](../framework_basics/faster_than_lightspeed_constants.md) feature.
Only after we explicitly convert the unit of a quantity to proper SI units we get an actual numeric
value of the constant.
The main part of the example prints all of the SI-defining constants:
```cpp title="si_constants.cpp" linenums="17"
--8<-- "example/si_constants.cpp:45:"
```
While analyzing the output of this program (provided below), we can easily notice that a direct
printing of the quantity provides just a value `1` with a proper constant symbol. This is the main
power of the [Faster-than-lightspeed Constants](../framework_basics/faster_than_lightspeed_constants.md)
feature. Only after we explicitly convert the unit of a quantity to proper SI units we get an
actual numeric value of the constant.
```text
The seven defining constants of the SI and the seven corresponding units they define:

View File

@@ -190,3 +190,70 @@ inline constexpr struct mag_pi final : magnitude<std::numbers::pi_v<long double>
```cpp
inline constexpr struct degree final : named_unit<{u8"°", "deg"}, mag_pi / mag<180> * si::radian> {} degree;
```
## Unit symbols
Units are available via their full names or through their short symbols.
To use a long version, it is enough to type:
```cpp
quantity q1 = 42 * si::metre / si::second;
quantity q2 = 42 * si::kilo<si::metre> / si::hour;
```
To simplify how we spell it a short, user-friendly symbols are provided in a dedicated
subnamespace in systems definitions:
```cpp
namespace si::unit_symbols {
constexpr auto m = si::metre;
constexpr auto km = si::kilo<si::metre>;
constexpr auto s = si::second;
constexpr auto h = si::hour;
}
```
Unit symbols introduce a lot of short identifiers into the current namespace. This is why they
are opt-in. A user has to explicitly "import" them from a dedicated `unit_symbols` namespace:
=== "using-declaration"
```cpp
using namespace si::unit_symbols;
quantity q1 = 42 * m / s;
quantity q2 = 42 * km / h;
```
=== "using-directive"
```cpp
using si::unit_symbols::m;
using si::unit_symbols::km;
using si::unit_symbols::s;
using si::unit_symbols::h;
quantity q1 = 42 * m / s;
quantity q2 = 42 * km / h;
```
We also provide alternative object identifiers using Unicode characters in their names for most
unit symbols. The code using Unicode looks nicer, but it is harder to type on the keyboard.
This is why we provide both versions of identifiers for such units.
=== "ASCII only"
```cpp
quantity resistance = 60 * kohm;
quantity capacitance = 100 * uF;
```
=== "With Unicode glyphs"
```cpp
quantity resistance = 60 * kΩ;
quantity capacitance = 100 * µF;
```

View File

@@ -182,11 +182,11 @@ which may well be outside the range of one or both quantity types.
The table below provides all the value conversions functions that may be run on `x` being the
instance of either `quantity` or `quantity_point`:
| Forcing | Representation | Unit | Member function | Conversion 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)` |
| 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)` |

View File

@@ -57,6 +57,7 @@ add_example(currency)
add_example(foot_pound_second)
add_example(glide_computer glide_computer_lib)
add_example(hello_units)
add_example(hw_voltage)
add_example(measurement)
add_example(si_constants)
add_example(spectroscopy_units)

99
example/hw_voltage.cpp Normal file
View File

@@ -0,0 +1,99 @@
// The MIT License (MIT)
//
// Copyright (c) 2018 Mateusz Pusz
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!! Before you commit any changes to this file please make sure to check if it !!!
// !!! renders correctly in the documentation "Examples" section. !!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#include <mp-units/compat_macros.h>
#include <mp-units/ext/format.h>
#ifdef MP_UNITS_IMPORT_STD
import std;
#else
#include <iostream>
#include <optional>
#endif
#ifdef MP_UNITS_MODULES
import mp_units;
#else
#include <mp-units/format.h>
#include <mp-units/systems/isq.h>
#include <mp-units/systems/si.h>
#endif
using namespace mp_units;
// real voltage range
inline constexpr int min_voltage = -10;
inline constexpr int max_voltage = 10;
inline constexpr int voltage_range = max_voltage - min_voltage;
// hardware encoding of voltage
using voltage_hw_t = std::uint16_t;
inline constexpr voltage_hw_t voltage_hw_error = std::numeric_limits<voltage_hw_t>::max();
inline constexpr voltage_hw_t voltage_hw_min = 0;
inline constexpr voltage_hw_t voltage_hw_max = voltage_hw_error - 1;
inline constexpr voltage_hw_t voltage_hw_range = voltage_hw_max - voltage_hw_min;
inline constexpr voltage_hw_t voltage_hw_zero = voltage_hw_range / 2;
// clang-format off
inline constexpr struct hw_voltage_origin final :
relative_point_origin<absolute<si::volt>(min_voltage)> {} hw_voltage_origin;
inline constexpr struct hw_voltage_unit final :
named_unit<"hwV", mag_ratio<voltage_range, voltage_hw_range> * si::volt, hw_voltage_origin> {} hw_voltage_unit;
using hw_voltage_quantity_point = quantity_point<hw_voltage_unit, hw_voltage_origin, voltage_hw_t>;
// clang-format on
// mapped HW register
volatile voltage_hw_t hw_voltage_value;
std::optional<hw_voltage_quantity_point> read_hw_voltage()
{
voltage_hw_t local_copy = hw_voltage_value;
if (local_copy == voltage_hw_error) return std::nullopt;
return absolute<hw_voltage_unit>(local_copy);
}
void print(QuantityPoint auto qp)
{
std::cout << MP_UNITS_STD_FMT::format("{:10} ({:5})", qp.quantity_from_zero(),
value_cast<double, si::volt>(qp).quantity_from_zero());
}
int main()
{
// simulate reading of 3 values from the hardware
hw_voltage_value = voltage_hw_min;
quantity_point qp1 = read_hw_voltage().value();
hw_voltage_value = voltage_hw_zero;
quantity_point qp2 = read_hw_voltage().value();
hw_voltage_value = voltage_hw_max;
quantity_point qp3 = read_hw_voltage().value();
print(qp1);
print(qp2);
print(qp3);
}

View File

@@ -175,6 +175,7 @@ nav:
- hello_units: users_guide/examples/hello_units.md
- avg_speed: users_guide/examples/avg_speed.md
- si_constants: users_guide/examples/si_constants.md
- hw_voltage: users_guide/examples/hw_voltage.md
- Appendix:
- Glossary: appendix/glossary.md
- References: appendix/references.md

View File

@@ -380,8 +380,9 @@ template<typename T>
// Always use `long double` for intermediate computations. We don't ever expect people to be
// calling this at runtime, so we want maximum accuracy.
long double xld = static_cast<long double>(x);
long double lo = 1.0;
long double hi = static_cast<long double>(x);
long double hi = xld;
// Do a binary search to find the closest value such that `checked_int_pow` recovers the input.
//
@@ -398,7 +399,7 @@ template<typename T>
}
// Early return if we get lucky with an exact answer.
if (result.value() == x) {
if (result.value() == xld) {
return static_cast<T>(mid);
}
@@ -408,7 +409,7 @@ template<typename T>
}
// Preserve the invariant that `checked_int_pow(lo, n) < x < checked_int_pow(hi, n)`.
if (result.value() < x) {
if (result.value() < xld) {
lo = mid;
} else {
hi = mid;
@@ -416,8 +417,8 @@ template<typename T>
}
// Pick whichever one gets closer to the target.
const auto lo_diff = x - checked_int_pow(lo, n).value();
const auto hi_diff = checked_int_pow(hi, n).value() - x;
const auto lo_diff = xld - checked_int_pow(lo, n).value();
const auto hi_diff = checked_int_pow(hi, n).value() - xld;
return static_cast<T>(lo_diff < hi_diff ? lo : hi);
}

View File

@@ -1,4 +1,3 @@
// The MIT License (MIT)
//
// Copyright (c) 2018 Mateusz Pusz

View File

@@ -88,6 +88,14 @@ template<Unit auto ToU, Representation ToRep, typename FwdQ, Quantity Q = std::r
return detail::sudo_cast<quantity<detail::make_reference(Q::quantity_spec, ToU), ToRep>>(std::forward<FwdQ>(q));
}
template<Representation ToRep, Unit auto ToU, typename FwdQ, Quantity Q = std::remove_cvref_t<FwdQ>>
requires(convertible(Q::reference, ToU)) && RepresentationOf<ToRep, Q::quantity_spec.character> &&
std::constructible_from<ToRep, typename Q::rep>
[[nodiscard]] constexpr Quantity auto value_cast(FwdQ&& q)
{
return value_cast<ToU, ToRep>(std::forward<FwdQ>(q));
}
/**
* @brief Explicit cast of a quantity's representation
@@ -168,6 +176,14 @@ template<Unit auto ToU, Representation ToRep, typename FwdQP, QuantityPoint QP =
QP::point_origin};
}
template<Representation ToRep, Unit auto ToU, typename FwdQP, QuantityPoint QP = std::remove_cvref_t<FwdQP>>
requires(convertible(QP::reference, ToU)) && RepresentationOf<ToRep, QP::quantity_spec.character> &&
std::constructible_from<ToRep, typename QP::rep>
[[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp)
{
return value_cast<ToU, ToRep>(std::forward<FwdQP>(qp));
}
/**
* @brief Explicit cast of a quantity point's representation
*

View File

@@ -35,7 +35,7 @@ namespace mp_units {
template<Quantity T>
struct AlmostEqualsMatcher : Catch::Matchers::MatcherGenericBase {
explicit AlmostEqualsMatcher(const T& target) : target_{target} {}
explicit AlmostEqualsMatcher(const T& target, int n_eps) : target_{target}, n_eps_{n_eps} {}
template<std::convertible_to<T> U>
requires std::same_as<typename T::rep, typename U::rep>
@@ -48,7 +48,7 @@ struct AlmostEqualsMatcher : Catch::Matchers::MatcherGenericBase {
const auto y = common(other).numerical_value_in(common::unit);
if constexpr (treat_as_floating_point<rep>) {
const auto maxXYOne = std::max({rep{1}, abs(x), abs(y)});
return abs(x - y) <= std::numeric_limits<rep>::epsilon() * maxXYOne;
return abs(x - y) <= (n_eps_ * std::numeric_limits<rep>::epsilon()) * maxXYOne;
} else {
if (x >= 0) {
return x - 1 <= y && y - 1 <= x;
@@ -71,12 +71,13 @@ struct AlmostEqualsMatcher : Catch::Matchers::MatcherGenericBase {
private:
const T& target_;
int n_eps_;
};
template<Quantity T>
AlmostEqualsMatcher<T> AlmostEquals(const T& target)
AlmostEqualsMatcher<T> AlmostEquals(const T& target, int n_eps = 1)
{
return AlmostEqualsMatcher<T>{target};
return AlmostEqualsMatcher<T>{target, n_eps};
}

View File

@@ -448,7 +448,7 @@ TEST_CASE("Angle trigonometric functions", "[trig][angle]")
REQUIRE_THAT(sin(0 * angle[grad]), AlmostEquals(0. * one));
REQUIRE_THAT(sin(100 * angle[grad]), AlmostEquals(1. * one));
REQUIRE_THAT(sin(200 * angle[grad]), AlmostEquals(0. * one));
REQUIRE_THAT(sin(200 * angle[grad]), AlmostEquals(0. * one, 2));
REQUIRE_THAT(sin(300 * angle[grad]), AlmostEquals(-1. * one));
}
@@ -475,7 +475,7 @@ TEST_CASE("Angle trigonometric functions", "[trig][angle]")
REQUIRE_THAT(tan(0 * angle[grad]), AlmostEquals(0. * one));
REQUIRE_THAT(tan(50 * angle[grad]), AlmostEquals(1. * one));
REQUIRE_THAT(tan(150 * angle[grad]), AlmostEquals(-1. * one));
REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one));
REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one, 2));
}
}

View File

@@ -1725,6 +1725,7 @@ constexpr quantity_point lvalue_qp{2 * km};
static_assert(value_cast<m>(lvalue_qp).quantity_from_zero().numerical_value_in(m) == 2000);
static_assert(value_cast<float>(lvalue_qp).quantity_from_zero().numerical_value_in(km) == 2.f);
static_assert(value_cast<m, float>(lvalue_qp).quantity_from_zero().numerical_value_in(m) == 2000.f);
static_assert(value_cast<float, m>(lvalue_qp).quantity_from_zero().numerical_value_in(m) == 2000.f);
} // namespace lvalue_tests
static_assert(value_cast<quantity<km, int>>(quantity_point{2000 * m}).quantity_from_zero().numerical_value_in(km) == 2);

View File

@@ -37,9 +37,15 @@ import std;
#include <utility>
#if MP_UNITS_HOSTED
#include <chrono>
#include <complex>
#endif
#endif
#if MP_UNITS_HOSTED
template<typename T>
constexpr bool mp_units::is_scalar<std::complex<T>> = true;
#endif
template<>
constexpr bool mp_units::is_vector<int> = true;
@@ -267,6 +273,12 @@ static_assert(quantity<isq::length[m], int>(2000 * m).force_in(km).numerical_val
static_assert((15. * m).in(nm).numerical_value_in(m) == 15.);
static_assert((15'000. * nm).in(m).numerical_value_in(nm) == 15'000.);
#if MP_UNITS_HOSTED
using namespace std::complex_literals;
static_assert(((2. + 1i) * V).in(mV).numerical_value_in(mV) == 2000. + 1000i);
static_assert(((2. + 1i) * V).in(mV).numerical_value_in(V) == 2. + 1i);
#endif
template<template<auto, typename> typename Q>
concept invalid_unit_conversion = requires {
requires !requires { Q<isq::length[m], int>(2000 * m).in(km); }; // truncating conversion
@@ -1014,6 +1026,7 @@ static_assert(value_cast<km / h>(2000.0 * m / (3600.0 * s)).numerical_value_in(k
static_assert(value_cast<int>(1.23 * m).numerical_value_in(m) == 1);
static_assert(value_cast<km, int>(1.23 * m).numerical_value_in(km) == 0);
static_assert(value_cast<int, km>(1.23 * m).numerical_value_in(km) == 0);
static_assert((2 * km).force_in(m).numerical_value_in(m) == 2000);
static_assert((2000 * m).force_in(km).numerical_value_in(km) == 2);