Merge branch 'master' into feature/faster-CI

This commit is contained in:
Yves Delley
2024-11-12 18:20:20 +01:00
committed by GitHub
21 changed files with 2426 additions and 1754 deletions

View File

@ -401,8 +401,8 @@ Some quantities are more complicated than others. For example, _power_ has:
- var (e.g., _reactive power_), - var (e.g., _reactive power_),
- complex quantities expressed in VA (volt-ampere) (e.g., _complex power_). - complex quantities expressed in VA (volt-ampere) (e.g., _complex power_).
How should we model this? Maybe those should be two independent trees of quantities, each having How should we model this? Maybe those should be two or three independent trees of quantities, each
a different unit? having its own unit?
```mermaid ```mermaid
flowchart TD flowchart TD
@ -411,14 +411,50 @@ flowchart TD
power --- electromagnetism_power["<b>electromagnetism_power</b> | <b>instantaneous_power</b><br><i>(instantaneous_voltage * instantaneous_electric_current)</i>"] power --- electromagnetism_power["<b>electromagnetism_power</b> | <b>instantaneous_power</b><br><i>(instantaneous_voltage * instantaneous_electric_current)</i>"]
power --- active_power["<b>active_power</b><br><i>(1 / period * instantaneous_power * time)<br>(re(complex_power))</i>"] power --- active_power["<b>active_power</b><br><i>(1 / period * instantaneous_power * time)<br>(re(complex_power))</i>"]
apparent_power["<b>apparent_power</b><br><i>(voltage * electric_current)<br>(mod(complex_power))</i><br>[VA]"] nonactive_power["<b>nonactive_power</b><br><i>(mass * length<sup>2</sup> / time<sup>3</sup>)</i><br>[VA]"]
nonactive_power --- reactive_power["<b>reactive_power</b><br><i>(im(complex_power))</i><br>[var]"]
complex_power["<b>complex_power</b><br>{complex}<br><i>(voltage_phasor * electric_current_phasor)<br>(active_power + j * reactive_power)</i><br>[VA]"]
complex_power --- apparent_power["<b>apparent_power</b><br><i>(voltage * electric_current)<br>(mod(complex_power))</i>"]
```
This will mean that we will not be able to add or compare _active power_, _reactive power_, and
_apparent power_, which probably makes a lot of sense. However, it also means that the following
will fail to compile:
```cpp
quantity apparent = isq::apparent_power(100 * VA);
quantity active = isq::active_power(60 * W);
quantity<isq::nonactive_power[VA]> q = sqrt(pow<2>(apparent) - pow<2>(active)); // Compile-time error
```
Also the following will not work:
```cpp
quantity active = isq::active_power(60 * W);
quantity reactive = isq::reactive_power(40 * var);
quantity<isq::apparent_power[VA]> q = sqrt(pow<2>(active) + pow<2>(reactive)); // Compile-time error
```
If we want the above to work maybe we need to implement the tree as follows?
```mermaid
flowchart TD
power["<b>power</b><br><i>(mass * length<sup>2</sup> / time<sup>3</sup>)</i><br>[W]"]
power --- mechanical_power["<b>mechanical_power</b><br><i>(scalar_product(force, velocity))</i>"]
power --- electromagnetism_power["<b>electromagnetism_power</b> | <b>instantaneous_power</b><br><i>(instantaneous_voltage * instantaneous_electric_current)</i>"]
power --- apparent_power["<b>apparent_power</b><br><i>(voltage * electric_current)<br>(mod(complex_power))</i><br>[VA]"]
apparent_power --- active_power["<b>active_power</b><br><i>(1 / period * instantaneous_power * time)<br>(re(complex_power))</i>"]
apparent_power --- nonactive_power["<b>nonactive_power</b><br><i>(sqrt(apparent_power<sup>2</sup> - active_power<sup>2</sup>))</i><br>"] apparent_power --- nonactive_power["<b>nonactive_power</b><br><i>(sqrt(apparent_power<sup>2</sup> - active_power<sup>2</sup>))</i><br>"]
nonactive_power --- reactive_power["<b>reactive_power</b><br><i>(im(complex_power))</i><br>[var]"] nonactive_power --- reactive_power["<b>reactive_power</b><br><i>(im(complex_power))</i><br>[var]"]
apparent_power --- complex_power["<b>complex_power</b><br>{complex}<br><i>(voltage_phasor * electric_current_phasor)<br>(active_power + j * reactive_power)</i>"] apparent_power --- complex_power["<b>complex_power</b><br>{complex}<br><i>(voltage_phasor * electric_current_phasor)<br>(active_power + j * reactive_power)</i>"]
``` ```
This will mean that we will not be able to add or compare _active power_ with _apparent power_, However, the above allows direct addition and comparison of _active power_ and _nonactive power_,
which probably makes a lot of sense. Again, ISQ does not provide a direct answer here. and also will not complain if someone will try to use watt (W) as a unit of _apparent power_ or
_reactive power_.
Again, ISQ does not provide a direct answer here.
## More base quantities? ## More base quantities?

View File

