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

@ -386,7 +386,7 @@ the same kind.
We can try to do this by ourselves, but it is tough. Probably no one, for sure we are We can try to do this by ourselves, but it is tough. Probably no one, for sure we are
not, is an expert in all the fields of ISO/IEC 80000 applicability. not, is an expert in all the fields of ISO/IEC 80000 applicability.
We need the help of subject matter experts who will help us build those trees for their domains We need the help of subject matter experts who will help us build those trees for their domains
and then verify that everything works as expected. and then verify that everything works as expected.
@ -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?
@ -490,5 +526,5 @@ a daily basis.
I hope you enjoyed following this series and learned more about the International System I hope you enjoyed following this series and learned more about the International System
of Quantities. Please try it out in your domain and share feedback with us. We always love to of Quantities. Please try it out in your domain and share feedback with us. We always love to
hear about the projects in which our library is being used and about use cases it helps hear about the projects in which our library is being used and about use cases it helps
address. address.

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()); }
}
}

File diff suppressed because it is too large Load Diff

View File

@ -38,19 +38,22 @@ 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]")
{ {
basic_fixed_string txt = "abc"; SECTION("fixed_string::at")
SECTION("in range")
{ {
CHECK(txt.at(0) == 'a'); basic_fixed_string txt = "abc";
CHECK(txt.at(1) == 'b'); SECTION("in range")
CHECK(txt.at(2) == 'c'); {
} CHECK(txt.at(0) == 'a');
SECTION("out of range") CHECK(txt.at(1) == 'b');
{ CHECK(txt.at(2) == 'c');
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")); SECTION("out of range")
{
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"));
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -43,486 +43,488 @@ 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<0>(q)' returns '1'") { CHECK(pow<0>(2 * isq::length[m]) == 1 * one); } SECTION("'pow<N>()' on quantity changes the value and the dimension accordingly")
SECTION("'pow<1>(q)' returns 'q'") { CHECK(pow<1>(2 * isq::length[m]) == 2 * isq::length[m]); }
SECTION("'pow<2>(q)' squares both the value and a dimension")
{ {
CHECK(pow<2>(2 * isq::length[m]) == 4 * isq::area[m2]); 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<2>(q)' squares both the value and a dimension")
{
CHECK(pow<2>(2 * isq::length[m]) == 4 * isq::area[m2]);
}
SECTION("'pow<3>(q)' cubes both the value and a dimension")
{
CHECK(pow<3>(2 * isq::length[m]) == 8 * isq::volume[m3]);
}
} }
SECTION("'pow<3>(q)' cubes both the value and a dimension") SECTION("'sqrt()' on quantity changes the value and the dimension accordingly")
{ {
CHECK(pow<3>(2 * isq::length[m]) == 8 * isq::volume[m3]); REQUIRE(sqrt(4 * isq::area[m2]) == 2 * isq::length[m]);
} }
}
SECTION("'cbrt()' on quantity changes the value and the dimension accordingly")
TEST_CASE("'sqrt()' on quantity changes the value and the dimension accordingly", "[math][sqrt]") {
{ REQUIRE(cbrt(8 * isq::volume[m3]) == 2 * isq::length[m]);
REQUIRE(sqrt(4 * isq::area[m2]) == 2 * isq::length[m]); }
}
SECTION("'fma()' on quantity changes the value and the dimension accordingly")
TEST_CASE("'cbrt()' on quantity changes the value and the dimension accordingly", "[math][cbrt]") {
{ REQUIRE(fma(1.0 * isq::length[m], 2.0 * one, 2.0 * isq::length[m]) == 4.0 * isq::length[m]);
REQUIRE(cbrt(8 * isq::volume[m3]) == 2 * 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));
} }
TEST_CASE("'fma()' on quantity changes the value and the dimension accordingly", "[math][fma]") SECTION("fmod functions")
{ {
REQUIRE(fma(1.0 * isq::length[m], 2.0 * one, 2.0 * isq::length[m]) == 4.0 * isq::length[m]); SECTION("fmod should work on the same quantities")
REQUIRE(fma(isq::speed(10.0 * m / s), isq::time(2.0 * s), isq::height(42.0 * m)) == isq::length(62.0 * m)); {
} REQUIRE(fmod(4. * isq::length[km], 3. * isq::length[km]) == 1. * isq::length[km]);
REQUIRE(fmod(-9. * isq::length[km], 3. * isq::length[km]) == -0. * isq::length[km]);
TEST_CASE("fmod functions", "[math][fmod]") REQUIRE(fmod(3 * isq::length[km], 2 * isq::length[km]) == 1 * isq::length[km]);
{ REQUIRE(fmod(4 * isq::length[km], 2.5f * isq::length[km]) == 1.5 * isq::length[km]);
SECTION("fmod should work on the same quantities") }
{ SECTION("fmod should work with different units of the same dimension")
REQUIRE(fmod(4. * isq::length[km], 3. * isq::length[km]) == 1. * isq::length[km]); {
REQUIRE(fmod(-9. * isq::length[km], 3. * isq::length[km]) == -0. * isq::length[km]); REQUIRE(fmod(4. * isq::length[km], 3000. * isq::length[m]) == 1000. * isq::length[m]);
REQUIRE(fmod(3 * isq::length[km], 2 * isq::length[km]) == 1 * isq::length[km]); REQUIRE(fmod(-9. * isq::length[km], 3000. * isq::length[m]) == -0. * isq::length[m]);
REQUIRE(fmod(4 * isq::length[km], 2.5f * isq::length[km]) == 1.5 * isq::length[km]); 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]);
SECTION("fmod should work with different units of the same dimension") }
{ }
REQUIRE(fmod(4. * isq::length[km], 3000. * isq::length[m]) == 1000. * isq::length[m]);
REQUIRE(fmod(-9. * isq::length[km], 3000. * isq::length[m]) == -0. * isq::length[m]); SECTION("remainder functions")
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]); SECTION("remainder should work on the same quantities")
} {
} REQUIRE(remainder(4. * isq::length[km], 3. * isq::length[km]) == 1. * isq::length[km]);
REQUIRE(remainder(-9. * isq::length[km], 3. * isq::length[km]) == -0. * isq::length[km]);
TEST_CASE("remainder functions", "[math][remainder]") REQUIRE(remainder(3 * isq::length[km], 2 * isq::length[km]) == -1 * isq::length[km]);
{ REQUIRE(remainder(4 * isq::length[km], 2.75f * isq::length[km]) == 1.25 * isq::length[km]);
SECTION("remainder should work on the same quantities") }
{ SECTION("remainder should work with different units of the same dimension")
REQUIRE(remainder(4. * isq::length[km], 3. * isq::length[km]) == 1. * isq::length[km]); {
REQUIRE(remainder(-9. * isq::length[km], 3. * isq::length[km]) == -0. * isq::length[km]); REQUIRE(remainder(4. * isq::length[km], 3000. * isq::length[m]) == 1000. * isq::length[m]);
REQUIRE(remainder(3 * isq::length[km], 2 * isq::length[km]) == -1 * isq::length[km]); REQUIRE(remainder(-9. * isq::length[km], 3000. * isq::length[m]) == -0. * isq::length[m]);
REQUIRE(remainder(4 * isq::length[km], 2.75f * isq::length[km]) == 1.25 * isq::length[km]); 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]);
SECTION("remainder should work with different units of the same dimension") }
{ }
REQUIRE(remainder(4. * isq::length[km], 3000. * isq::length[m]) == 1000. * isq::length[m]);
REQUIRE(remainder(-9. * isq::length[km], 3000. * isq::length[m]) == -0. * isq::length[m]); SECTION("'isfinite()' accepts dimensioned arguments") { REQUIRE(isfinite(4.0 * 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]); SECTION("'isinf()' accepts dimensioned arguments") { REQUIRE(!isinf(4.0 * isq::length[m])); }
}
} SECTION("'isnan()' accepts dimensioned arguments") { REQUIRE(!isnan(4.0 * isq::length[m])); }
TEST_CASE("'isfinite()' accepts dimensioned arguments", "[math][isfinite]") { REQUIRE(isfinite(4.0 * isq::length[m])); }
SECTION("'pow<Num, Den>()' on quantity changes the value and the dimension accordingly")
TEST_CASE("'isinf()' accepts dimensioned arguments", "[math][isinf]") { REQUIRE(!isinf(4.0 * isq::length[m])); } {
REQUIRE(pow<1, 4>(16 * isq::area[m2]) == sqrt(4 * isq::length[m]));
TEST_CASE("'isnan()' accepts dimensioned arguments", "[math][isnan]") { REQUIRE(!isnan(4.0 * isq::length[m])); } }
// TODO add tests for exp()
TEST_CASE("'pow<Num, Den>()' on quantity changes the value and the dimension accordingly", "[math][pow]")
{ SECTION("absolute functions on quantity returns the absolute value")
REQUIRE(pow<1, 4>(16 * isq::area[m2]) == sqrt(4 * isq::length[m])); {
} SECTION("'abs()' on a negative quantity returns the abs")
{
// TODO add tests for exp() SECTION("integral representation") { REQUIRE(abs(-1 * isq::length[m]) == 1 * isq::length[m]); }
TEST_CASE("absolute functions on quantity returns the absolute value", "[math][abs][fabs]") SECTION("floating-point representation") { REQUIRE(abs(-1. * isq::length[m]) == 1 * isq::length[m]); }
{ }
SECTION("'abs()' on a negative quantity returns the abs")
{ SECTION("'abs()' on a positive 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]); }
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]); }
}
SECTION("'abs()' on a positive quantity returns the abs") }
{
SECTION("integral representation") { REQUIRE(abs(1 * isq::length[m]) == 1 * isq::length[m]); } SECTION("numeric_limits functions")
{
SECTION("floating-point representation") { REQUIRE(abs(1. * isq::length[m]) == 1 * isq::length[m]); } SECTION("'epsilon' works as expected using default floating type")
} {
} REQUIRE(epsilon<double>(isq::length[m]).numerical_value_in(m) ==
std::numeric_limits<decltype(1. * isq::length[m])::rep>::epsilon());
TEST_CASE("numeric_limits functions", "[limits]") }
{ SECTION("'epsilon' works as expected using integers")
SECTION("'epsilon' works as expected using default floating type") {
{ REQUIRE(epsilon<int>(isq::length[m]).numerical_value_in(m) ==
REQUIRE(epsilon<double>(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()); }
} }
SECTION("'epsilon' works as expected using integers")
{ SECTION("floor functions")
REQUIRE(epsilon<int>(isq::length[m]).numerical_value_in(m) == {
std::numeric_limits<decltype(1 * isq::length[m])::rep>::epsilon()); SECTION("floor 1 second with target unit second should be 1 second")
} {
} REQUIRE(floor<si::second>(1 * isq::time[s]) == 1 * isq::time[s]);
}
TEST_CASE("floor functions", "[floor]") SECTION("floor 1000 milliseconds with target unit second should be 1 second")
{ {
SECTION("floor 1 second with target unit second should be 1 second") REQUIRE(floor<si::second>(1000 * isq::time[ms]) == 1 * isq::time[s]);
{ }
REQUIRE(floor<si::second>(1 * isq::time[s]) == 1 * isq::time[s]); SECTION("floor 1001 milliseconds with target unit second should be 1 second")
} {
SECTION("floor 1000 milliseconds with target unit second should be 1 second") REQUIRE(floor<si::second>(1001 * isq::time[ms]) == 1 * isq::time[s]);
{ }
REQUIRE(floor<si::second>(1000 * isq::time[ms]) == 1 * isq::time[s]); SECTION("floor 1999 milliseconds with target unit second should be 1 second")
} {
SECTION("floor 1001 milliseconds with target unit second should be 1 second") REQUIRE(floor<si::second>(1999 * isq::time[ms]) == 1 * isq::time[s]);
{ }
REQUIRE(floor<si::second>(1001 * isq::time[ms]) == 1 * isq::time[s]); SECTION("floor -1000 milliseconds with target unit second should be -1 second")
} {
SECTION("floor 1999 milliseconds with target unit second should be 1 second") REQUIRE(floor<si::second>(-1000 * isq::time[ms]) == -1 * isq::time[s]);
{ }
REQUIRE(floor<si::second>(1999 * isq::time[ms]) == 1 * isq::time[s]); SECTION("floor -999 milliseconds with target unit second should be -1 second")
} {
SECTION("floor -1000 milliseconds with target unit second should be -1 second") REQUIRE(floor<si::second>(-999 * isq::time[ms]) == -1 * isq::time[s]);
{ }
REQUIRE(floor<si::second>(-1000 * isq::time[ms]) == -1 * isq::time[s]); SECTION("floor 1.3 seconds with target unit second should be 1 second")
} {
SECTION("floor -999 milliseconds with target unit second should be -1 second") REQUIRE(floor<si::second>(1.3 * isq::time[s]) == 1 * isq::time[s]);
{ }
REQUIRE(floor<si::second>(-999 * isq::time[ms]) == -1 * isq::time[s]); SECTION("floor -1.3 seconds with target unit second should be -2 seconds")
} {
SECTION("floor 1.3 seconds with target unit second should be 1 second") REQUIRE(floor<si::second>(-1.3 * isq::time[s]) == -2 * isq::time[s]);
{ }
REQUIRE(floor<si::second>(1.3 * isq::time[s]) == 1 * isq::time[s]); SECTION("floor 1001. milliseconds with target unit second should be 1 second")
} {
SECTION("floor -1.3 seconds with target unit second should be -2 seconds") REQUIRE(floor<si::second>(1001. * isq::time[ms]) == 1 * isq::time[s]);
{ }
REQUIRE(floor<si::second>(-1.3 * isq::time[s]) == -2 * isq::time[s]); SECTION("floor 1999. milliseconds with target unit second should be 1 second")
} {
SECTION("floor 1001. milliseconds with target unit second should be 1 second") REQUIRE(floor<si::second>(1999. * isq::time[ms]) == 1 * isq::time[s]);
{ }
REQUIRE(floor<si::second>(1001. * isq::time[ms]) == 1 * isq::time[s]); SECTION("floor -1000. milliseconds with target unit second should be -1 second")
} {
SECTION("floor 1999. milliseconds with target unit second should be 1 second") REQUIRE(floor<si::second>(-1000. * isq::time[ms]) == -1 * isq::time[s]);
{ }
REQUIRE(floor<si::second>(1999. * isq::time[ms]) == 1 * isq::time[s]); SECTION("floor -999. milliseconds with target unit second should be -1 second")
} {
SECTION("floor -1000. milliseconds with target unit second should be -1 second") REQUIRE(floor<si::second>(-999. * isq::time[ms]) == -1 * isq::time[s]);
{ }
REQUIRE(floor<si::second>(-1000. * isq::time[ms]) == -1 * isq::time[s]);
} // TODO Add tests for `N`, `kN` and `kg * m / s2` i `kg * km / s2`
SECTION("floor -999. milliseconds with target unit second should be -1 second") }
{
REQUIRE(floor<si::second>(-999. * isq::time[ms]) == -1 * isq::time[s]); SECTION("ceil functions")
} {
SECTION("ceil 1 second with target unit second should be 1 second")
// TODO Add tests for `N`, `kN` and `kg * m / s2` i `kg * km / s2` {
} REQUIRE(ceil<si::second>(1 * isq::time[s]) == 1 * isq::time[s]);
}
TEST_CASE("ceil functions", "[ceil]") SECTION("ceil 1000 milliseconds with target unit second should be 1 second")
{ {
SECTION("ceil 1 second with target unit second should be 1 second") REQUIRE(ceil<si::second>(1000 * isq::time[ms]) == 1 * isq::time[s]);
{ }
REQUIRE(ceil<si::second>(1 * isq::time[s]) == 1 * isq::time[s]); SECTION("ceil 1001 milliseconds with target unit second should be 2 seconds")
} {
SECTION("ceil 1000 milliseconds with target unit second should be 1 second") REQUIRE(ceil<si::second>(1001 * isq::time[ms]) == 2 * isq::time[s]);
{ }
REQUIRE(ceil<si::second>(1000 * isq::time[ms]) == 1 * isq::time[s]); SECTION("ceil 1999 milliseconds with target unit second should be 2 seconds")
} {
SECTION("ceil 1001 milliseconds with target unit second should be 2 seconds") REQUIRE(ceil<si::second>(1999 * isq::time[ms]) == 2 * isq::time[s]);
{ }
REQUIRE(ceil<si::second>(1001 * isq::time[ms]) == 2 * isq::time[s]); SECTION("ceil -1000 milliseconds with target unit second should be -1 second")
} {
SECTION("ceil 1999 milliseconds with target unit second should be 2 seconds") REQUIRE(ceil<si::second>(-1000 * isq::time[ms]) == -1 * isq::time[s]);
{ }
REQUIRE(ceil<si::second>(1999 * isq::time[ms]) == 2 * isq::time[s]); SECTION("ceil -999 milliseconds with target unit second should be 0 seconds")
} {
SECTION("ceil -1000 milliseconds with target unit second should be -1 second") REQUIRE(ceil<si::second>(-999 * isq::time[ms]) == 0 * isq::time[s]);
{ }
REQUIRE(ceil<si::second>(-1000 * isq::time[ms]) == -1 * isq::time[s]); SECTION("ceil 1.3 seconds with target unit second should be 2 seconds")
} {
SECTION("ceil -999 milliseconds with target unit second should be 0 seconds") REQUIRE(ceil<si::second>(1.3 * isq::time[s]) == 2 * isq::time[s]);
{ }
REQUIRE(ceil<si::second>(-999 * isq::time[ms]) == 0 * isq::time[s]); SECTION("ceil -1.3 seconds with target unit second should be -1 second")
} {
SECTION("ceil 1.3 seconds with target unit second should be 2 seconds") REQUIRE(ceil<si::second>(-1.3 * isq::time[s]) == -1 * isq::time[s]);
{ }
REQUIRE(ceil<si::second>(1.3 * isq::time[s]) == 2 * isq::time[s]); SECTION("ceil 1001. milliseconds with target unit second should be 2 seconds")
} {
SECTION("ceil -1.3 seconds with target unit second should be -1 second") REQUIRE(ceil<si::second>(1001. * isq::time[ms]) == 2 * isq::time[s]);
{ }
REQUIRE(ceil<si::second>(-1.3 * isq::time[s]) == -1 * isq::time[s]); SECTION("ceil 1999. milliseconds with target unit second should be 2 seconds")
} {
SECTION("ceil 1001. milliseconds with target unit second should be 2 seconds") REQUIRE(ceil<si::second>(1999. * isq::time[ms]) == 2 * isq::time[s]);
{ }
REQUIRE(ceil<si::second>(1001. * isq::time[ms]) == 2 * isq::time[s]); SECTION("ceil -1000. milliseconds with target unit second should be -1 second")
} {
SECTION("ceil 1999. milliseconds with target unit second should be 2 seconds") REQUIRE(ceil<si::second>(-1000. * isq::time[ms]) == -1 * isq::time[s]);
{ }
REQUIRE(ceil<si::second>(1999. * isq::time[ms]) == 2 * isq::time[s]); SECTION("ceil -999. milliseconds with target unit second should be 0 seconds")
} {
SECTION("ceil -1000. milliseconds with target unit second should be -1 second") REQUIRE(ceil<si::second>(-999. * isq::time[ms]) == 0 * isq::time[s]);
{ }
REQUIRE(ceil<si::second>(-1000. * isq::time[ms]) == -1 * isq::time[s]); }
}
SECTION("ceil -999. milliseconds with target unit second should be 0 seconds") SECTION("round functions")
{ {
REQUIRE(ceil<si::second>(-999. * isq::time[ms]) == 0 * isq::time[s]); SECTION("round 1 second with target unit second should be 1 second")
} {
} REQUIRE(round<si::second>(1 * isq::time[s]) == 1 * isq::time[s]);
}
TEST_CASE("round functions", "[round]") SECTION("round 1000 milliseconds with target unit second should be 1 second")
{ {
SECTION("round 1 second with target unit second should be 1 second") REQUIRE(round<si::second>(1000 * isq::time[ms]) == 1 * isq::time[s]);
{ }
REQUIRE(round<si::second>(1 * isq::time[s]) == 1 * isq::time[s]); SECTION("round 1001 milliseconds with target unit second should be 1 second")
} {
SECTION("round 1000 milliseconds with target unit second should be 1 second") REQUIRE(round<si::second>(1001 * isq::time[ms]) == 1 * isq::time[s]);
{ }
REQUIRE(round<si::second>(1000 * isq::time[ms]) == 1 * isq::time[s]); SECTION("round 1499 milliseconds with target unit second should be 1 second")
} {
SECTION("round 1001 milliseconds with target unit second should be 1 second") REQUIRE(round<si::second>(1499 * isq::time[ms]) == 1 * isq::time[s]);
{ }
REQUIRE(round<si::second>(1001 * isq::time[ms]) == 1 * isq::time[s]); SECTION("round 1500 milliseconds with target unit second should be 2 seconds")
} {
SECTION("round 1499 milliseconds with target unit second should be 1 second") REQUIRE(round<si::second>(1500 * isq::time[ms]) == 2 * isq::time[s]);
{ }
REQUIRE(round<si::second>(1499 * isq::time[ms]) == 1 * isq::time[s]); SECTION("round 1999 milliseconds with target unit second should be 2 seconds")
} {
SECTION("round 1500 milliseconds with target unit second should be 2 seconds") REQUIRE(round<si::second>(1999 * isq::time[ms]) == 2 * isq::time[s]);
{ }
REQUIRE(round<si::second>(1500 * isq::time[ms]) == 2 * isq::time[s]); SECTION("round -1000 milliseconds with target unit second should be -1 second")
} {
SECTION("round 1999 milliseconds with target unit second should be 2 seconds") REQUIRE(round<si::second>(-1000 * isq::time[ms]) == -1 * isq::time[s]);
{ }
REQUIRE(round<si::second>(1999 * isq::time[ms]) == 2 * isq::time[s]); SECTION("round -1001 milliseconds with target unit second should be -1 second")
} {
SECTION("round -1000 milliseconds with target unit second should be -1 second") REQUIRE(round<si::second>(-1001 * isq::time[ms]) == -1 * isq::time[s]);
{ }
REQUIRE(round<si::second>(-1000 * isq::time[ms]) == -1 * isq::time[s]); SECTION("round -1499 milliseconds with target unit second should be -1 second")
} {
SECTION("round -1001 milliseconds with target unit second should be -1 second") REQUIRE(round<si::second>(-1499 * isq::time[ms]) == -1 * isq::time[s]);
{ }
REQUIRE(round<si::second>(-1001 * isq::time[ms]) == -1 * isq::time[s]); SECTION("round -1500 milliseconds with target unit second should be -2 seconds")
} {
SECTION("round -1499 milliseconds with target unit second should be -1 second") REQUIRE(round<si::second>(-1500 * isq::time[ms]) == -2 * isq::time[s]);
{ }
REQUIRE(round<si::second>(-1499 * isq::time[ms]) == -1 * isq::time[s]); SECTION("round -1999 milliseconds with target unit second should be -2 seconds")
} {
SECTION("round -1500 milliseconds with target unit second should be -2 seconds") REQUIRE(round<si::second>(-1999 * isq::time[ms]) == -2 * isq::time[s]);
{ }
REQUIRE(round<si::second>(-1500 * isq::time[ms]) == -2 * isq::time[s]); SECTION("round 1000. milliseconds with target unit second should be 1 second")
} {
SECTION("round -1999 milliseconds with target unit second should be -2 seconds") REQUIRE(round<si::second>(1000. * isq::time[ms]) == 1 * isq::time[s]);
{ }
REQUIRE(round<si::second>(-1999 * isq::time[ms]) == -2 * isq::time[s]); SECTION("round 1001. milliseconds with target unit second should be 1 second")
} {
SECTION("round 1000. milliseconds with target unit second should be 1 second") REQUIRE(round<si::second>(1001. * isq::time[ms]) == 1 * isq::time[s]);
{ }
REQUIRE(round<si::second>(1000. * isq::time[ms]) == 1 * isq::time[s]); SECTION("round 1499. milliseconds with target unit second should be 1 second")
} {
SECTION("round 1001. milliseconds with target unit second should be 1 second") REQUIRE(round<si::second>(1499. * isq::time[ms]) == 1 * isq::time[s]);
{ }
REQUIRE(round<si::second>(1001. * isq::time[ms]) == 1 * isq::time[s]); SECTION("round 1500. milliseconds with target unit second should be 2 seconds")
} {
SECTION("round 1499. milliseconds with target unit second should be 1 second") REQUIRE(round<si::second>(1500. * isq::time[ms]) == 2 * isq::time[s]);
{ }
REQUIRE(round<si::second>(1499. * isq::time[ms]) == 1 * isq::time[s]); SECTION("round 1999. milliseconds with target unit second should be 2 seconds")
} {
SECTION("round 1500. milliseconds with target unit second should be 2 seconds") REQUIRE(round<si::second>(1999. * isq::time[ms]) == 2 * isq::time[s]);
{ }
REQUIRE(round<si::second>(1500. * isq::time[ms]) == 2 * isq::time[s]); SECTION("round -1000. milliseconds with target unit second should be -1 second")
} {
SECTION("round 1999. milliseconds with target unit second should be 2 seconds") REQUIRE(round<si::second>(-1000. * isq::time[ms]) == -1 * isq::time[s]);
{ }
REQUIRE(round<si::second>(1999. * isq::time[ms]) == 2 * isq::time[s]); SECTION("round -1001. milliseconds with target unit second should be -1 second")
} {
SECTION("round -1000. milliseconds with target unit second should be -1 second") REQUIRE(round<si::second>(-1001. * isq::time[ms]) == -1 * isq::time[s]);
{ }
REQUIRE(round<si::second>(-1000. * isq::time[ms]) == -1 * isq::time[s]); SECTION("round -1499. milliseconds with target unit second should be -1 second")
} {
SECTION("round -1001. milliseconds with target unit second should be -1 second") REQUIRE(round<si::second>(-1499. * isq::time[ms]) == -1 * isq::time[s]);
{ }
REQUIRE(round<si::second>(-1001. * isq::time[ms]) == -1 * isq::time[s]); SECTION("round -1500. milliseconds with target unit second should be -2 seconds")
} {
SECTION("round -1499. milliseconds with target unit second should be -1 second") REQUIRE(round<si::second>(-1500. * isq::time[ms]) == -2 * isq::time[s]);
{ }
REQUIRE(round<si::second>(-1499. * isq::time[ms]) == -1 * isq::time[s]); SECTION("round -1999. milliseconds with target unit second should be -2 seconds")
} {
SECTION("round -1500. milliseconds with target unit second should be -2 seconds") REQUIRE(round<si::second>(-1999. * isq::time[ms]) == -2 * isq::time[s]);
{ }
REQUIRE(round<si::second>(-1500. * isq::time[ms]) == -2 * isq::time[s]); }
}
SECTION("round -1999. milliseconds with target unit second should be -2 seconds") SECTION("hypot functions")
{ {
REQUIRE(round<si::second>(-1999. * isq::time[ms]) == -2 * isq::time[s]); SECTION("hypot should work on the same quantities")
} {
} REQUIRE(hypot(3. * isq::length[km], 4. * isq::length[km]) == 5. * isq::length[km]);
REQUIRE(hypot(2. * isq::length[km], 3. * isq::length[km], 6. * isq::length[km]) == 7. * isq::length[km]);
TEST_CASE("hypot functions", "[hypot]") }
{ SECTION("hypot should work with different units of the same dimension")
SECTION("hypot should work on the same quantities") {
{ REQUIRE(hypot(3. * isq::length[km], 4000. * isq::length[m]) == 5. * isq::length[km]);
REQUIRE(hypot(3. * isq::length[km], 4. * isq::length[km]) == 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], 3. * isq::length[km], 6. * isq::length[km]) == 7. * isq::length[km]); }
} }
SECTION("hypot should work with different units of the same dimension")
{ SECTION("SI trigonometric functions")
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]); SECTION("sin")
} {
} REQUIRE_THAT(si::sin(0 * deg), AlmostEquals(0. * one));
REQUIRE_THAT(si::sin(90 * deg), AlmostEquals(1. * one));
TEST_CASE("SI trigonometric functions", "[trig][si]") REQUIRE_THAT(si::sin(180 * deg), AlmostEquals(0. * one));
{ REQUIRE_THAT(si::sin(270 * deg), AlmostEquals(-1. * one));
SECTION("sin") }
{
REQUIRE_THAT(si::sin(0 * deg), AlmostEquals(0. * one)); SECTION("cos")
REQUIRE_THAT(si::sin(90 * deg), AlmostEquals(1. * one)); {
REQUIRE_THAT(si::sin(180 * deg), AlmostEquals(0. * one)); REQUIRE_THAT(si::cos(0 * deg), AlmostEquals(1. * one));
REQUIRE_THAT(si::sin(270 * deg), AlmostEquals(-1. * one)); REQUIRE_THAT(si::cos(90 * deg), AlmostEquals(0. * one));
} REQUIRE_THAT(si::cos(180 * deg), AlmostEquals(-1. * one));
REQUIRE_THAT(si::cos(270 * deg), AlmostEquals(0. * one));
SECTION("cos") }
{
REQUIRE_THAT(si::cos(0 * deg), AlmostEquals(1. * one)); SECTION("tan")
REQUIRE_THAT(si::cos(90 * deg), AlmostEquals(0. * one)); {
REQUIRE_THAT(si::cos(180 * deg), AlmostEquals(-1. * one)); REQUIRE_THAT(si::tan(0 * deg), AlmostEquals(0. * one));
REQUIRE_THAT(si::cos(270 * deg), AlmostEquals(0. * one)); REQUIRE_THAT(si::tan(45. * deg), AlmostEquals(1. * one));
} REQUIRE_THAT(si::tan(135. * deg), AlmostEquals(-1. * one));
REQUIRE_THAT(si::tan(180. * deg), AlmostEquals(0. * one));
SECTION("tan") }
{ }
REQUIRE_THAT(si::tan(0 * deg), AlmostEquals(0. * one));
REQUIRE_THAT(si::tan(45. * deg), AlmostEquals(1. * one)); SECTION("SI inverse trigonometric functions")
REQUIRE_THAT(si::tan(135. * deg), AlmostEquals(-1. * one)); {
REQUIRE_THAT(si::tan(180. * deg), AlmostEquals(0. * one)); SECTION("asin")
} {
} REQUIRE_THAT(si::asin(-1 * one), AlmostEquals(-90. * deg));
REQUIRE_THAT(si::asin(0 * one), AlmostEquals(0. * deg));
TEST_CASE("SI inverse trigonometric functions", "[inv trig][si]") REQUIRE_THAT(si::asin(1 * one), AlmostEquals(90. * deg));
{ }
SECTION("asin")
{ SECTION("acos")
REQUIRE_THAT(si::asin(-1 * one), AlmostEquals(-90. * deg)); {
REQUIRE_THAT(si::asin(0 * one), AlmostEquals(0. * deg)); REQUIRE_THAT(si::asin(-1 * one), AlmostEquals(-90. * deg));
REQUIRE_THAT(si::asin(1 * one), AlmostEquals(90. * deg)); REQUIRE_THAT(si::asin(0 * one), AlmostEquals(0. * deg));
} REQUIRE_THAT(si::asin(1 * one), AlmostEquals(90. * deg));
}
SECTION("acos")
{ SECTION("atan")
REQUIRE_THAT(si::asin(-1 * one), AlmostEquals(-90. * deg)); {
REQUIRE_THAT(si::asin(0 * one), AlmostEquals(0. * deg)); REQUIRE_THAT(si::atan(-1 * one), AlmostEquals(-45. * deg));
REQUIRE_THAT(si::asin(1 * one), AlmostEquals(90. * deg)); REQUIRE_THAT(si::atan(0 * one), AlmostEquals(0. * deg));
} REQUIRE_THAT(si::atan(1 * one), AlmostEquals(45. * deg));
}
SECTION("atan") }
{
REQUIRE_THAT(si::atan(-1 * one), AlmostEquals(-45. * deg)); SECTION("SI atan2 functions")
REQUIRE_THAT(si::atan(0 * one), AlmostEquals(0. * deg)); {
REQUIRE_THAT(si::atan(1 * one), AlmostEquals(45. * deg)); 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(0. * isq::length[km], 1. * isq::length[km]), AlmostEquals(0. * deg));
TEST_CASE("SI atan2 functions", "[atan2][si]") REQUIRE_THAT(si::atan2(1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(45. * deg));
{ }
SECTION("atan2 should work on the same quantities") SECTION("atan2 should work with different units of the same dimension")
{ {
REQUIRE_THAT(si::atan2(-1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(-45. * deg)); REQUIRE_THAT(si::atan2(-1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(-45. * deg));
REQUIRE_THAT(si::atan2(0. * isq::length[km], 1. * isq::length[km]), 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], 1. * isq::length[km]), AlmostEquals(45. * deg)); REQUIRE_THAT(si::atan2(1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(45. * deg));
} }
SECTION("atan2 should work with different units of the same dimension") }
{
REQUIRE_THAT(si::atan2(-1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(-45. * deg)); SECTION("Angle trigonometric functions")
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)); using namespace mp_units::angular;
} using namespace mp_units::angular::unit_symbols;
} using mp_units::angular::unit_symbols::deg;
SECTION("sin")
TEST_CASE("Angle trigonometric functions", "[trig][angle]") {
{ REQUIRE_THAT(sin(0 * angle[deg]), AlmostEquals(0. * one));
using namespace mp_units::angular; REQUIRE_THAT(sin(90 * angle[deg]), AlmostEquals(1. * one));
using namespace mp_units::angular::unit_symbols; REQUIRE_THAT(sin(180 * angle[deg]), AlmostEquals(0. * one));
using mp_units::angular::unit_symbols::deg; REQUIRE_THAT(sin(270 * angle[deg]), AlmostEquals(-1. * one));
SECTION("sin") REQUIRE_THAT(sin(0 * angle[grad]), AlmostEquals(0. * one));
{ REQUIRE_THAT(sin(100 * angle[grad]), AlmostEquals(1. * one));
REQUIRE_THAT(sin(0 * angle[deg]), AlmostEquals(0. * one)); REQUIRE_THAT(sin(200 * angle[grad]), AlmostEquals(0. * one, 2));
REQUIRE_THAT(sin(90 * angle[deg]), AlmostEquals(1. * one)); REQUIRE_THAT(sin(300 * angle[grad]), AlmostEquals(-1. * one));
REQUIRE_THAT(sin(180 * angle[deg]), AlmostEquals(0. * one)); }
REQUIRE_THAT(sin(270 * angle[deg]), AlmostEquals(-1. * one));
SECTION("cos")
REQUIRE_THAT(sin(0 * angle[grad]), AlmostEquals(0. * one)); {
REQUIRE_THAT(sin(100 * angle[grad]), AlmostEquals(1. * one)); REQUIRE_THAT(cos(0 * angle[deg]), AlmostEquals(1. * one));
REQUIRE_THAT(sin(200 * angle[grad]), AlmostEquals(0. * one, 2)); REQUIRE_THAT(cos(90 * angle[deg]), AlmostEquals(0. * one));
REQUIRE_THAT(sin(300 * angle[grad]), AlmostEquals(-1. * one)); REQUIRE_THAT(cos(180 * angle[deg]), AlmostEquals(-1. * one));
} REQUIRE_THAT(cos(270 * angle[deg]), AlmostEquals(0. * one));
SECTION("cos") REQUIRE_THAT(cos(0 * angle[grad]), AlmostEquals(1. * one));
{ REQUIRE_THAT(cos(100 * angle[grad]), AlmostEquals(0. * one));
REQUIRE_THAT(cos(0 * angle[deg]), AlmostEquals(1. * one)); REQUIRE_THAT(cos(200 * angle[grad]), AlmostEquals(-1. * one));
REQUIRE_THAT(cos(90 * angle[deg]), AlmostEquals(0. * one)); REQUIRE_THAT(cos(300 * angle[grad]), AlmostEquals(0. * one));
REQUIRE_THAT(cos(180 * angle[deg]), AlmostEquals(-1. * one)); }
REQUIRE_THAT(cos(270 * angle[deg]), AlmostEquals(0. * one));
SECTION("tan")
REQUIRE_THAT(cos(0 * angle[grad]), AlmostEquals(1. * one)); {
REQUIRE_THAT(cos(100 * angle[grad]), AlmostEquals(0. * one)); REQUIRE_THAT(tan(0 * angle[deg]), AlmostEquals(0. * one));
REQUIRE_THAT(cos(200 * angle[grad]), AlmostEquals(-1. * one)); REQUIRE_THAT(tan(45 * angle[deg]), AlmostEquals(1. * one));
REQUIRE_THAT(cos(300 * angle[grad]), AlmostEquals(0. * one)); REQUIRE_THAT(tan(135 * angle[deg]), AlmostEquals(-1. * one));
} REQUIRE_THAT(tan(180 * angle[deg]), AlmostEquals(0. * one));
SECTION("tan") REQUIRE_THAT(tan(0 * angle[grad]), AlmostEquals(0. * one));
{ REQUIRE_THAT(tan(50 * angle[grad]), AlmostEquals(1. * one));
REQUIRE_THAT(tan(0 * angle[deg]), AlmostEquals(0. * one)); REQUIRE_THAT(tan(150 * angle[grad]), AlmostEquals(-1. * one));
REQUIRE_THAT(tan(45 * angle[deg]), AlmostEquals(1. * one)); REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one, 2));
REQUIRE_THAT(tan(135 * angle[deg]), AlmostEquals(-1. * one)); }
REQUIRE_THAT(tan(180 * angle[deg]), AlmostEquals(0. * one)); }
REQUIRE_THAT(tan(0 * angle[grad]), AlmostEquals(0. * one)); SECTION("Angle inverse trigonometric functions")
REQUIRE_THAT(tan(50 * angle[grad]), AlmostEquals(1. * one)); {
REQUIRE_THAT(tan(150 * angle[grad]), AlmostEquals(-1. * one)); using namespace mp_units::angular;
REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one, 2)); using namespace mp_units::angular::unit_symbols;
} using mp_units::angular::unit_symbols::deg;
}
SECTION("asin")
TEST_CASE("Angle inverse trigonometric functions", "[inv trig][angle]") {
{ REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg]));
using namespace mp_units::angular; REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg]));
using namespace mp_units::angular::unit_symbols; REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg]));
using mp_units::angular::unit_symbols::deg; }
SECTION("asin") SECTION("acos")
{ {
REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg])); REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg]));
REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg])); REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg]));
REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg])); REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg]));
} }
SECTION("acos") SECTION("atan")
{ {
REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg])); REQUIRE_THAT(atan(-1 * one), AlmostEquals(-45. * angle[deg]));
REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg])); REQUIRE_THAT(atan(0 * one), AlmostEquals(0. * angle[deg]));
REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg])); REQUIRE_THAT(atan(1 * one), AlmostEquals(45. * angle[deg]));
} }
}
SECTION("atan")
{ SECTION("Angle atan2 functions")
REQUIRE_THAT(atan(-1 * one), AlmostEquals(-45. * angle[deg])); {
REQUIRE_THAT(atan(0 * one), AlmostEquals(0. * angle[deg])); using namespace mp_units::angular;
REQUIRE_THAT(atan(1 * one), AlmostEquals(45. * angle[deg])); using namespace mp_units::angular::unit_symbols;
} using mp_units::angular::unit_symbols::deg;
}
SECTION("atan2 should work on the same quantities")
TEST_CASE("Angle atan2 functions", "[atan2][angle]") {
{ REQUIRE_THAT(atan2(-1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(-45. * angle[deg]));
using namespace mp_units::angular; REQUIRE_THAT(atan2(0. * isq::length[km], 1. * isq::length[km]), AlmostEquals(0. * angle[deg]));
using namespace mp_units::angular::unit_symbols; REQUIRE_THAT(atan2(1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(45. * angle[deg]));
using mp_units::angular::unit_symbols::deg; }
SECTION("atan2 should work with different units of the same dimension")
SECTION("atan2 should work on the same quantities") {
{ REQUIRE_THAT(atan2(-1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(-45. * angle[deg]));
REQUIRE_THAT(atan2(-1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(-45. * angle[deg])); REQUIRE_THAT(atan2(0. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(0. * angle[deg]));
REQUIRE_THAT(atan2(0. * isq::length[km], 1. * isq::length[km]), 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], 1. * isq::length[km]), AlmostEquals(45. * angle[deg])); }
}
SECTION("atan2 should work with different units of the same dimension")
{
REQUIRE_THAT(atan2(-1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(-45. * 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]));
} }
} }

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]")
{ {
REQUIRE(within_4_ulps(sqrt((1.0 * m) * (1.0 * km)).numerical_value_in(m), sqrt(1000.0))); // 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)));
}
// 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