@ -16,19 +16,11 @@ work in practice.
--8<-- "example/si_constants.cpp:28:40" --8<-- "example/si_constants.cpp:28:40"
``` ```
As always, we start with the inclusion of all the needed header files. After that, for As always, we start with the inclusion of all the needed header files.
the simplicity of this example, we
[hack the character of quantities](../framework_basics/character_of_a_quantity.md#hacking-the-character)
to be able to express vector quantities with simple scalar types.
```cpp title="si_constants.cpp" linenums="14"
--8<-- "example/si_constants.cpp:42:44"
```
The main part of the example prints all of the SI-defining constants: The main part of the example prints all of the SI-defining constants:
```cpp title="si_constants.cpp" linenums="17" ```cpp title="si_constants.cpp" linenums="14"
--8<-- "example/si_constants.cpp:45:" --8<-- "example/si_constants.cpp:42:"
``` ```
While analyzing the output of this program (provided below), we can easily notice that a direct While analyzing the output of this program (provided below), we can easily notice that a direct

View File

@ -129,8 +129,8 @@ std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>&
template<typename T, typename Validator, typename Char> template<typename T, typename Validator, typename Char>
struct MP_UNITS_STD_FMT::formatter<validated_type<T, Validator>, Char> : formatter<T, Char> { struct MP_UNITS_STD_FMT::formatter<validated_type<T, Validator>, Char> : formatter<T, Char> {
template<typename FormatContext> template<typename FormatContext>
auto format(const validated_type<T, Validator>& v, FormatContext& ctx) const -> decltype(ctx.out()) auto format(const validated_type<T, Validator>& val, FormatContext& ctx) const -> decltype(ctx.out())
{ {
return formatter<T, Char>::format(v.value(), ctx); return formatter<T, Char>::format(val.value(), ctx);
} }
}; };

View File

@ -39,10 +39,6 @@ import mp_units;
#include <mp-units/systems/si.h> #include <mp-units/systems/si.h>
#endif #endif
template<class T>
requires mp_units::is_scalar<T>
constexpr bool mp_units::is_vector<T> = true;
int main() int main()
{ {
using namespace mp_units; using namespace mp_units;

View File

@ -91,6 +91,7 @@ if(NOT ${projectPrefix}API_FREESTANDING)
include/mp-units/bits/fmt.h include/mp-units/bits/fmt.h
include/mp-units/bits/requires_hosted.h include/mp-units/bits/requires_hosted.h
include/mp-units/ext/format.h include/mp-units/ext/format.h
include/mp-units/cartesian_vector.h
include/mp-units/complex.h include/mp-units/complex.h
include/mp-units/format.h include/mp-units/format.h
include/mp-units/math.h include/mp-units/math.h

View File

@ -0,0 +1,230 @@
// 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.
#pragma once
#include <mp-units/bits/requires_hosted.h>
//
#include <mp-units/bits/module_macros.h>
#include <mp-units/framework/customization_points.h>
#if MP_UNITS_HOSTED
#include <mp-units/bits/fmt.h>
#endif
#ifndef MP_UNITS_IN_MODULE_INTERFACE
#ifdef MP_UNITS_IMPORT_STD
import std;
#else
#include <cmath>
#include <cstddef>
#include <type_traits>
#if MP_UNITS_HOSTED
#include <ostream>
#endif
#endif
#endif
namespace mp_units {
MP_UNITS_EXPORT template<typename T = double>
class cartesian_vector {
public:
// public members required to satisfy structural type requirements :-(
T _coordinates_[3];
using value_type = T;
cartesian_vector() = default;
cartesian_vector(const cartesian_vector&) = default;
cartesian_vector(cartesian_vector&&) = default;
cartesian_vector& operator=(const cartesian_vector&) = default;
cartesian_vector& operator=(cartesian_vector&&) = default;
template<std::convertible_to<T> Arg1, std::convertible_to<T>... Args>
constexpr cartesian_vector(Arg1&& arg1, Args&&... args) :
_coordinates_(std::forward<Arg1>(arg1), std::forward<Args>(args)...)
{
}
template<std::convertible_to<T> U>
constexpr cartesian_vector(const cartesian_vector<U>& other) : _coordinates_{other[0], other[1], other[2]}
{
}
template<std::convertible_to<T> U>
constexpr cartesian_vector(cartesian_vector<U>&& other) :
_coordinates_{std::move(other[0]), std::move(other[1]), std::move(other[2])}
{
}
template<std::convertible_to<T> U>
constexpr cartesian_vector& operator=(const cartesian_vector<U>& other)
{
_coordinates_[0] = other[0];
_coordinates_[1] = other[1];
_coordinates_[2] = other[2];
return *this;
}
template<std::convertible_to<T> U>
constexpr cartesian_vector& operator=(cartesian_vector<U>&& other)
{
_coordinates_[0] = std::move(other[0]);
_coordinates_[1] = std::move(other[1]);
_coordinates_[2] = std::move(other[2]);
return *this;
}
[[nodiscard]] constexpr T magnitude() const
requires treat_as_floating_point<T>
{
return std::hypot(_coordinates_[0], _coordinates_[1], _coordinates_[2]);
}
[[nodiscard]] constexpr cartesian_vector unit() const
requires treat_as_floating_point<T>
{
return *this / magnitude();
}
[[nodiscard]] constexpr T& operator[](std::size_t i) { return _coordinates_[i]; }
[[nodiscard]] constexpr const T& operator[](std::size_t i) const { return _coordinates_[i]; }
[[nodiscard]] constexpr cartesian_vector operator+() const { return *this; }
[[nodiscard]] constexpr cartesian_vector operator-() const
{
return {-_coordinates_[0], -_coordinates_[1], -_coordinates_[2]};
}
template<std::same_as<T> U, typename V>
requires requires(U u, V v) { u + v; }
[[nodiscard]] friend constexpr auto operator+(const cartesian_vector<U>& lhs, const cartesian_vector<V>& rhs)
{
return ::mp_units::cartesian_vector{lhs._coordinates_[0] + rhs._coordinates_[0],
lhs._coordinates_[1] + rhs._coordinates_[1],
lhs._coordinates_[2] + rhs._coordinates_[2]};
}
template<std::same_as<T> U, typename V>
requires requires(U u, V v) { u - v; }
[[nodiscard]] friend constexpr auto operator-(const cartesian_vector<U>& lhs, const cartesian_vector<V>& rhs)
{
return ::mp_units::cartesian_vector{lhs._coordinates_[0] - rhs._coordinates_[0],
lhs._coordinates_[1] - rhs._coordinates_[1],
lhs._coordinates_[2] - rhs._coordinates_[2]};
}
template<std::same_as<T> U>
requires requires(U u, T t) { u* t; }
[[nodiscard]] friend constexpr auto operator*(const cartesian_vector<U>& lhs, const T& rhs)
{
return ::mp_units::cartesian_vector{lhs._coordinates_[0] * rhs, lhs._coordinates_[1] * rhs,
lhs._coordinates_[2] * rhs};
}
template<std::same_as<T> U>
requires requires(T t, U u) { t* u; }
[[nodiscard]] friend constexpr auto operator*(const T& lhs, const cartesian_vector<U>& rhs)
{
return rhs * lhs;
}
template<std::same_as<T> U>
requires requires(U u, T t) { u / t; }
[[nodiscard]] friend constexpr auto operator/(const cartesian_vector<U>& lhs, const T& rhs)
{
return ::mp_units::cartesian_vector{lhs._coordinates_[0] / rhs, lhs._coordinates_[1] / rhs,
lhs._coordinates_[2] / rhs};
}
template<std::same_as<T> U, std::equality_comparable_with<U> V>
[[nodiscard]] friend constexpr bool operator==(const cartesian_vector<U>& lhs, const cartesian_vector<V>& rhs)
{
return lhs._coordinates_[0] == rhs._coordinates_[0] && lhs._coordinates_[1] == rhs._coordinates_[1] &&
lhs._coordinates_[2] == rhs._coordinates_[2];
}
[[nodiscard]] friend constexpr T norm(const cartesian_vector& vec)
requires treat_as_floating_point<T>
{
return vec.magnitude();
}
[[nodiscard]] friend constexpr cartesian_vector unit_vector(const cartesian_vector& vec)
requires treat_as_floating_point<T>
{
return vec.unit();
}
template<std::same_as<T> U, typename V>
requires requires(U u, V v, decltype(u * v) t) {
u* v;
t + t;
}
[[nodiscard]] friend constexpr auto scalar_product(const cartesian_vector<U>& lhs, const cartesian_vector<V>& rhs)
{
return lhs._coordinates_[0] * rhs._coordinates_[0] + lhs._coordinates_[1] * rhs._coordinates_[1] +
lhs._coordinates_[2] * rhs._coordinates_[2];
}
template<std::same_as<T> U, typename V>
requires requires(U u, V v, decltype(u * v) t) {
u* v;
t - t;
}
[[nodiscard]] friend constexpr auto vector_product(const cartesian_vector<U>& lhs, const cartesian_vector<V>& rhs)
{
return ::mp_units::cartesian_vector{
lhs._coordinates_[1] * rhs._coordinates_[2] - lhs._coordinates_[2] * rhs._coordinates_[1],
lhs._coordinates_[2] * rhs._coordinates_[0] - lhs._coordinates_[0] * rhs._coordinates_[2],
lhs._coordinates_[0] * rhs._coordinates_[1] - lhs._coordinates_[1] * rhs._coordinates_[0]};
}
#if MP_UNITS_HOSTED
friend constexpr std::ostream& operator<<(std::ostream& os, const cartesian_vector& vec)
{
return os << '[' << vec[0] << ", " << vec[1] << ", " << vec[2] << ']';
}
#endif
};
template<typename Arg, typename... Args>
requires(sizeof...(Args) <= 2) && requires { typename std::common_type_t<Arg, Args...>; }
cartesian_vector(Arg, Args...) -> cartesian_vector<std::common_type_t<Arg, Args...>>;
template<class T>
constexpr bool is_vector<cartesian_vector<T>> = true;
} // namespace mp_units
#if MP_UNITS_HOSTED
// TODO use parse and use formatter for the underlying type
template<typename T, typename Char>
struct MP_UNITS_STD_FMT::formatter<mp_units::cartesian_vector<T>, Char> :
formatter<std::basic_string_view<Char>, Char> {
template<typename FormatContext>
auto format(const mp_units::cartesian_vector<T>& vec, FormatContext& ctx) const
{
return format_to(ctx.out(), "[{}, {}, {}]", vec[0], vec[1], vec[2]);
}
};
#endif

View File

@ -28,6 +28,7 @@
#include <mp-units/framework.h> #include <mp-units/framework.h>
#if MP_UNITS_HOSTED #if MP_UNITS_HOSTED
#include <mp-units/cartesian_vector.h>
#include <mp-units/complex.h> #include <mp-units/complex.h>
#include <mp-units/format.h> #include <mp-units/format.h>
#include <mp-units/math.h> #include <mp-units/math.h>

View File

@ -90,16 +90,16 @@ public:
constexpr T* data() noexcept { return data_; } constexpr T* data() noexcept { return data_; }
constexpr const T* data() const noexcept { return data_; } constexpr const T* data() const noexcept { return data_; }
constexpr reference push_back(const T& v) constexpr reference push_back(const T& val)
requires std::constructible_from<T, const T&> requires std::constructible_from<T, const T&>
{ {
return emplace_back(v); return emplace_back(val);
} }
constexpr reference push_back(T&& v) constexpr reference push_back(T&& val)
requires std::constructible_from<T, T&&> requires std::constructible_from<T, T&&>
{ {
return emplace_back(std::forward<T&&>(v)); return emplace_back(std::forward<T&&>(val));
} }
template<typename... Args> template<typename... Args>

View File

@ -174,21 +174,21 @@ public:
template<typename FwdValue, Reference R2> template<typename FwdValue, Reference R2>
requires detail::SameValueAs<R2{}, R, std::remove_cvref_t<FwdValue>, Rep> requires detail::SameValueAs<R2{}, R, std::remove_cvref_t<FwdValue>, Rep>
constexpr quantity(FwdValue&& v, R2) : numerical_value_is_an_implementation_detail_(std::forward<FwdValue>(v)) constexpr quantity(FwdValue&& val, R2) : numerical_value_is_an_implementation_detail_(std::forward<FwdValue>(val))
{ {
} }
template<typename FwdValue, Reference R2, typename Value = std::remove_cvref_t<FwdValue>> template<typename FwdValue, Reference R2, typename Value = std::remove_cvref_t<FwdValue>>
requires(!detail::SameValueAs<R2{}, R, Value, Rep>) && requires(!detail::SameValueAs<R2{}, R, Value, Rep>) &&
detail::QuantityConvertibleTo<quantity<R2{}, Value>, quantity> detail::QuantityConvertibleTo<quantity<R2{}, Value>, quantity>
constexpr quantity(FwdValue&& v, R2) : quantity(quantity<R2{}, Value>{std::forward<FwdValue>(v), R2{}}) constexpr quantity(FwdValue&& val, R2) : quantity(quantity<R2{}, Value>{std::forward<FwdValue>(val), R2{}})
{ {
} }
template<detail::ValuePreservingTo<Rep> FwdValue> template<detail::ValuePreservingTo<Rep> FwdValue>
requires(unit == ::mp_units::one) requires(unit == ::mp_units::one)
constexpr explicit(false) quantity(FwdValue&& v) : constexpr explicit(false) quantity(FwdValue&& val) :
numerical_value_is_an_implementation_detail_(std::forward<FwdValue>(v)) numerical_value_is_an_implementation_detail_(std::forward<FwdValue>(val))
{ {
} }
@ -214,9 +214,9 @@ public:
template<detail::ValuePreservingTo<Rep> FwdValue> template<detail::ValuePreservingTo<Rep> FwdValue>
requires(unit == ::mp_units::one) requires(unit == ::mp_units::one)
constexpr quantity& operator=(FwdValue&& v) constexpr quantity& operator=(FwdValue&& val)
{ {
numerical_value_is_an_implementation_detail_ = std::forward<FwdValue>(v); numerical_value_is_an_implementation_detail_ = std::forward<FwdValue>(val);
return *this; return *this;
} }
@ -422,11 +422,11 @@ public:
requires(!Quantity<Value>) && requires(rep a, Value b) { requires(!Quantity<Value>) && requires(rep a, Value b) {
{ a *= b } -> std::same_as<rep&>; { a *= b } -> std::same_as<rep&>;
} }
friend constexpr decltype(auto) operator*=(Q&& lhs, const Value& v) friend constexpr decltype(auto) operator*=(Q&& lhs, const Value& val)
{ {
// TODO use *= when compiler bug is resolved: // TODO use *= when compiler bug is resolved:
// https://developercommunity.visualstudio.com/t/Discrepancy-in-Behavior-of-operator-an/10732445 // https://developercommunity.visualstudio.com/t/Discrepancy-in-Behavior-of-operator-an/10732445
lhs.numerical_value_is_an_implementation_detail_ = lhs.numerical_value_is_an_implementation_detail_ * v; lhs.numerical_value_is_an_implementation_detail_ = lhs.numerical_value_is_an_implementation_detail_ * val;
return std::forward<Q>(lhs); return std::forward<Q>(lhs);
} }
@ -444,12 +444,12 @@ public:
requires(!Quantity<Value>) && requires(rep a, Value b) { requires(!Quantity<Value>) && requires(rep a, Value b) {
{ a /= b } -> std::same_as<rep&>; { a /= b } -> std::same_as<rep&>;
} }
friend constexpr decltype(auto) operator/=(Q&& lhs, const Value& v) friend constexpr decltype(auto) operator/=(Q&& lhs, const Value& val)
{ {
MP_UNITS_EXPECTS_DEBUG(v != quantity_values<Value>::zero()); MP_UNITS_EXPECTS_DEBUG(val != quantity_values<Value>::zero());
// TODO use /= when compiler bug is resolved: // TODO use /= when compiler bug is resolved:
// https://developercommunity.visualstudio.com/t/Discrepancy-in-Behavior-of-operator-an/10732445 // https://developercommunity.visualstudio.com/t/Discrepancy-in-Behavior-of-operator-an/10732445
lhs.numerical_value_is_an_implementation_detail_ = lhs.numerical_value_is_an_implementation_detail_ / v; lhs.numerical_value_is_an_implementation_detail_ = lhs.numerical_value_is_an_implementation_detail_ / val;
return std::forward<Q>(lhs); return std::forward<Q>(lhs);
} }
@ -551,17 +551,17 @@ public:
template<std::derived_from<quantity> Q, typename Value> template<std::derived_from<quantity> Q, typename Value>
requires(!Quantity<Value>) && requires(!Quantity<Value>) &&
(!Reference<Value>) && detail::InvokeResultOf<quantity_spec, std::multiplies<>, Rep, const Value&> (!Reference<Value>) && detail::InvokeResultOf<quantity_spec, std::multiplies<>, Rep, const Value&>
[[nodiscard]] friend constexpr QuantityOf<quantity_spec> auto operator*(const Q& q, const Value& v) [[nodiscard]] friend constexpr QuantityOf<quantity_spec> auto operator*(const Q& q, const Value& val)
{ {
return ::mp_units::quantity{q.numerical_value_ref_in(unit) * v, R}; return ::mp_units::quantity{q.numerical_value_ref_in(unit) * val, R};
} }
template<typename Value, std::derived_from<quantity> Q> template<typename Value, std::derived_from<quantity> Q>
requires(!Quantity<Value>) && requires(!Quantity<Value>) &&
(!Reference<Value>) && detail::InvokeResultOf<quantity_spec, std::multiplies<>, const Value&, Rep> (!Reference<Value>) && detail::InvokeResultOf<quantity_spec, std::multiplies<>, const Value&, Rep>
[[nodiscard]] friend constexpr QuantityOf<quantity_spec> auto operator*(const Value& v, const Q& q) [[nodiscard]] friend constexpr QuantityOf<quantity_spec> auto operator*(const Value& val, const Q& q)
{ {
return ::mp_units::quantity{v * q.numerical_value_ref_in(unit), R}; return ::mp_units::quantity{val * q.numerical_value_ref_in(unit), R};
} }
template<std::derived_from<quantity> Q, auto R2, typename Rep2> template<std::derived_from<quantity> Q, auto R2, typename Rep2>
@ -575,18 +575,18 @@ public:
template<std::derived_from<quantity> Q, typename Value> template<std::derived_from<quantity> Q, typename Value>
requires(!Quantity<Value>) && requires(!Quantity<Value>) &&
(!Reference<Value>) && detail::InvokeResultOf<quantity_spec, std::divides<>, Rep, const Value&> (!Reference<Value>) && detail::InvokeResultOf<quantity_spec, std::divides<>, Rep, const Value&>
[[nodiscard]] friend constexpr QuantityOf<quantity_spec> auto operator/(const Q& q, const Value& v) [[nodiscard]] friend constexpr QuantityOf<quantity_spec> auto operator/(const Q& q, const Value& val)
{ {
MP_UNITS_EXPECTS_DEBUG(v != quantity_values<Value>::zero()); MP_UNITS_EXPECTS_DEBUG(val != quantity_values<Value>::zero());
return ::mp_units::quantity{q.numerical_value_ref_in(unit) / v, R}; return ::mp_units::quantity{q.numerical_value_ref_in(unit) / val, R};
} }
template<typename Value, std::derived_from<quantity> Q> template<typename Value, std::derived_from<quantity> Q>
requires(!Quantity<Value>) && requires(!Quantity<Value>) &&
(!Reference<Value>) && detail::InvokeResultOf<quantity_spec, std::divides<>, const Value&, Rep> (!Reference<Value>) && detail::InvokeResultOf<quantity_spec, std::divides<>, const Value&, Rep>
[[nodiscard]] friend constexpr QuantityOf<inverse(quantity_spec)> auto operator/(const Value& v, const Q& q) [[nodiscard]] friend constexpr QuantityOf<inverse(quantity_spec)> auto operator/(const Value& val, const Q& q)
{ {
return ::mp_units::quantity{v / q.numerical_value_ref_in(unit), ::mp_units::one / R}; return ::mp_units::quantity{val / q.numerical_value_ref_in(unit), ::mp_units::one / R};
} }
template<std::derived_from<quantity> Q, auto R2, typename Rep2> template<std::derived_from<quantity> Q, auto R2, typename Rep2>

View File

@ -118,12 +118,11 @@ concept ComplexRepresentation = Complex<T> && WeaklyRegular<T> && requires(T a,
{ a - b } -> Complex; { a - b } -> Complex;
{ a* b } -> Complex; { a* b } -> Complex;
{ a / b } -> Complex; { a / b } -> Complex;
// TBD { real(a) } -> Scalar;
// { re(a) } -> Scalar; { imag(a) } -> Scalar;
// { im(a) } -> Scalar; { abs(a) } -> Scalar;
// { mod(a) } -> Scalar; { arg(a) } -> Scalar;
// { arg(a) } -> Scalar; { conj(a) } -> Complex;
// { conj(a) } -> Complex;
}; };
// TODO how to check for a complex(Scalar, Scalar) -> Complex? // TODO how to check for a complex(Scalar, Scalar) -> Complex?

View File

@ -68,8 +68,8 @@ void to_stream_impl(std::basic_ostream<CharT, Traits>& os, const quantity<R, Rep
} }
template<typename CharT, class Traits, typename T> template<typename CharT, class Traits, typename T>
std::basic_ostream<CharT, Traits>& to_stream(std::basic_ostream<CharT, Traits>& os, const T& v) std::basic_ostream<CharT, Traits>& to_stream(std::basic_ostream<CharT, Traits>& os, const T& val)
requires requires { detail::to_stream_impl(os, v); } requires requires { detail::to_stream_impl(os, val); }
{ {
if (os.width()) { if (os.width()) {
// std::setw() applies to the whole output so it has to be first put into std::string // std::setw() applies to the whole output so it has to be first put into std::string
@ -77,11 +77,11 @@ std::basic_ostream<CharT, Traits>& to_stream(std::basic_ostream<CharT, Traits>&
oss.flags(os.flags()); oss.flags(os.flags());
oss.imbue(os.getloc()); oss.imbue(os.getloc());
oss.precision(os.precision()); oss.precision(os.precision());
detail::to_stream_impl(oss, v); detail::to_stream_impl(oss, val);
return os << std::move(oss).str(); return os << std::move(oss).str();
} }
detail::to_stream_impl(os, v); detail::to_stream_impl(os, val);
return os; return os;
} }
@ -93,10 +93,10 @@ constexpr bool is_mp_units_stream = requires(OStream os, T v) { detail::to_strea
MP_UNITS_EXPORT_BEGIN MP_UNITS_EXPORT_BEGIN
template<typename CharT, typename Traits, typename T> template<typename CharT, typename Traits, typename T>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const T& v) std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const T& val)
requires detail::is_mp_units_stream<std::basic_ostream<CharT, Traits>, T> requires detail::is_mp_units_stream<std::basic_ostream<CharT, Traits>, T>
{ {
return detail::to_stream(os, v); return detail::to_stream(os, val);
} }
MP_UNITS_EXPORT_END MP_UNITS_EXPORT_END

View File

@ -83,10 +83,10 @@ struct quantity_like_traits<std::chrono::duration<Rep, Period>> {
return q.count(); return q.count();
} }
[[nodiscard]] static constexpr T from_numerical_value(const rep& v) noexcept( [[nodiscard]] static constexpr T from_numerical_value(const rep& val) noexcept(
std::is_nothrow_copy_constructible_v<rep>) std::is_nothrow_copy_constructible_v<rep>)
{ {
return T(v); return T(val);
} }
}; };
@ -113,10 +113,10 @@ struct quantity_point_like_traits<std::chrono::time_point<C, std::chrono::durati
return tp.time_since_epoch().count(); return tp.time_since_epoch().count();
} }
[[nodiscard]] static constexpr T from_numerical_value(const rep& v) noexcept( [[nodiscard]] static constexpr T from_numerical_value(const rep& val) noexcept(
std::is_nothrow_copy_constructible_v<rep>) std::is_nothrow_copy_constructible_v<rep>)
{ {
return T(std::chrono::duration<Rep, Period>(v)); return T(std::chrono::duration<Rep, Period>(val));
} }
}; };

View File

@ -24,13 +24,14 @@ find_package(Catch2 3 REQUIRED)
add_executable( add_executable(
unit_tests_runtime unit_tests_runtime
atomic_test.cpp
cartesian_vector_test.cpp
distribution_test.cpp distribution_test.cpp
fixed_string_test.cpp fixed_string_test.cpp
fmt_test.cpp fmt_test.cpp
math_test.cpp math_test.cpp
atomic_test.cpp
truncation_test.cpp
quantity_test.cpp quantity_test.cpp
truncation_test.cpp
) )
if(${projectPrefix}BUILD_CXX_MODULES) if(${projectPrefix}BUILD_CXX_MODULES)
target_compile_definitions(unit_tests_runtime PUBLIC ${projectPrefix}MODULES) target_compile_definitions(unit_tests_runtime PUBLIC ${projectPrefix}MODULES)

View File

@ -0,0 +1,381 @@
// 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.
#include "almost_equals.h"
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <mp-units/compat_macros.h>
#include <mp-units/ext/format.h>
#ifdef MP_UNITS_IMPORT_STD
import std;
#else
#include <sstream>
#endif
#ifdef MP_UNITS_MODULES
import mp_units;
#else
#include <mp-units/cartesian_vector.h>
#endif
using namespace mp_units;
using namespace Catch::Matchers;
TEST_CASE("cartesian_vector operations", "[vector]")
{
SECTION("cartesian_vector initialization and access")
{
SECTION("one argument")
{
cartesian_vector v{1.0};
REQUIRE(v[0] == 1.0);
REQUIRE(v[1] == 0);
REQUIRE(v[2] == 0);
}
SECTION("two arguments")
{
cartesian_vector v{1.0, 2.0};
REQUIRE(v[0] == 1.0);
REQUIRE(v[1] == 2.0);
REQUIRE(v[2] == 0);
}
SECTION("all arguments")
{
cartesian_vector v{1.0, 2.0, 3.0};
REQUIRE(v[0] == 1.0);
REQUIRE(v[1] == 2.0);
REQUIRE(v[2] == 3.0);
}
}
SECTION("convertibility")
{
cartesian_vector v1{1, 2, 3};
SECTION("construction")
{
cartesian_vector v2 = v1;
REQUIRE(v2[0] == 1.0);
REQUIRE(v2[1] == 2.0);
REQUIRE(v2[2] == 3.0);
}
SECTION("assignment")
{
cartesian_vector v2{3.0, 2.0, 1.0};
v2 = v1;
REQUIRE(v2[0] == 1.0);
REQUIRE(v2[1] == 2.0);
REQUIRE(v2[2] == 3.0);
}
}
SECTION("cartesian_vector addition")
{
SECTION("double + double")
{
cartesian_vector v1{1.0, 2.0, 3.0};
cartesian_vector v2{4.0, 5.0, 6.0};
cartesian_vector result = v1 + v2;
REQUIRE(result[0] == 5.0);
REQUIRE(result[1] == 7.0);
REQUIRE(result[2] == 9.0);
}
SECTION("double + int")
{
cartesian_vector v1{1.0, 2.0, 3.0};
cartesian_vector v2{4, 5, 6};
cartesian_vector result = v1 + v2;
REQUIRE(result[0] == 5.0);
REQUIRE(result[1] == 7.0);
REQUIRE(result[2] == 9.0);
}
SECTION("int + double")
{
cartesian_vector v1{1, 2, 3};
cartesian_vector v2{4.0, 5.0, 6.0};
cartesian_vector result = v1 + v2;
REQUIRE(result[0] == 5.0);
REQUIRE(result[1] == 7.0);
REQUIRE(result[2] == 9.0);
}
SECTION("int + int")
{
cartesian_vector v1{1, 2, 3};
cartesian_vector v2{4, 5, 6};
cartesian_vector result = v1 + v2;
REQUIRE(result[0] == 5);
REQUIRE(result[1] == 7);
REQUIRE(result[2] == 9);
}
}
SECTION("cartesian_vector subtraction")
{
SECTION("double - double")
{
cartesian_vector v1{4.0, 5.0, 6.0};
cartesian_vector v2{1.0, 2.0, 3.0};
cartesian_vector result = v1 - v2;
REQUIRE(result[0] == 3.0);
REQUIRE(result[1] == 3.0);
REQUIRE(result[2] == 3.0);
}
SECTION("double - int")
{
cartesian_vector v1{4.0, 5.0, 6.0};
cartesian_vector v2{1, 2, 3};
cartesian_vector result = v1 - v2;
REQUIRE(result[0] == 3.0);
REQUIRE(result[1] == 3.0);
REQUIRE(result[2] == 3.0);
}
SECTION("int - double")
{
cartesian_vector v1{4, 5, 6};
cartesian_vector v2{1.0, 2.0, 3.0};
cartesian_vector result = v1 - v2;
REQUIRE(result[0] == 3.0);
REQUIRE(result[1] == 3.0);
REQUIRE(result[2] == 3.0);
}
SECTION("int - int")
{
cartesian_vector v1{4, 5, 6};
cartesian_vector v2{1, 2, 3};
cartesian_vector result = v1 - v2;
REQUIRE(result[0] == 3);
REQUIRE(result[1] == 3);
REQUIRE(result[2] == 3);
}
}
SECTION("cartesian_vector scalar multiplication")
{
SECTION("double * double")
{
cartesian_vector v{1.0, 2.0, 3.0};
cartesian_vector result = v * 2.0;
REQUIRE(result[0] == 2.0);
REQUIRE(result[1] == 4.0);
REQUIRE(result[2] == 6.0);
}
SECTION("double * int")
{
cartesian_vector v{1.0, 2.0, 3.0};
cartesian_vector result = v * 2;
REQUIRE(result[0] == 2.0);
REQUIRE(result[1] == 4.0);
REQUIRE(result[2] == 6.0);
}
SECTION("int * double")
{
cartesian_vector v{1, 2, 3};
cartesian_vector result = v * 2.0;
REQUIRE(result[0] == 2.0);
REQUIRE(result[1] == 4.0);
REQUIRE(result[2] == 6.0);
}
SECTION("int * int")
{
cartesian_vector v{1, 2, 3};
cartesian_vector result = v * 2;
REQUIRE(result[0] == 2);
REQUIRE(result[1] == 4);
REQUIRE(result[2] == 6);
}
}
SECTION("cartesian_vector scalar division")
{
SECTION("double / double")
{
cartesian_vector v{2.0, 4.0, 6.0};
cartesian_vector result = v / 2.0;
REQUIRE(result[0] == 1.0);
REQUIRE(result[1] == 2.0);
REQUIRE(result[2] == 3.0);
}
SECTION("double / int")
{
cartesian_vector v{2.0, 4.0, 6.0};
cartesian_vector result = v / 2;
REQUIRE(result[0] == 1.0);
REQUIRE(result[1] == 2.0);
REQUIRE(result[2] == 3.0);
}
SECTION("int / double")
{
cartesian_vector v{2, 4, 6};
cartesian_vector result = v / 2.0;
REQUIRE(result[0] == 1.0);
REQUIRE(result[1] == 2.0);
REQUIRE(result[2] == 3.0);
}
SECTION("int / int")
{
cartesian_vector v{2, 4, 6};
cartesian_vector result = v / 2;
REQUIRE(result[0] == 1);
REQUIRE(result[1] == 2);
REQUIRE(result[2] == 3);
}
}
SECTION("cartesian_vector magnitude")
{
cartesian_vector v1{3.0, 4.0, 0.0};
cartesian_vector v2{2.0, 3.0, 6.0};
REQUIRE(v1.magnitude() == 5.0);
REQUIRE(v2.magnitude() == 7.0);
}
SECTION("cartesian_vector unit vector")
{
cartesian_vector v{3.0, 4.0, 0.0};
cartesian_vector unit_v = v.unit();
REQUIRE_THAT(unit_v.magnitude(), WithinULP(1.0, 1));
}
SECTION("cartesian_vector equality")
{
cartesian_vector v1{1.0, 2.0, 3.0};
cartesian_vector v2{1, 2, 3};
cartesian_vector v3{1.1, 2.0, 3.0};
cartesian_vector v4{1.0, 2.1, 3.0};
cartesian_vector v5{1.0, 2.0, 3.1};
REQUIRE(v1 == v2);
REQUIRE(v1 != v3);
REQUIRE(v1 != v4);
REQUIRE(v1 != v5);
}
SECTION("cartesian_vector scalar product")
{
SECTION("double * double")
{
cartesian_vector v1{1.0, 2.0, 3.0};
cartesian_vector v2{4.0, 5.0, 6.0};
REQUIRE(scalar_product(v1, v2) == 32.0);
}
SECTION("double * int")
{
cartesian_vector v1{1.0, 2.0, 3.0};
cartesian_vector v2{4, 5, 6};
REQUIRE(scalar_product(v1, v2) == 32.0);
}
SECTION("int * double")
{
cartesian_vector v1{1, 2, 3};
cartesian_vector v2{4.0, 5.0, 6.0};
REQUIRE(scalar_product(v1, v2) == 32.0);
}
SECTION("int * int")
{
cartesian_vector v1{1, 2, 3};
cartesian_vector v2{4, 5, 6};
REQUIRE(scalar_product(v1, v2) == 32);
}
}
SECTION("cartesian_vector vector product")
{
SECTION("double * double")
{
cartesian_vector v1{1.0, 2.0, 3.0};
cartesian_vector v2{4.0, 5.0, 6.0};
cartesian_vector result = vector_product(v1, v2);
REQUIRE(result[0] == -3.0);
REQUIRE(result[1] == 6.0);
REQUIRE(result[2] == -3.0);
}
SECTION("double * int")
{
cartesian_vector v1{1.0, 2.0, 3.0};
cartesian_vector v2{4, 5, 6};
cartesian_vector result = vector_product(v1, v2);
REQUIRE(result[0] == -3.0);
REQUIRE(result[1] == 6.0);
REQUIRE(result[2] == -3.0);
}
SECTION("int * double")
{
cartesian_vector v1{1, 2, 3};
cartesian_vector v2{4.0, 5.0, 6.0};
cartesian_vector result = vector_product(v1, v2);
REQUIRE(result[0] == -3.0);
REQUIRE(result[1] == 6.0);
REQUIRE(result[2] == -3.0);
}
SECTION("int * int")
{
cartesian_vector v1{1, 2, 3};
cartesian_vector v2{4, 5, 6};
cartesian_vector result = vector_product(v1, v2);
REQUIRE(result[0] == -3);
REQUIRE(result[1] == 6);
REQUIRE(result[2] == -3);
}
}
}
TEST_CASE("cartesian_vector text output", "[vector][fmt][ostream]")
{
std::ostringstream os;
SECTION("integral representation")
{
cartesian_vector v{1, 2, 3};
os << v;
SECTION("iostream") { CHECK(os.str() == "[1, 2, 3]"); }
SECTION("fmt with default format {}") { CHECK(MP_UNITS_STD_FMT::format("{}", v) == os.str()); }
}
SECTION("floating-point representation")
{
cartesian_vector v{1.2, 2.3, 3.4};
os << v;
SECTION("iostream") { CHECK(os.str() == "[1.2, 2.3, 3.4]"); }
SECTION("fmt with default format {}") { CHECK(MP_UNITS_STD_FMT::format("{}", v) == os.str()); }
}
}

View File

@ -43,8 +43,10 @@ import mp_units;
using namespace mp_units; using namespace mp_units;
TEST_CASE("uniform_int_distribution") TEST_CASE("distributions", "[random][distribution]")
{ {
SECTION("uniform_int_distribution")
{
using rep = std::int64_t; using rep = std::int64_t;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -69,10 +71,10 @@ TEST_CASE("uniform_int_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("uniform_real_distribution") SECTION("uniform_real_distribution")
{ {
using rep = long double; using rep = long double;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -97,10 +99,10 @@ TEST_CASE("uniform_real_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("binomial_distribution") SECTION("binomial_distribution")
{ {
using rep = std::int64_t; using rep = std::int64_t;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -125,10 +127,10 @@ TEST_CASE("binomial_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("negative_binomial_distribution") SECTION("negative_binomial_distribution")
{ {
using rep = std::int64_t; using rep = std::int64_t;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -153,10 +155,10 @@ TEST_CASE("negative_binomial_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("geometric_distribution") SECTION("geometric_distribution")
{ {
using rep = std::int64_t; using rep = std::int64_t;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -178,10 +180,10 @@ TEST_CASE("geometric_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("poisson_distribution") SECTION("poisson_distribution")
{ {
using rep = std::int64_t; using rep = std::int64_t;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -203,10 +205,10 @@ TEST_CASE("poisson_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("exponential_distribution") SECTION("exponential_distribution")
{ {
using rep = long double; using rep = long double;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -228,10 +230,10 @@ TEST_CASE("exponential_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("gamma_distribution") SECTION("gamma_distribution")
{ {
using rep = long double; using rep = long double;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -256,10 +258,10 @@ TEST_CASE("gamma_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("weibull_distribution") SECTION("weibull_distribution")
{ {
using rep = long double; using rep = long double;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -284,10 +286,10 @@ TEST_CASE("weibull_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("extreme_value_distribution") SECTION("extreme_value_distribution")
{ {
using rep = long double; using rep = long double;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -312,10 +314,10 @@ TEST_CASE("extreme_value_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("normal_distribution") SECTION("normal_distribution")
{ {
using rep = long double; using rep = long double;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -340,10 +342,10 @@ TEST_CASE("normal_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("lognormal_distribution") SECTION("lognormal_distribution")
{ {
using rep = long double; using rep = long double;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -368,10 +370,10 @@ TEST_CASE("lognormal_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("chi_squared_distribution") SECTION("chi_squared_distribution")
{ {
using rep = long double; using rep = long double;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -393,10 +395,10 @@ TEST_CASE("chi_squared_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("cauchy_distribution") SECTION("cauchy_distribution")
{ {
using rep = long double; using rep = long double;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -421,10 +423,10 @@ TEST_CASE("cauchy_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("fisher_f_distribution") SECTION("fisher_f_distribution")
{ {
using rep = long double; using rep = long double;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -449,10 +451,10 @@ TEST_CASE("fisher_f_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("student_t_distribution") SECTION("student_t_distribution")
{ {
using rep = long double; using rep = long double;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -474,10 +476,10 @@ TEST_CASE("student_t_distribution")
CHECK(units_dist.min() == stl_dist.min() * si::metre); CHECK(units_dist.min() == stl_dist.min() * si::metre);
CHECK(units_dist.max() == stl_dist.max() * si::metre); CHECK(units_dist.max() == stl_dist.max() * si::metre);
} }
} }
TEST_CASE("discrete_distribution") SECTION("discrete_distribution")
{ {
using rep = std::int64_t; using rep = std::int64_t;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -521,10 +523,10 @@ TEST_CASE("discrete_distribution")
CHECK(units_dist.probabilities() == stl_dist.probabilities()); CHECK(units_dist.probabilities() == stl_dist.probabilities());
} }
} }
TEST_CASE("piecewise_constant_distribution") SECTION("piecewise_constant_distribution")
{ {
using rep = long double; using rep = long double;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -589,10 +591,10 @@ TEST_CASE("piecewise_constant_distribution")
CHECK(units_dist.intervals() == intervals_qty_vec); CHECK(units_dist.intervals() == intervals_qty_vec);
CHECK(units_dist.densities() == stl_dist.densities()); CHECK(units_dist.densities() == stl_dist.densities());
} }
} }
TEST_CASE("piecewise_linear_distribution") SECTION("piecewise_linear_distribution")
{ {
using rep = long double; using rep = long double;
using q = quantity<isq::length[si::metre], rep>; using q = quantity<isq::length[si::metre], rep>;
@ -657,4 +659,5 @@ TEST_CASE("piecewise_linear_distribution")
CHECK(units_dist.intervals() == intervals_qty_vec); CHECK(units_dist.intervals() == intervals_qty_vec);
CHECK(units_dist.densities() == stl_dist.densities()); CHECK(units_dist.densities() == stl_dist.densities());
} }
}
} }

View File

@ -38,8 +38,10 @@ import mp_units;
using namespace mp_units; using namespace mp_units;
TEST_CASE("fixed_string::at", "[fixed_string]") TEST_CASE("fixed_string operations", "[fixed_string]")
{ {
SECTION("fixed_string::at")
{
basic_fixed_string txt = "abc"; basic_fixed_string txt = "abc";
SECTION("in range") SECTION("in range")
{ {
@ -52,6 +54,7 @@ TEST_CASE("fixed_string::at", "[fixed_string]")
REQUIRE_THROWS_MATCHES(txt.at(3), std::out_of_range, Catch::Matchers::Message("basic_fixed_string::at")); REQUIRE_THROWS_MATCHES(txt.at(3), std::out_of_range, Catch::Matchers::Message("basic_fixed_string::at"));
REQUIRE_THROWS_MATCHES(txt.at(1024), std::out_of_range, Catch::Matchers::Message("basic_fixed_string::at")); REQUIRE_THROWS_MATCHES(txt.at(1024), std::out_of_range, Catch::Matchers::Message("basic_fixed_string::at"));
} }
}
} }
TEST_CASE("fixed_string text output", "[fixed_string][ostream][fmt]") TEST_CASE("fixed_string text output", "[fixed_string][ostream][fmt]")

View File

@ -39,6 +39,7 @@ import std;
#ifdef MP_UNITS_MODULES #ifdef MP_UNITS_MODULES
import mp_units; import mp_units;
#else #else
#include <mp-units/cartesian_vector.h>
#include <mp-units/format.h> #include <mp-units/format.h>
#include <mp-units/ostream.h> // IWYU pragma: keep #include <mp-units/ostream.h> // IWYU pragma: keep
#include <mp-units/systems/cgs.h> #include <mp-units/systems/cgs.h>
@ -48,14 +49,275 @@ import mp_units;
#include <mp-units/systems/si.h> #include <mp-units/systems/si.h>
#endif #endif
template<class T>
requires mp_units::is_scalar<T>
constexpr bool mp_units::is_vector<T> = true;
using namespace mp_units; using namespace mp_units;
using namespace mp_units::si::unit_symbols; using namespace mp_units::si::unit_symbols;
using v = cartesian_vector<double>;
TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") TEST_CASE("dimension_symbol", "[dimension][symbol]")
{
using enum text_encoding;
std::ostringstream os;
SECTION("default formatting")
{
os << dimension_symbol(isq::power.dimension);
CHECK(os.str() == "L²MT⁻³");
}
SECTION("Portable mode")
{
os << dimension_symbol<dimension_symbol_formatting{.encoding = portable}>(isq::power.dimension);
CHECK(os.str() == "L^2MT^-3");
}
}
TEST_CASE("unit_symbol", "[unit][symbol]")
{
using enum text_encoding;
using enum unit_symbol_solidus;
using enum unit_symbol_separator;
std::ostringstream os;
SECTION("default formatting")
{
os << unit_symbol(m / s2);
CHECK(os.str() == "m/s²");
}
SECTION("Portable mode")
{
os << unit_symbol<unit_symbol_formatting{.encoding = portable}>(m / s2);
CHECK(os.str() == "m/s^2");
}
SECTION("solidus")
{
os << unit_symbol<unit_symbol_formatting{.solidus = never}>(m / s2);
CHECK(os.str() == "m s⁻²");
}
SECTION("separator")
{
os << unit_symbol<unit_symbol_formatting{.solidus = never, .separator = half_high_dot}>(m / s2);
CHECK(os.str() == "m⋅s⁻²");
}
}
// TODO add dimension formatting tests
TEST_CASE("unit formatting", "[unit][fmt]")
{
SECTION("Unit formatting should use proper text encoding")
{
SECTION("Unicode text output")
{
CHECK(MP_UNITS_STD_FMT::format("{:U}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:U}", si::kilo<si::ohm>) == "");
CHECK(MP_UNITS_STD_FMT::format("{:U}", us) == "µs");
CHECK(MP_UNITS_STD_FMT::format("{:U}", m / s2) == "m/s²");
}
SECTION("Unicode text output is used by default")
{
CHECK(MP_UNITS_STD_FMT::format("{}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{}", si::kilo<si::ohm>) == "");
CHECK(MP_UNITS_STD_FMT::format("{}", us) == "µs");
CHECK(MP_UNITS_STD_FMT::format("{}", m / s2) == "m/s²");
}
SECTION("Portable text output")
{
CHECK(MP_UNITS_STD_FMT::format("{:P}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:P}", si::kilo<si::ohm>) == "kohm");
CHECK(MP_UNITS_STD_FMT::format("{:P}", us) == "us");
CHECK(MP_UNITS_STD_FMT::format("{:P}", m / s2) == "m/s^2");
}
}
SECTION("unit formatting should print solidus according to specs")
{
SECTION("Solidus for only one element in denominator")
{
CHECK(MP_UNITS_STD_FMT::format("{:1}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:1}", m / s2) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:1}", kg / m / s2) == "kg m⁻¹ s⁻²");
}
SECTION("Solidus for only one element in denominator is used by default")
{
CHECK(MP_UNITS_STD_FMT::format("{}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{}", m / s2) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{}", kg / m / s2) == "kg m⁻¹ s⁻²");
}
SECTION("Always use solidus")
{
CHECK(MP_UNITS_STD_FMT::format("{:a}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:a}", m / s2) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:a}", kg / m / s2) == "kg/(m s²)");
}
SECTION("Never use solidus")
{
CHECK(MP_UNITS_STD_FMT::format("{:n}", km / h) == "km h⁻¹");
CHECK(MP_UNITS_STD_FMT::format("{:n}", m / s2) == "m s⁻²");
CHECK(MP_UNITS_STD_FMT::format("{:n}", kg / m / s2) == "kg m⁻¹ s⁻²");
}
}
SECTION("Unit formatting should user proper separator")
{
SECTION("Space")
{
CHECK(MP_UNITS_STD_FMT::format("{:s}", kg * m / s2) == "kg m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:s}", kg / m / s2) == "kg m⁻¹ s⁻²");
CHECK(MP_UNITS_STD_FMT::format("{:sa}", kg / m / s2) == "kg/(m s²)");
}
SECTION("Space is used by default")
{
CHECK(MP_UNITS_STD_FMT::format("{}", kg * m / s2) == "kg m/s²");
CHECK(MP_UNITS_STD_FMT::format("{}", kg / m / s2) == "kg m⁻¹ s⁻²");
CHECK(MP_UNITS_STD_FMT::format("{:a}", kg / m / s2) == "kg/(m s²)");
}
SECTION("Dot")
{
CHECK(MP_UNITS_STD_FMT::format("{:d}", kg * m / s2) == "kg⋅m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:d}", kg / m / s2) == "kg⋅m⁻¹⋅s⁻²");
CHECK(MP_UNITS_STD_FMT::format("{:ad}", kg / m / s2) == "kg/(m⋅s²)");
}
}
}
TEST_CASE("unit formatting error handling", "[unit][fmt][exception]")
{
SECTION("unknown unit modifiers should throw")
{
SECTION("only the invalid modifier")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:x}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("invalid unit modifier specified"));
}
SECTION("invalid modifier in the front")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:xUda}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("invalid unit modifier specified"));
}
SECTION("invalid modifier in the end")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:Udax}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("invalid unit modifier specified"));
}
SECTION("invalid modifier in the middle")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:Udxa}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("invalid unit modifier specified"));
}
}
SECTION("repeated unit modifiers should throw")
{
SECTION("text encoding")
{
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:UdaU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:dUaU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:dUUa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec"));
}
SECTION("solidus")
{
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:aUda}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:daUa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:daaU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
}
SECTION("separator")
{
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:dUad}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:dadU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:addU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
}
}
SECTION("more then one modifier of the same kind should throw")
{
SECTION("text encoding")
{
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:UdaP}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:dPaU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:dPUa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec"));
}
SECTION("solidus")
{
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:aUdn}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:dnUa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:da1U}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
}
SECTION("separator")
{
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:dUas}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:sadU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(
MP_UNITS_STD_FMT::vformat("{:adsU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
}
}
SECTION("half_high_dot separator requested for portable encoding should throw")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dPa}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("half_high_dot unit separator allowed only for UTF-8 encoding"));
}
}
TEST_CASE("default quantity formatting", "[quantity][ostream][fmt]")
{ {
std::ostringstream os; std::ostringstream os;
@ -138,12 +400,12 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
} }
} }
SECTION("surface tension") SECTION("entropy")
{ {
const auto q = 20 * isq::force[N] / (2 * isq::length[m]); const auto q = 20 * isq::kinetic_energy[J] / (delta<isq::thermodynamic_temperature[K]>(2));
os << q; os << q;
SECTION("iostream") { CHECK(os.str() == "10 N/m"); } SECTION("iostream") { CHECK(os.str() == "10 J/K"); }
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
@ -173,10 +435,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("angular impulse") SECTION("angular impulse")
{ {
const auto q = 123 * isq::angular_impulse[N * m * s]; const auto q = v{1, 2, 3} * isq::angular_impulse[N * m * s];
os << q; os << q;
SECTION("iostream") { CHECK(os.str() == "123 m N s"); } SECTION("iostream") { CHECK(os.str() == "[1, 2, 3] m N s"); }
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
@ -203,10 +465,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("angular acceleration") SECTION("angular acceleration")
{ {
const auto q = 123 * isq::angular_acceleration[rad / s2]; const auto q = v{1, 2, 3} * isq::angular_acceleration[rad / s2];
os << q; os << q;
SECTION("iostream") { CHECK(os.str() == "123 rad/s²"); } SECTION("iostream") { CHECK(os.str() == "[1, 2, 3] rad/s²"); }
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
@ -375,266 +637,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
} }
} }
TEST_CASE("quantity format string with only %N should print quantity value only", "[text][fmt]") TEST_CASE("quantity fill and align specification", "[quantity][ostream][fmt]")
{
SECTION("integral representation")
{
SECTION("positive value") { CHECK(MP_UNITS_STD_FMT::format("{:%N}", 123 * isq::speed[km / h]) == "123"); }
SECTION("negative value")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N}", 5 * isq::length[m] - 10 * isq::length[m]) == "-5");
}
}
SECTION("floating-point representation")
{
SECTION("positive value")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N}", 221. * isq::length[km] / (2 * isq::time[h])) == "110.5");
}
SECTION("negative value")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N}", 3.14 * isq::length[m] - 10 * isq::length[m]) == "-6.859999999999999");
}
SECTION("nan")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits<double>::quiet_NaN() * isq::length[m]) == "nan");
}
SECTION("inf")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits<double>::infinity() * isq::length[m]) == "inf");
}
SECTION("-inf")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N}", -std::numeric_limits<double>::infinity() * isq::length[m]) == "-inf");
}
}
}
TEST_CASE("quantity format string with only %U should print quantity unit symbol only", "[text][fmt]")
{
CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::speed[km / h]) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::resistance[si::kilo<si::ohm>]) == "");
CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::time[us]) == "µs");
CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::acceleration[m / s2]) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * percent) == "%");
}
TEST_CASE("Unit formatting should use proper text encoding")
{
SECTION("Unicode text output")
{
CHECK(MP_UNITS_STD_FMT::format("{:U}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:U}", si::kilo<si::ohm>) == "");
CHECK(MP_UNITS_STD_FMT::format("{:U}", us) == "µs");
CHECK(MP_UNITS_STD_FMT::format("{:U}", m / s2) == "m/s²");
}
SECTION("Unicode text output is used by default")
{
CHECK(MP_UNITS_STD_FMT::format("{}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{}", si::kilo<si::ohm>) == "");
CHECK(MP_UNITS_STD_FMT::format("{}", us) == "µs");
CHECK(MP_UNITS_STD_FMT::format("{}", m / s2) == "m/s²");
}
SECTION("Portable text output")
{
CHECK(MP_UNITS_STD_FMT::format("{:P}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:P}", si::kilo<si::ohm>) == "kohm");
CHECK(MP_UNITS_STD_FMT::format("{:P}", us) == "us");
CHECK(MP_UNITS_STD_FMT::format("{:P}", m / s2) == "m/s^2");
}
}
TEST_CASE("Unit formatting should print solidus according to specs")
{
SECTION("Solidus for only one element in denominator")
{
CHECK(MP_UNITS_STD_FMT::format("{:1}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:1}", m / s2) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:1}", kg / m / s2) == "kg m⁻¹ s⁻²");
}
SECTION("Solidus for only one element in denominator is used by default")
{
CHECK(MP_UNITS_STD_FMT::format("{}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{}", m / s2) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{}", kg / m / s2) == "kg m⁻¹ s⁻²");
}
SECTION("Always use solidus")
{
CHECK(MP_UNITS_STD_FMT::format("{:a}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:a}", m / s2) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:a}", kg / m / s2) == "kg/(m s²)");
}
SECTION("Never use solidus")
{
CHECK(MP_UNITS_STD_FMT::format("{:n}", km / h) == "km h⁻¹");
CHECK(MP_UNITS_STD_FMT::format("{:n}", m / s2) == "m s⁻²");
CHECK(MP_UNITS_STD_FMT::format("{:n}", kg / m / s2) == "kg m⁻¹ s⁻²");
}
}
TEST_CASE("Unit formatting should user proper separator")
{
SECTION("Space")
{
CHECK(MP_UNITS_STD_FMT::format("{:s}", kg * m / s2) == "kg m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:s}", kg / m / s2) == "kg m⁻¹ s⁻²");
CHECK(MP_UNITS_STD_FMT::format("{:sa}", kg / m / s2) == "kg/(m s²)");
}
SECTION("Space is used by default")
{
CHECK(MP_UNITS_STD_FMT::format("{}", kg * m / s2) == "kg m/s²");
CHECK(MP_UNITS_STD_FMT::format("{}", kg / m / s2) == "kg m⁻¹ s⁻²");
CHECK(MP_UNITS_STD_FMT::format("{:a}", kg / m / s2) == "kg/(m s²)");
}
SECTION("Dot")
{
CHECK(MP_UNITS_STD_FMT::format("{:d}", kg * m / s2) == "kg⋅m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:d}", kg / m / s2) == "kg⋅m⁻¹⋅s⁻²");
CHECK(MP_UNITS_STD_FMT::format("{:ad}", kg / m / s2) == "kg/(m⋅s²)");
}
}
TEST_CASE("unknown unit modifiers should throw", "[text][fmt][exception]")
{
SECTION("only the invalid modifier")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:x}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified"));
}
SECTION("invalid modifier in the front")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:xUda}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified"));
}
SECTION("invalid modifier in the end")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:Udax}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified"));
}
SECTION("invalid modifier in the middle")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:Udxa}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified"));
}
}
TEST_CASE("repeated unit modifiers should throw", "[text][fmt][exception]")
{
SECTION("text encoding")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:UdaU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUaU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUUa}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec"));
}
SECTION("solidus")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:aUda}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:daUa}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:daaU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
}
SECTION("separator")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUad}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dadU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:addU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
}
}
TEST_CASE("more then one modifier of the same kind should throw", "[text][fmt][exception]")
{
SECTION("text encoding")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:UdaP}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dPaU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dPUa}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec"));
}
SECTION("solidus")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:aUdn}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dnUa}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:da1U}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
}
SECTION("separator")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUas}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:sadU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:adsU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
}
}
TEST_CASE("half_high_dot separator requested for portable encoding should throw", "[text][fmt][exception]")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dPa}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("half_high_dot unit separator allowed only for UTF-8 encoding"));
}
TEST_CASE("%U and %N can be put anywhere in a format string", "[text][fmt]")
{
SECTION("no space") { CHECK(MP_UNITS_STD_FMT::format("{:%N%U}", 123 * isq::speed[km / h]) == "123km/h"); }
SECTION("separator") { CHECK(MP_UNITS_STD_FMT::format("{:%N###%U}", 123 * isq::speed[km / h]) == "123###km/h"); }
SECTION("opposite order") { CHECK(MP_UNITS_STD_FMT::format("{:%U %N}", 123 * isq::speed[km / h]) == "km/h 123"); }
}
TEST_CASE("quantity fill and align specification", "[text][fmt][ostream]")
{ {
SECTION("ostream") SECTION("ostream")
{ {
@ -726,8 +729,75 @@ TEST_CASE("quantity fill and align specification", "[text][fmt][ostream]")
} }
} }
TEST_CASE("sign specification", "[text][fmt]") TEST_CASE("quantity subentities selection", "[quantity][fmt]")
{ {
SECTION("quantity format string with only %N should print quantity value only")
{
SECTION("integral representation")
{
SECTION("positive value") { CHECK(MP_UNITS_STD_FMT::format("{:%N}", 123 * isq::speed[km / h]) == "123"); }
SECTION("negative value")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N}", 5 * isq::length[m] - 10 * isq::length[m]) == "-5");
}
}
SECTION("floating-point representation")
{
SECTION("positive value")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N}", 221. * isq::length[km] / (2 * isq::time[h])) == "110.5");
}
SECTION("negative value")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N}", 3.14 * isq::length[m] - 10 * isq::length[m]) == "-6.859999999999999");
}
SECTION("nan")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits<double>::quiet_NaN() * isq::length[m]) == "nan");
}
SECTION("inf")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits<double>::infinity() * isq::length[m]) == "inf");
}
SECTION("-inf")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N}", -std::numeric_limits<double>::infinity() * isq::length[m]) == "-inf");
}
}
}
SECTION("quantity format string with only %U should print quantity unit symbol only")
{
CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::speed[km / h]) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::resistance[si::kilo<si::ohm>]) == "");
CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::time[us]) == "µs");
CHECK(MP_UNITS_STD_FMT::format("{:%U}", v{1, 2, 3} * isq::acceleration[m / s2]) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * percent) == "%");
}
SECTION("%U and %N can be put anywhere in a format string")
{
SECTION("no space") { CHECK(MP_UNITS_STD_FMT::format("{:%N%U}", 123 * isq::speed[km / h]) == "123km/h"); }
SECTION("separator") { CHECK(MP_UNITS_STD_FMT::format("{:%N###%U}", 123 * isq::speed[km / h]) == "123###km/h"); }
SECTION("opposite order") { CHECK(MP_UNITS_STD_FMT::format("{:%U %N}", 123 * isq::speed[km / h]) == "km/h 123"); }
}
}
// TODO provide basic tests if format string when provided in a quantity formatter are passed to respective dimensions
// and units formatters (detail formatting tests for dimensions and units are done separately)
TEST_CASE("quantity numerical value formatting for `std` arithmetic types", "[quantity][fmt]")
{
SECTION("sign specification")
{
auto inf = std::numeric_limits<double>::infinity() * si::metre; auto inf = std::numeric_limits<double>::infinity() * si::metre;
auto nan = std::numeric_limits<double>::quiet_NaN() * si::metre; auto nan = std::numeric_limits<double>::quiet_NaN() * si::metre;
@ -746,14 +816,15 @@ TEST_CASE("sign specification", "[text][fmt]")
SECTION("value only format {:%N} on a quantity") SECTION("value only format {:%N} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", 1 * isq::length[m]) == "1,+1,1, 1"); CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", 1 * isq::length[m]) == "1,+1,1, 1");
CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", -1 * isq::length[m]) == "-1,-1,-1,-1"); CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", -1 * isq::length[m]) ==
"-1,-1,-1,-1");
CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", inf) == "inf,+inf,inf, inf"); CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", inf) == "inf,+inf,inf, inf");
CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", nan) == "nan,+nan,nan, nan"); CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", nan) == "nan,+nan,nan, nan");
} }
} }
TEST_CASE("precision specification", "[text][fmt]") SECTION("precision specification")
{ {
SECTION("full format on a quantity") SECTION("full format on a quantity")
{ {
SECTION("default spec") SECTION("default spec")
@ -800,10 +871,10 @@ TEST_CASE("precision specification", "[text][fmt]")
CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450"); CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450");
CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000"); CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000");
} }
} }
TEST_CASE("type specification", "[text][fmt]") SECTION("type specification")
{ {
SECTION("full format {:%N%?%U} on a quantity") SECTION("full format {:%N%?%U} on a quantity")
{ {
SECTION("default spec") SECTION("default spec")
@ -942,10 +1013,10 @@ TEST_CASE("type specification", "[text][fmt]")
CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23"); CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23");
CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08"); CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08");
} }
} }
TEST_CASE("different base types with the # specifier", "[text][fmt]") SECTION("different base types with the # specifier")
{ {
SECTION("full format {:%N%?%U} on a quantity") SECTION("full format {:%N%?%U} on a quantity")
{ {
SECTION("default spec") SECTION("default spec")
@ -984,10 +1055,10 @@ TEST_CASE("different base types with the # specifier", "[text][fmt]")
CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#x]}", 42 * isq::length[m]) == "0x2a"); CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#x]}", 42 * isq::length[m]) == "0x2a");
CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#X]}", 42 * isq::length[m]) == "0X2A"); CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#X]}", 42 * isq::length[m]) == "0X2A");
} }
} }
TEST_CASE("localization with the 'L' specifier", "[text][fmt][localization]") SECTION("localization with the 'L' specifier")
{ {
struct group2 : std::numpunct<char> { struct group2 : std::numpunct<char> {
[[nodiscard]] char do_thousands_sep() const override { return '_'; } [[nodiscard]] char do_thousands_sep() const override { return '_'; }
[[nodiscard]] std::string do_grouping() const override { return "\2"; } [[nodiscard]] std::string do_grouping() const override { return "\2"; }
@ -1021,61 +1092,10 @@ TEST_CASE("localization with the 'L' specifier", "[text][fmt][localization]")
CHECK(MP_UNITS_STD_FMT::format(grp3, "{:%N%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458m/s"); CHECK(MP_UNITS_STD_FMT::format(grp3, "{:%N%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458m/s");
} }
} }
}
TEST_CASE("unit_symbol", "[text]")
{
using enum text_encoding;
using enum unit_symbol_solidus;
using enum unit_symbol_separator;
std::ostringstream os;
SECTION("default formatting")
{
os << unit_symbol(m / s2);
CHECK(os.str() == "m/s²");
}
SECTION("Portable mode")
{
os << unit_symbol<unit_symbol_formatting{.encoding = portable}>(m / s2);
CHECK(os.str() == "m/s^2");
}
SECTION("solidus")
{
os << unit_symbol<unit_symbol_formatting{.solidus = never}>(m / s2);
CHECK(os.str() == "m s⁻²");
}
SECTION("separator")
{
os << unit_symbol<unit_symbol_formatting{.solidus = never, .separator = half_high_dot}>(m / s2);
CHECK(os.str() == "m⋅s⁻²");
} }
} }
TEST_CASE("dimension_symbol", "[text]") TEST_CASE("check if `value_cast` properly changes the numerical value of a quantity", "[value_cast][ostream]")
{
using enum text_encoding;
std::ostringstream os;
SECTION("default formatting")
{
os << dimension_symbol(isq::power.dimension);
CHECK(os.str() == "L²MT⁻³");
}
SECTION("Portable mode")
{
os << dimension_symbol<dimension_symbol_formatting{.encoding = portable}>(isq::power.dimension);
CHECK(os.str() == "L^2MT^-3");
}
}
TEST_CASE("value_cast", "[text][ostream]")
{ {
std::ostringstream os; std::ostringstream os;

View File

@ -43,8 +43,10 @@ using namespace mp_units::si::unit_symbols;
// classical // classical
TEST_CASE("'pow<N>()' on quantity changes the value and the dimension accordingly", "[math][pow]") TEST_CASE("math operations", "[math]")
{ {
SECTION("'pow<N>()' on quantity changes the value and the dimension accordingly")
{
SECTION("'pow<0>(q)' returns '1'") { CHECK(pow<0>(2 * isq::length[m]) == 1 * one); } SECTION("'pow<0>(q)' returns '1'") { CHECK(pow<0>(2 * isq::length[m]) == 1 * one); }
SECTION("'pow<1>(q)' returns 'q'") { CHECK(pow<1>(2 * isq::length[m]) == 2 * isq::length[m]); } SECTION("'pow<1>(q)' returns 'q'") { CHECK(pow<1>(2 * isq::length[m]) == 2 * isq::length[m]); }
@ -58,26 +60,26 @@ TEST_CASE("'pow<N>()' on quantity changes the value and the dimension accordingl
{ {
CHECK(pow<3>(2 * isq::length[m]) == 8 * isq::volume[m3]); CHECK(pow<3>(2 * isq::length[m]) == 8 * isq::volume[m3]);
} }
} }
TEST_CASE("'sqrt()' on quantity changes the value and the dimension accordingly", "[math][sqrt]") SECTION("'sqrt()' on quantity changes the value and the dimension accordingly")
{ {
REQUIRE(sqrt(4 * isq::area[m2]) == 2 * isq::length[m]); REQUIRE(sqrt(4 * isq::area[m2]) == 2 * isq::length[m]);
} }
TEST_CASE("'cbrt()' on quantity changes the value and the dimension accordingly", "[math][cbrt]") SECTION("'cbrt()' on quantity changes the value and the dimension accordingly")
{ {
REQUIRE(cbrt(8 * isq::volume[m3]) == 2 * isq::length[m]); REQUIRE(cbrt(8 * isq::volume[m3]) == 2 * isq::length[m]);
} }
TEST_CASE("'fma()' on quantity changes the value and the dimension accordingly", "[math][fma]") SECTION("'fma()' on quantity changes the value and the dimension accordingly")
{ {
REQUIRE(fma(1.0 * isq::length[m], 2.0 * one, 2.0 * isq::length[m]) == 4.0 * isq::length[m]); REQUIRE(fma(1.0 * isq::length[m], 2.0 * one, 2.0 * isq::length[m]) == 4.0 * isq::length[m]);
REQUIRE(fma(isq::speed(10.0 * m / s), isq::time(2.0 * s), isq::height(42.0 * m)) == isq::length(62.0 * m)); REQUIRE(fma(isq::speed(10.0 * m / s), isq::time(2.0 * s), isq::height(42.0 * m)) == isq::length(62.0 * m));
} }
TEST_CASE("fmod functions", "[math][fmod]") SECTION("fmod functions")
{ {
SECTION("fmod should work on the same quantities") SECTION("fmod should work on the same quantities")
{ {
REQUIRE(fmod(4. * isq::length[km], 3. * isq::length[km]) == 1. * isq::length[km]); REQUIRE(fmod(4. * isq::length[km], 3. * isq::length[km]) == 1. * isq::length[km]);
@ -92,10 +94,10 @@ TEST_CASE("fmod functions", "[math][fmod]")
REQUIRE(fmod(3. * isq::length[km], 2000. * isq::length[m]) == 1000 * isq::length[m]); REQUIRE(fmod(3. * isq::length[km], 2000. * isq::length[m]) == 1000 * isq::length[m]);
REQUIRE(fmod(4 * isq::length[km], 2500 * isq::length[m]) == 1500 * isq::length[m]); REQUIRE(fmod(4 * isq::length[km], 2500 * isq::length[m]) == 1500 * isq::length[m]);
} }
} }
TEST_CASE("remainder functions", "[math][remainder]") SECTION("remainder functions")
{ {
SECTION("remainder should work on the same quantities") SECTION("remainder should work on the same quantities")
{ {
REQUIRE(remainder(4. * isq::length[km], 3. * isq::length[km]) == 1. * isq::length[km]); REQUIRE(remainder(4. * isq::length[km], 3. * isq::length[km]) == 1. * isq::length[km]);
@ -110,24 +112,24 @@ TEST_CASE("remainder functions", "[math][remainder]")
REQUIRE(remainder(3. * isq::length[km], 2000. * isq::length[m]) == -1000 * isq::length[m]); REQUIRE(remainder(3. * isq::length[km], 2000. * isq::length[m]) == -1000 * isq::length[m]);
REQUIRE(remainder(4 * isq::length[km], 2750 * isq::length[m]) == 1250 * isq::length[m]); REQUIRE(remainder(4 * isq::length[km], 2750 * isq::length[m]) == 1250 * isq::length[m]);
} }
} }
TEST_CASE("'isfinite()' accepts dimensioned arguments", "[math][isfinite]") { REQUIRE(isfinite(4.0 * isq::length[m])); } SECTION("'isfinite()' accepts dimensioned arguments") { REQUIRE(isfinite(4.0 * isq::length[m])); }
TEST_CASE("'isinf()' accepts dimensioned arguments", "[math][isinf]") { REQUIRE(!isinf(4.0 * isq::length[m])); } SECTION("'isinf()' accepts dimensioned arguments") { REQUIRE(!isinf(4.0 * isq::length[m])); }
TEST_CASE("'isnan()' accepts dimensioned arguments", "[math][isnan]") { REQUIRE(!isnan(4.0 * isq::length[m])); } SECTION("'isnan()' accepts dimensioned arguments") { REQUIRE(!isnan(4.0 * isq::length[m])); }
TEST_CASE("'pow<Num, Den>()' on quantity changes the value and the dimension accordingly", "[math][pow]") SECTION("'pow<Num, Den>()' on quantity changes the value and the dimension accordingly")
{ {
REQUIRE(pow<1, 4>(16 * isq::area[m2]) == sqrt(4 * isq::length[m])); REQUIRE(pow<1, 4>(16 * isq::area[m2]) == sqrt(4 * isq::length[m]));
} }
// TODO add tests for exp() // TODO add tests for exp()
TEST_CASE("absolute functions on quantity returns the absolute value", "[math][abs][fabs]") SECTION("absolute functions on quantity returns the absolute value")
{ {
SECTION("'abs()' on a negative quantity returns the abs") SECTION("'abs()' on a negative quantity returns the abs")
{ {
SECTION("integral representation") { REQUIRE(abs(-1 * isq::length[m]) == 1 * isq::length[m]); } SECTION("integral representation") { REQUIRE(abs(-1 * isq::length[m]) == 1 * isq::length[m]); }
@ -141,10 +143,10 @@ TEST_CASE("absolute functions on quantity returns the absolute value", "[math][a
SECTION("floating-point representation") { REQUIRE(abs(1. * isq::length[m]) == 1 * isq::length[m]); } SECTION("floating-point representation") { REQUIRE(abs(1. * isq::length[m]) == 1 * isq::length[m]); }
} }
} }
TEST_CASE("numeric_limits functions", "[limits]") SECTION("numeric_limits functions")
{ {
SECTION("'epsilon' works as expected using default floating type") SECTION("'epsilon' works as expected using default floating type")
{ {
REQUIRE(epsilon<double>(isq::length[m]).numerical_value_in(m) == REQUIRE(epsilon<double>(isq::length[m]).numerical_value_in(m) ==
@ -155,10 +157,10 @@ TEST_CASE("numeric_limits functions", "[limits]")
REQUIRE(epsilon<int>(isq::length[m]).numerical_value_in(m) == REQUIRE(epsilon<int>(isq::length[m]).numerical_value_in(m) ==
std::numeric_limits<decltype(1 * isq::length[m])::rep>::epsilon()); std::numeric_limits<decltype(1 * isq::length[m])::rep>::epsilon());
} }
} }
TEST_CASE("floor functions", "[floor]") SECTION("floor functions")
{ {
SECTION("floor 1 second with target unit second should be 1 second") SECTION("floor 1 second with target unit second should be 1 second")
{ {
REQUIRE(floor<si::second>(1 * isq::time[s]) == 1 * isq::time[s]); REQUIRE(floor<si::second>(1 * isq::time[s]) == 1 * isq::time[s]);
@ -209,10 +211,10 @@ TEST_CASE("floor functions", "[floor]")
} }
// TODO Add tests for `N`, `kN` and `kg * m / s2` i `kg * km / s2` // TODO Add tests for `N`, `kN` and `kg * m / s2` i `kg * km / s2`
} }
TEST_CASE("ceil functions", "[ceil]") SECTION("ceil functions")
{ {
SECTION("ceil 1 second with target unit second should be 1 second") SECTION("ceil 1 second with target unit second should be 1 second")
{ {
REQUIRE(ceil<si::second>(1 * isq::time[s]) == 1 * isq::time[s]); REQUIRE(ceil<si::second>(1 * isq::time[s]) == 1 * isq::time[s]);
@ -261,10 +263,10 @@ TEST_CASE("ceil functions", "[ceil]")
{ {
REQUIRE(ceil<si::second>(-999. * isq::time[ms]) == 0 * isq::time[s]); REQUIRE(ceil<si::second>(-999. * isq::time[ms]) == 0 * isq::time[s]);
} }
} }
TEST_CASE("round functions", "[round]") SECTION("round functions")
{ {
SECTION("round 1 second with target unit second should be 1 second") SECTION("round 1 second with target unit second should be 1 second")
{ {
REQUIRE(round<si::second>(1 * isq::time[s]) == 1 * isq::time[s]); REQUIRE(round<si::second>(1 * isq::time[s]) == 1 * isq::time[s]);
@ -349,10 +351,10 @@ TEST_CASE("round functions", "[round]")
{ {
REQUIRE(round<si::second>(-1999. * isq::time[ms]) == -2 * isq::time[s]); REQUIRE(round<si::second>(-1999. * isq::time[ms]) == -2 * isq::time[s]);
} }
} }
TEST_CASE("hypot functions", "[hypot]") SECTION("hypot functions")
{ {
SECTION("hypot should work on the same quantities") SECTION("hypot should work on the same quantities")
{ {
REQUIRE(hypot(3. * isq::length[km], 4. * isq::length[km]) == 5. * isq::length[km]); REQUIRE(hypot(3. * isq::length[km], 4. * isq::length[km]) == 5. * isq::length[km]);
@ -363,10 +365,10 @@ TEST_CASE("hypot functions", "[hypot]")
REQUIRE(hypot(3. * isq::length[km], 4000. * isq::length[m]) == 5. * isq::length[km]); REQUIRE(hypot(3. * isq::length[km], 4000. * isq::length[m]) == 5. * isq::length[km]);
REQUIRE(hypot(2. * isq::length[km], 3000. * isq::length[m], 6. * isq::length[km]) == 7. * isq::length[km]); REQUIRE(hypot(2. * isq::length[km], 3000. * isq::length[m], 6. * isq::length[km]) == 7. * isq::length[km]);
} }
} }
TEST_CASE("SI trigonometric functions", "[trig][si]") SECTION("SI trigonometric functions")
{ {
SECTION("sin") SECTION("sin")
{ {
REQUIRE_THAT(si::sin(0 * deg), AlmostEquals(0. * one)); REQUIRE_THAT(si::sin(0 * deg), AlmostEquals(0. * one));
@ -390,10 +392,10 @@ TEST_CASE("SI trigonometric functions", "[trig][si]")
REQUIRE_THAT(si::tan(135. * deg), AlmostEquals(-1. * one)); REQUIRE_THAT(si::tan(135. * deg), AlmostEquals(-1. * one));
REQUIRE_THAT(si::tan(180. * deg), AlmostEquals(0. * one)); REQUIRE_THAT(si::tan(180. * deg), AlmostEquals(0. * one));
} }
} }
TEST_CASE("SI inverse trigonometric functions", "[inv trig][si]") SECTION("SI inverse trigonometric functions")
{ {
SECTION("asin") SECTION("asin")
{ {
REQUIRE_THAT(si::asin(-1 * one), AlmostEquals(-90. * deg)); REQUIRE_THAT(si::asin(-1 * one), AlmostEquals(-90. * deg));
@ -414,10 +416,10 @@ TEST_CASE("SI inverse trigonometric functions", "[inv trig][si]")
REQUIRE_THAT(si::atan(0 * one), AlmostEquals(0. * deg)); REQUIRE_THAT(si::atan(0 * one), AlmostEquals(0. * deg));
REQUIRE_THAT(si::atan(1 * one), AlmostEquals(45. * deg)); REQUIRE_THAT(si::atan(1 * one), AlmostEquals(45. * deg));
} }
} }
TEST_CASE("SI atan2 functions", "[atan2][si]") SECTION("SI atan2 functions")
{ {
SECTION("atan2 should work on the same quantities") SECTION("atan2 should work on the same quantities")
{ {
REQUIRE_THAT(si::atan2(-1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(-45. * deg)); REQUIRE_THAT(si::atan2(-1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(-45. * deg));
@ -430,11 +432,10 @@ TEST_CASE("SI atan2 functions", "[atan2][si]")
REQUIRE_THAT(si::atan2(0. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(0. * deg)); REQUIRE_THAT(si::atan2(0. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(0. * deg));
REQUIRE_THAT(si::atan2(1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(45. * deg)); REQUIRE_THAT(si::atan2(1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(45. * deg));
} }
} }
SECTION("Angle trigonometric functions")
TEST_CASE("Angle trigonometric functions", "[trig][angle]") {
{
using namespace mp_units::angular; using namespace mp_units::angular;
using namespace mp_units::angular::unit_symbols; using namespace mp_units::angular::unit_symbols;
using mp_units::angular::unit_symbols::deg; using mp_units::angular::unit_symbols::deg;
@ -477,10 +478,10 @@ TEST_CASE("Angle trigonometric functions", "[trig][angle]")
REQUIRE_THAT(tan(150 * angle[grad]), AlmostEquals(-1. * one)); REQUIRE_THAT(tan(150 * angle[grad]), AlmostEquals(-1. * one));
REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one, 2)); REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one, 2));
} }
} }
TEST_CASE("Angle inverse trigonometric functions", "[inv trig][angle]") SECTION("Angle inverse trigonometric functions")
{ {
using namespace mp_units::angular; using namespace mp_units::angular;
using namespace mp_units::angular::unit_symbols; using namespace mp_units::angular::unit_symbols;
using mp_units::angular::unit_symbols::deg; using mp_units::angular::unit_symbols::deg;
@ -505,10 +506,10 @@ TEST_CASE("Angle inverse trigonometric functions", "[inv trig][angle]")
REQUIRE_THAT(atan(0 * one), AlmostEquals(0. * angle[deg])); REQUIRE_THAT(atan(0 * one), AlmostEquals(0. * angle[deg]));
REQUIRE_THAT(atan(1 * one), AlmostEquals(45. * angle[deg])); REQUIRE_THAT(atan(1 * one), AlmostEquals(45. * angle[deg]));
} }
} }
TEST_CASE("Angle atan2 functions", "[atan2][angle]") SECTION("Angle atan2 functions")
{ {
using namespace mp_units::angular; using namespace mp_units::angular;
using namespace mp_units::angular::unit_symbols; using namespace mp_units::angular::unit_symbols;
using mp_units::angular::unit_symbols::deg; using mp_units::angular::unit_symbols::deg;
@ -525,4 +526,5 @@ TEST_CASE("Angle atan2 functions", "[atan2][angle]")
REQUIRE_THAT(atan2(0. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(0. * angle[deg])); REQUIRE_THAT(atan2(0. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(0. * angle[deg]));
REQUIRE_THAT(atan2(1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(45. * angle[deg])); REQUIRE_THAT(atan2(1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(45. * angle[deg]));
} }
}
} }

View File

@ -60,22 +60,25 @@ constexpr bool within_4_ulps(T a, T b)
} // namespace } // namespace
// conversion requiring radical magnitudes TEST_CASE("quantity operations", "[quantity]")
TEST_CASE("unit conversions support radical magnitudes", "[conversion][radical]")
{ {
// conversion requiring radical magnitudes
SECTION("unit conversions support radical magnitudes")
{
REQUIRE(within_4_ulps(sqrt((1.0 * m) * (1.0 * km)).numerical_value_in(m), sqrt(1000.0))); REQUIRE(within_4_ulps(sqrt((1.0 * m) * (1.0 * km)).numerical_value_in(m), sqrt(1000.0)));
} }
// Reproducing issue #474 exactly: // Reproducing issue #474 exactly:
TEST_CASE("Issue 474 is fixed", "[conversion][radical]") SECTION("Issue 474 is fixed")
{ {
constexpr auto val_issue_474 = 8.0 * si::si2019::boltzmann_constant * 1000.0 * K / (std::numbers::pi * 10 * Da); constexpr auto val_issue_474 = 8.0 * si::si2019::boltzmann_constant * 1000.0 * K / (std::numbers::pi * 10 * Da);
REQUIRE(within_4_ulps(sqrt(val_issue_474).numerical_value_in(m / s), REQUIRE(within_4_ulps(sqrt(val_issue_474).numerical_value_in(m / s),
sqrt(val_issue_474.numerical_value_in(m * m / s / s)))); sqrt(val_issue_474.numerical_value_in(m * m / s / s))));
} }
TEST_CASE("Volatile representation type", "[volatile]") SECTION("Volatile representation type")
{ {
volatile std::int16_t vint = 123; volatile std::int16_t vint = 123;
REQUIRE(quantity(vint * m).numerical_value_in(m) == 123); REQUIRE(quantity(vint * m).numerical_value_in(m) == 123);
}
} }

View File

@ -25,24 +25,28 @@
#include <mp-units/systems/isq/mechanics.h> #include <mp-units/systems/isq/mechanics.h>
#include <mp-units/systems/isq/space_and_time.h> #include <mp-units/systems/isq/space_and_time.h>
#include <mp-units/systems/si/units.h> #include <mp-units/systems/si/units.h>
#if MP_UNITS_HOSTED
template<class T> #include <mp-units/cartesian_vector.h>
requires mp_units::is_scalar<T> #endif
constexpr bool mp_units::is_vector<T> = true;
namespace { namespace {
using namespace mp_units; using namespace mp_units;
using namespace mp_units::cgs; using namespace mp_units::cgs;
using namespace mp_units::cgs::unit_symbols; using namespace mp_units::cgs::unit_symbols;
#if MP_UNITS_HOSTED
using v = cartesian_vector<double>;
#endif
// https://en.wikipedia.org/wiki/Centimetre%E2%80%93gram%E2%80%93second_system_of_units#Definitions_and_conversion_factors_of_CGS_units_in_mechanics // https://en.wikipedia.org/wiki/Centimetre%E2%80%93gram%E2%80%93second_system_of_units#Definitions_and_conversion_factors_of_CGS_units_in_mechanics
static_assert(isq::length(100 * cm) == isq::length(1 * si::metre)); static_assert(isq::length(100 * cm) == isq::length(1 * si::metre));
static_assert(isq::mass(1000 * g) == isq::mass(1 * si::kilogram)); static_assert(isq::mass(1000 * g) == isq::mass(1 * si::kilogram));
static_assert(isq::time(1 * s) == isq::time(1 * si::second)); static_assert(isq::time(1 * s) == isq::time(1 * si::second));
static_assert(isq::speed(100 * cm / s) == isq::speed(1 * si::metre / si::second)); static_assert(isq::speed(100 * cm / s) == isq::speed(1 * si::metre / si::second));
static_assert(isq::acceleration(100 * Gal) == isq::acceleration(1 * si::metre / square(si::second))); #if MP_UNITS_HOSTED
static_assert(isq::force(100'000 * dyn) == isq::force(1 * si::newton)); static_assert(isq::acceleration(v{100} * Gal) == isq::acceleration(v{1} * si::metre / square(si::second)));
static_assert(isq::force(v{100'000} * dyn) == isq::force(v{1} * si::newton));
#endif
static_assert(isq::energy(10'000'000 * erg) == isq::energy(1 * si::joule)); static_assert(isq::energy(10'000'000 * erg) == isq::energy(1 * si::joule));
static_assert(isq::power(10'000'000 * erg / s) == isq::power(1 * si::watt)); static_assert(isq::power(10'000'000 * erg / s) == isq::power(1 * si::watt));
static_assert(isq::pressure(10 * Ba) == isq::pressure(1 * si::pascal)); static_assert(isq::pressure(10 * Ba) == isq::pressure(1 * si::pascal));

View File

@ -237,17 +237,17 @@ static_assert(compare(round<si::second>(-1999. * isq::time[ms]), -2. * isq::time
#endif #endif
// non-truncating // non-truncating
static_assert(compare(inverse<us>(250 * Hz), 4000 * us)); static_assert(compare(kind_of<isq::time>(inverse<us>(250 * Hz)), 4000 * us));
static_assert(compare(inverse<us>(250 * kHz), 4 * us)); static_assert(compare(kind_of<isq::time>(inverse<us>(250 * kHz)), 4 * us));
static_assert(compare(inverse<ks>(250 * uHz), 4 * ks)); static_assert(compare(kind_of<isq::time>(inverse<ks>(250 * uHz)), 4 * ks));
// truncating // truncating
static_assert(compare(inverse<s>(1 * kHz), 0 * s)); static_assert(compare(kind_of<isq::time>(inverse<s>(1 * kHz)), 0 * s));
// floating-point representation does not truncate // floating-point representation does not truncate
static_assert(compare(inverse<s>(1. * kHz), 0.001 * s)); static_assert(compare(kind_of<isq::time>(inverse<s>(1. * kHz)), 0.001 * s));
// check if constraints work properly for a derived unit of a narrowed kind // check if constraints work properly for a derived unit of a narrowed kind
static_assert(compare(inverse<Hz>(1 * s), 1 * Hz)); static_assert(compare(kind_of<isq::frequency>(inverse<Hz>(1 * s)), 1 * Hz));
} // namespace } // namespace