From 8ec2f94ae24611a6c722409a25e3c18a2d94c429 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Mon, 11 Nov 2024 23:36:58 +0100 Subject: [PATCH 01/27] fix: `make_reference` should skip only the exact kinds deduced from a unit --- src/core/include/mp-units/framework/quantity_spec.h | 2 +- test/static/reference_test.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/core/include/mp-units/framework/quantity_spec.h b/src/core/include/mp-units/framework/quantity_spec.h index 900cf190..8d37e151 100644 --- a/src/core/include/mp-units/framework/quantity_spec.h +++ b/src/core/include/mp-units/framework/quantity_spec.h @@ -59,7 +59,7 @@ template requires(!AssociatedUnit) || UnitOf [[nodiscard]] consteval Reference auto make_reference(QS, U u) { - if constexpr (QuantityKindSpec) + if constexpr (requires { requires(get_quantity_spec(U{}) == QS{}); }) return u; else return reference{}; diff --git a/test/static/reference_test.cpp b/test/static/reference_test.cpp index 09758aac..fce21a0b 100644 --- a/test/static/reference_test.cpp +++ b/test/static/reference_test.cpp @@ -378,4 +378,14 @@ static_assert(invalid_comparison) static_assert(invalid_comparison); static_assert(invalid_comparison); +// make_reference +static_assert(is_of_type>); +static_assert(is_of_type>); +static_assert(is_of_type, metre), metre_>); +static_assert(is_of_type); +static_assert(is_of_type); +static_assert(is_of_type, hertz), hertz_>); +static_assert(is_of_type); +static_assert(is_of_type, watt), reference, watt_>>); + } // namespace From dc6fb931ea342a21c76709e35f88b6d44ec9710d Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Mon, 11 Nov 2024 23:37:29 +0100 Subject: [PATCH 02/27] docs: ISQ part 6 released --- docs/blog/posts/isq-part-1-introduction.md | 1 + ...sq-part-2-problems-when-isq-is-not-used.md | 1 + docs/blog/posts/isq-part-3-modeling-isq.md | 1 + docs/blog/posts/isq-part-4-implemeting-isq.md | 1 + docs/blog/posts/isq-part-5-benefits.md | 1 + docs/blog/posts/isq-part-6-challenges.md | 494 ++++++++++++++++++ 6 files changed, 499 insertions(+) create mode 100644 docs/blog/posts/isq-part-6-challenges.md diff --git a/docs/blog/posts/isq-part-1-introduction.md b/docs/blog/posts/isq-part-1-introduction.md index e567e9e4..d49d399b 100644 --- a/docs/blog/posts/isq-part-1-introduction.md +++ b/docs/blog/posts/isq-part-1-introduction.md @@ -26,6 +26,7 @@ In this series, we will describe: - [Part 3 - Modeling ISQ](isq-part-3-modeling-isq.md) - [Part 4 - Implementing ISQ](isq-part-4-implemeting-isq.md) - [Part 5 - Benefits](isq-part-5-benefits.md) +- [Part 6 - Challenges](isq-part-6-challenges.md) ## Terms and Definitions diff --git a/docs/blog/posts/isq-part-2-problems-when-isq-is-not-used.md b/docs/blog/posts/isq-part-2-problems-when-isq-is-not-used.md index f64b0940..911d8a05 100644 --- a/docs/blog/posts/isq-part-2-problems-when-isq-is-not-used.md +++ b/docs/blog/posts/isq-part-2-problems-when-isq-is-not-used.md @@ -28,6 +28,7 @@ library on just units or dimensions. - [Part 3 - Modeling ISQ](isq-part-3-modeling-isq.md) - [Part 4 - Implementing ISQ](isq-part-4-implemeting-isq.md) - [Part 5 - Benefits](isq-part-5-benefits.md) +- [Part 6 - Challenges](isq-part-6-challenges.md) ## Limitations of units-only solutions diff --git a/docs/blog/posts/isq-part-3-modeling-isq.md b/docs/blog/posts/isq-part-3-modeling-isq.md index 58e21107..fab9661e 100644 --- a/docs/blog/posts/isq-part-3-modeling-isq.md +++ b/docs/blog/posts/isq-part-3-modeling-isq.md @@ -27,6 +27,7 @@ language. - Part 3 - Modeling ISQ - [Part 4 - Implementing ISQ](isq-part-4-implemeting-isq.md) - [Part 5 - Benefits](isq-part-5-benefits.md) +- [Part 6 - Challenges](isq-part-6-challenges.md) ## Dimension is not enough to describe a quantity diff --git a/docs/blog/posts/isq-part-4-implemeting-isq.md b/docs/blog/posts/isq-part-4-implemeting-isq.md index 61dca347..0eea0078 100644 --- a/docs/blog/posts/isq-part-4-implemeting-isq.md +++ b/docs/blog/posts/isq-part-4-implemeting-isq.md @@ -28,6 +28,7 @@ Now, it is time to see how we can implement hierarchies of quantities of the sam - [Part 3 - Modeling ISQ](isq-part-3-modeling-isq.md) - Part 4 - Implementing ISQ - [Part 5 - Benefits](isq-part-5-benefits.md) +- [Part 6 - Challenges](isq-part-6-challenges.md) ## Modeling a hierarchy of kind _length_ diff --git a/docs/blog/posts/isq-part-5-benefits.md b/docs/blog/posts/isq-part-5-benefits.md index c9d63435..136e1e1f 100644 --- a/docs/blog/posts/isq-part-5-benefits.md +++ b/docs/blog/posts/isq-part-5-benefits.md @@ -26,6 +26,7 @@ how our ISQ model elegantly addresses the remaining problems. - [Part 3 - Modeling ISQ](isq-part-3-modeling-isq.md) - [Part 4 - Implementing ISQ](isq-part-4-implemeting-isq.md) - Part 5 - Benefits +- [Part 6 - Challenges](isq-part-6-challenges.md) ## Generic but safe interfaces diff --git a/docs/blog/posts/isq-part-6-challenges.md b/docs/blog/posts/isq-part-6-challenges.md new file mode 100644 index 00000000..9058e82b --- /dev/null +++ b/docs/blog/posts/isq-part-6-challenges.md @@ -0,0 +1,494 @@ +--- +date: 2024-11-11 +authors: + - mpusz +categories: + - Metrology +comments: true +--- + +# International System of Quantities (ISQ): Part 6 - Challenges + +This article might be the last one from our series. This time, we will discuss the challenges and +issues with modeling of the ISQ in software. + + + +## Articles from this series + +- [Part 1 - Introduction](isq-part-1-introduction.md) +- [Part 2 - Problems when ISQ is not used](isq-part-2-problems-when-isq-is-not-used.md) +- [Part 3 - Modeling ISQ](isq-part-3-modeling-isq.md) +- [Part 4 - Implementing ISQ](isq-part-4-implemeting-isq.md) +- [Part 5 - Benefits](isq-part-5-benefits.md) +- Part 6 - Challenges + + +## Ambiguity + +Some quantity names are ambiguous. It is not a problem of ISQ but of the English language and +the way we communicate things. When I say: "Every _width_ is a _length_, but not every _length_ +is a _width_" most people understand this right away. However, the same people trying to model +[our 3D box problem](isq-part-2-problems-when-isq-is-not-used.md#various-quantities-of-the-same-dimension-and-kinds) +try to do it as follows: + +```cpp +class Box { + quantity length_; + quantity width_; + quantity height_; +public: + // ... +}; +``` + +This looks correct at first sight. Only when we think about the sentence mentioned above will +we realize that this implementation has a problem. We intended to specify three orthogonal +dimensions of the box, each of which will be a strong quantity that is not convertible to others. +But we've failed. + +When we look at the +[tree of quantities of length](isq-part-4-implemeting-isq.md#modeling-a-hierarchy-of-kind-length) +we immediately see that both _width_ and _height_ are special _lengths_ so they are convertible to +it. + +To implement our task correctly, we had to define and use a new quantity of kind _length_: + +```cpp +inline constexpr struct horizontal_length final : quantity_spec {} horizontal_length; +``` + +We do not propose adding _horizontal length_ to ISO 80000-3. There are probably other similar +cases as well, but so far, this was the most common and obvious one we've encountered. + + +## No common quantities + +ISO 80000-1:2009 explicitly states: + +!!! quote + + Two or more quantities cannot be added or subtracted unless they belong to the same category + of mutually comparable quantities. + +This means that we should be able to add and subtract any quantities as long as they belong to +the same kind. However, ISO/IEC documents do not provide any rules or even hints about what should +be the result of such operations. + +If it is possible to add _radius_ and _distance_, then what quantity should be provided +in return? Undoubtedly, the resulting quantity type can't be the same as any of the arguments. +It is not a _radius_ or _distance_. It is some closer unspecified _length_, though. + +!!! info + + Finding the correct solution took us many months of experimentation and implementation. + Based on the hierarchy tree of quantities, we can define + [conversion rules](isq-part-3-modeling-isq.md#converting-between-quantities-of-the-same-kind) + and what a [common quantity should be](isq-part-3-modeling-isq.md#comparing-adding-and-subtracting-quantities-of-the-same-kind). + + +## Lack of consistency + +The documents of ISO/IEC 80000 are not 100% consistent, and programming languages do not like +inconsistencies. + +For example: + +- _time_ is mentioned as a base quantity of ISQ in ISO 80000-1 chapter 4.5. +- ISO 80000-3 "Space and time", does not define a quantity of _time_. It provides a _duration_ + quantity (item 3-9) with symbol _t_, and states in the Remarks section: + + !!! quote + + Duration is often just called time. + +- Other parts (e.g., IEC 80000-6 "Electromagnetism") often say: + + !!! quote + + ... _t_ is time (ISO 80000-3) + +To be consistent, ISO/IEC should either: + +- change ISO 80000-1 chapter 4.5 and all references in other parts to use _duration_ (unlikely), +- or add _time_ as an alias name to _duration_ in the definition 3-9 of ISO 80000-3. + + +## Lack of definitions + +ISQ defines derived quantities in terms of other quantities provided in the series. However, some +definitions mention quantities that are not defined in the ISQ at all. + +For example, _weight_ is defined as $F_\textsf{g} = m\;g$, where $m$ is the _mass_ of the body +(item 4-1 of ISO 80000-4 "Mechanics"), and $g$ is the _local acceleration of free fall_ (ISO 80000-3). + +The problem here is that ISO 80000-3 never defines a quantity with a symbol $g$ or named as a +_local acceleration of free fall_. The closest one we have is _acceleration_ (item 3-11) with +a symbol $a$. + +!!! info + + To have a proper definition of _weight_ in **mp-units** that is not defined in terms of just + any kind of _acceleration_, we have added `isq::acceleration_of_free_fall` in our definitions + as an extension to the original ISQ set of quantities. + + +## Not engineering-friendly + +Many quantities have proper physical definitions, but they are sometimes not engineering-friendly. + +For example, _velocity_ is defined as a rate of change of _position vector_ +$v = \frac{\textsf{d}r}{\textsf{d}t}$, where $r$ denotes the _position vector_ (item 3‑1.10) and +$t$ the _duration_ (item 3‑9). + +Next, a _speed_ quantity is defined as the magnitude of _velocity_. Despite being +physically correct, requiring every _speed_ to be derived from the vector quantity of _velocity_ +in software would be inconvenient. If this was the only case, people would always need to use +vector representations of _position vectors_ to talk about _speeds_, which differs from what we +do in practice. In practice, we divide any kind of _length_ by _time_ to get some kind of _speed_. + +ISO 80000-3 provides _length_, _height_, _distance_ and other quantities of kind _length_ that when +divided by _duration_ can serve really well to calculate _speed_. + +!!! info + + This is why in **mp-units**, we decided to divert from the official definition of _speed_ and + define it as: + + ```cpp + inline constexpr struct speed : quantity_spec {} speed; + ``` + + This allows us to create a quantity of kind _speed_ from any quantity of _length_ divided + by _time_. + + Additionally, it is essential to note that for the needs of our library, defining _velocity_ + as `position_vector / duration` would be wrong. We miss the delta part here. Even though it is + not mentioned in ISO 80000-3, the delta of _position vectors_ is actually a _displacement_. + This is why our _velocity_ is defined as: + + ```cpp + inline constexpr struct velocity : quantity_spec {} velocity; + ``` + + Please also note that _velocity_ is defined as a more specialized quantity of _speed_. + + +## Affine space agnostic + +[The affine space](../../users_guide/framework_basics/the_affine_space.md) is a powerful +abstraction, allowing us to model some problems safer or more accurately. It has two types of +entities: + +- point - a position specified with coordinate values (e.g., location, address, etc.), +- displacement vector - the difference between two points (e.g., shift, offset, displacement, + duration, etc.). + +Vectors support all the arithmetics operations, but points have some limitations. It is not +possible to: + +- add two _points_, +- subtract a _point_ from a _vector_, +- multiply nor divide _points_ with anything else. + +ISO/IEC series does not acknowledge this abstraction even though it would be really useful in +some cases. Let's discuss the following two examples. + +What does it mean to add two _altitudes_? It is not meaningful. On the other hand, subtracting +those should not result in an _altitude_, but in a quantity of _height_. Adding or +subtracting _height_ to/from _altitude_ results in _altitude_. Subtracting _altitude_ from +_height_ is meaningless again. Those quantities clearly model affine space. Maybe this is why +ISQ defines them as one quantity type _height_/_depth_/_altitude_? + +What does it mean to add two _position vectors_? It is not meaningful again. However, subtracting +those results in a _displacement_ as we noted in the previous chapter. Adding or subtracting +_displacement_ to/from _position vector_ results in another _position vector_, and subtracting +_position vector_ from _displacement_ does not have physical sense. Again, those quantities +perfectly model affine space. However, this time, those are defined as separate and independent +quantities (i.e., displacement is not modeled as delta _position vector_ or _position vector_ +is not modeled as a _displacement_ from the origin of a coordinate system). + +!!! info + + Currently, **mp-units** does not enforce the affine space behavior for such quantities. + Today, subtracting two _altitudes_ result in an _altitude_ and subtracting two + _position vectors_ result in a _position vector_. However, we plan to support automatic + conversion to a proper quantity type on subtraction and addition shortly. + + +## Non-negative quantities + +Some quantities in the ISQ are defined as non-negative. This is a really interesting property that +may be checked at runtime to increase safety. However, the number of such quantities is minimal. +From a few hundred quantities provided by the ISO/IEC series, only the following have this property +mentioned explicitly: + +- _width_/_breadth_, +- _thickness_, +- _diameter_, +- _radius_. + +If _height_ was defined separately from _altitude_, it could probably also join this group. + +Let's think a bit more about this. What does it mean that a quantity is non-negative? Indeed, +it is hard to imagine something of a negative _width_ or _radius_. However, if we subtract +two _widths_, the second one may be larger. This will result in a negative +quantity of _width_, violating our precondition. So, is it non-negative or not? + +Again, we have to talk about the affine space abstractions. Every empirical measurement can be +expressed as a point. Such points for some quantities may be non-negative indeed. + +Non-negative quantities do not end on the ones provided above. For example, _speed_ is a good +example here as well. In general, all magnitudes of vector quantities will also have this property. + +When subtracting two points, we end up with a delta/displacement type, which may be negative +even for quantities listed as non-negative in the ISQ. As stated in the previous chapter, +having affine space abstractions acknowledged in ISQ would greatly help here. + + +## Lack of quantity recipes + +Definition of many derived quantities provides their recipes in the form of +[quantity equations](../../appendix/glossary.md#quantity-equation) (e.g., _weight_ equation +in the previous chapter). However, some of them do not. Instead, they often provide a very +generic description. + +For example, _force_ is defined as: + +!!! quote + + vector (ISO 80000-2) quantity describing interaction between bodies or particles. + +This is not helpful for programming languages that like explicit definitions. Different +vendors may interpret the above differently, which will result in different implementations that +will not be compatible with each other. + +As the derived quantity of _force_ has to be a vector quantity, it has to be defined in terms of +at least one other vector quantity. We have a few to choose from: + +- _displacement_ ($\Delta{r}$), +- _velocity_ ($v$), +- _acceleration_ ($a$). + +It is not stated explicitly in ISQ which one of those should be used and how. + +!!! info + + In **mp-units** we decided to define _force_ as $F = m\;a$. + + +## Lack of generic quantities and name conflicts + +In the previous chapter, we complained about some definitions needing to be more complex or generic. +On the other hand, we also lack some generic quantities in ISQ that could serve as a root for +a quantity hierarchy tree. + +For example: + +- ISO 80000-4 "Mechanics" defines _power <mechanics>_ as $P = F\;v$ (scalar product of force $F$ + (item 4-9.1) acting to a body and its velocity $v$ (ISO 80000-3)), +- ISO 80000-6 "Electromagnetism" defines _power_ as $p = u\;i$ (scalar quantity given by the + product of _instantaneous voltage_ $u$ (item 6-11.3) and _instantaneous electric current_ $i$ + (item 6-1)). + +First, the above definitions have somehow conflicting names which makes it hard for the programming +languages to name them consistently by different vendors. + +!!! info + + In **mp-units**, we chose `mechanical_power` and `electromagnetism_power` for those. + +Second, we do not have any other more generic definition of _power_ to put above those in the tree. +Not having it makes it hard to answer what should be the result of: + +```cpp +quantity q = isq::mechanical_power(42 * W) + isq::electromagnetism_power(60 * W); +``` + +!!! info + + To solve the above problem, we have added `isq::power` in **mp-units**, that has a really + generic definition of: + + ```cpp + inline constexpr struct power : quantity_spec(length) / pow<3>(time)> {} power; + ``` + + +## Invalid definitions order + +_Energy_ is defined a bit better than _power_, but still not without issues. + +The first time ISQ mentions _energy_ is in the ISO 80000-4 "Mechanics". It defines +_potential energy_, _kinetic energy_, and a _mechanical energy_ as the sum of the first two. +Right after that a _mechanical work/work_ is defined. + +Then ISO 80000-5 "Thermodynamics" defines _energy <thermodynamics>_ as: + +!!! quote + + ability of a system to do work (ISO 80000-4). + +Next, _internal energy/thermodynamic energy_ is defined in terms of the change of heat. + +From the above, it seems that what is called _energy <thermodynamics>_ should actually be +the root of our tree and probably be provided in Part 4 before the _mechanical energy_ is defined. + + +## Hierarchies of derived quantities + +Derived quantities of the same kind are often independently defined in the ISQ. The ISO/IEC 80000 +series often does not suggest any hierarchy between those. Even more, it states: + +!!! quote "ISO/IEC Guide 99" + + The division of ‘quantity’ according to ‘kind of quantity’ is, to some extent, arbitrary. + +Because of this, it is unknown or ambiguous how to form a hierarchy tree for such quantities. + +To get some sense of the complexity here, let's look again at our tree of quantities of a kind +_energy_: + +```mermaid +flowchart TD + energy["energy
(mass * length2 / time2)
[J]"] + energy --- mechanical_energy["mechanical_energy"] + mechanical_energy --- potential_energy["potential_energy"] + mechanical_energy --- kinetic_energy["kinetic_energy"] + energy --- enthalpy["enthalpy"] + enthalpy --- internal_energy["internal_energy / thermodynamic_energy"] + internal_energy --- Helmholtz_energy["Helmholtz_energy / Helmholtz_function"] + enthalpy --- Gibbs_energy["Gibbs_energy / Gibbs_function"] + energy --- active_energy["active_energy"] +``` + +Not being exact means that every vendor may implement it differently. This will result in: + +- different convertibility rules among quantities: + + ```cpp + static_assert(implicitly_convertible(isq::potential_energy, isq::mechanical_energy)); + static_assert(explicitly_convertible(isq::mechanical_energy, isq::potential_energy)); + ``` + +- different common quantities resulting from the arithmetics on various quantities of the same + kind: + + ```cpp + static_assert((isq::potential_energy(1 * J) + isq::kinetic_energy(1 * J)).quantity_spec == isq::mechanical_energy); + ``` + +It would be great if ISQ could provide specific division of quantities into kinds and more +information about the position of each quantity within the hierarchy of quantities of +the same kind. + +!!! important + + 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. + + 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. + + +## The same or a different kind? + +Some quantities are more complicated than others. For example, _power_ has: + +- scalar quantities expressed in: + - W (watts) (e.g., _mechanical power_, _active power_), + - VA (volt-ampere) (e.g., _apparent power_), + - var (e.g., _reactive 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 +a different unit? + +```mermaid +flowchart TD + power["power
(mass * length2 / time3)
[W]"] + power --- mechanical_power["mechanical_power
(scalar_product(force, velocity))"] + power --- electromagnetism_power["electromagnetism_power | instantaneous_power
(instantaneous_voltage * instantaneous_electric_current)"] + power --- active_power["active_power
(1 / period * instantaneous_power * time)
(re(complex_power))
"] + + apparent_power["apparent_power
(voltage * electric_current)
(mod(complex_power))

[VA]"] + apparent_power --- nonactive_power["nonactive_power
(sqrt(apparent_power2 - active_power2))
"] + nonactive_power --- reactive_power["reactive_power
(im(complex_power))
[var]"] + apparent_power --- complex_power["complex_power
{complex}
(voltage_phasor * electric_current_phasor)
(active_power + j * reactive_power)
"] +``` + +This will mean that we will not be able to add or compare _active power_ with _apparent power_, +which probably makes a lot of sense. Again, ISQ does not provide a direct answer here. + + +## More base quantities? + +Is ISQ really based on only seven base quantities? Let's look at the definition of +_traffic intensity_ in IEC 80000-13 "Information science and technology": + +!!! quote + + number of simultaneously busy resources in a particular pool of resources. + +It looks like a definition of a specialized dimensionless quantity or, more correctly, a quantity +of dimension one. This would not be the only such case. Even in the same Part 13, we can find +quantities like _storage capacity_ with a similar property. + +Only when we look closer do we start to see differences. All dimensionless quantities, even if they +have their own dedicated units, can also be measured in a unit of one (1). This is true for +_storage capacity_ (also measured in bits), _angular measure_ (also measured in radians), +_solid angular measure (also measured in steradians), and more. + +However, _traffic intensity_ can only be measured in erlangs (E), not in a unit one (1). +Does it mean that it is a "hidden" 8-th base quantity in ISQ? If so, should it have its own +dimension as well? + +Angular quantities are another interesting case here. Scientists have written petitions and papers +for years to make them an additional dimension in ISQ and SI. More about this can be found in +our documentation's [Strong Angular System](../../users_guide/systems/strong_angular_system.md) +chapter. + + +## Summary + +ISQ is tremendous and solves many problems we had in modeling various subjects for years in +software. As a result, we have more powerful tools in our hands that allow us to deliver safer +products. + +Unfortunately, ISQ, contrarily to SI, is not widely recognized, and no libraries besides +**mp-units** model it in any programming language. Keeping it behind a paywall does not help +either. We hope that posts from this series will spread in the community, raise awareness +of ISQ and its benefits, and encourage authors of other libraries to implement it in their +products. + +Despite all the benefits, it is essential to realize that ISQ has many problems. International +standards should be specified in such a way that there is no room for ambiguity in their +interpretation by different parties trying to use them. As described above, this is not the case +here. + +ISQ is not ready to be unambiguously modeled in software by various vendors. Here are the most +important problems to solve to allow this: + +1. ISQ needs to define basic operations on quantities: + +    - what the result of addition and subtraction should be when arguments differ, +    - convertibility rules. + +2. The exact quantity equation recipe needs to be included for many derived quantities. +3. Many ISQ quantities do not provide their exact relation versus other quantities of the same + kind (no strict hierarchy). +4. Some missing quantities need to be included. Others would benefit from corrected names. + +Additionally: + +- extending ISQ with affine space abstractions, +- specifying more quantities as non-negative, +- adding more base quantities (i.e., _angle_) + +could improve the safety of our programs and products that people depend on with their lives on +a daily basis. + +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 +hear about the projects in which our library is being used and about use cases it helps +address. From 0e7314e3a63eff9c5cc280ec0ffd06d9eb83de10 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 08:50:11 +0100 Subject: [PATCH 03/27] fix: math tests for `inverse` fixed after change in `make_reference` --- test/static/math_test.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/static/math_test.cpp b/test/static/math_test.cpp index a573fd1f..48682ee8 100644 --- a/test/static/math_test.cpp +++ b/test/static/math_test.cpp @@ -237,17 +237,17 @@ static_assert(compare(round(-1999. * isq::time[ms]), -2. * isq::time #endif // non-truncating -static_assert(compare(inverse(250 * Hz), 4000 * us)); -static_assert(compare(inverse(250 * kHz), 4 * us)); -static_assert(compare(inverse(250 * uHz), 4 * ks)); +static_assert(compare(kind_of(inverse(250 * Hz)), 4000 * us)); +static_assert(compare(kind_of(inverse(250 * kHz)), 4 * us)); +static_assert(compare(kind_of(inverse(250 * uHz)), 4 * ks)); // truncating -static_assert(compare(inverse(1 * kHz), 0 * s)); +static_assert(compare(kind_of(inverse(1 * kHz)), 0 * s)); // floating-point representation does not truncate -static_assert(compare(inverse(1. * kHz), 0.001 * s)); +static_assert(compare(kind_of(inverse(1. * kHz)), 0.001 * s)); // check if constraints work properly for a derived unit of a narrowed kind -static_assert(compare(inverse(1 * s), 1 * Hz)); +static_assert(compare(kind_of(inverse(1 * s)), 1 * Hz)); } // namespace From f368ef5c832ceeb41a6e402f259c7702879255f7 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 09:01:50 +0100 Subject: [PATCH 04/27] style: trailing whitespaces fixed in ISQ Part 6 --- docs/blog/posts/isq-part-6-challenges.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/blog/posts/isq-part-6-challenges.md b/docs/blog/posts/isq-part-6-challenges.md index 9058e82b..45674e30 100644 --- a/docs/blog/posts/isq-part-6-challenges.md +++ b/docs/blog/posts/isq-part-6-challenges.md @@ -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 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 and then verify that everything works as expected. @@ -490,5 +490,5 @@ a daily basis. 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 -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. From 123b4c0f140a2d9b67d89230209e488889931198 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 09:53:31 +0100 Subject: [PATCH 05/27] docs: "The same or a different kind?" of ISQ Part 6 extended --- docs/blog/posts/isq-part-6-challenges.md | 46 +++++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/docs/blog/posts/isq-part-6-challenges.md b/docs/blog/posts/isq-part-6-challenges.md index 45674e30..f658813e 100644 --- a/docs/blog/posts/isq-part-6-challenges.md +++ b/docs/blog/posts/isq-part-6-challenges.md @@ -401,8 +401,8 @@ Some quantities are more complicated than others. For example, _power_ has: - var (e.g., _reactive 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 -a different unit? +How should we model this? Maybe those should be two or three independent trees of quantities, each +having its own unit? ```mermaid flowchart TD @@ -411,14 +411,50 @@ flowchart TD power --- electromagnetism_power["electromagnetism_power | instantaneous_power
(instantaneous_voltage * instantaneous_electric_current)"] power --- active_power["active_power
(1 / period * instantaneous_power * time)
(re(complex_power))
"] - apparent_power["apparent_power
(voltage * electric_current)
(mod(complex_power))

[VA]"] + nonactive_power["nonactive_power
(mass * length2 / time3)
[VA]"] + nonactive_power --- reactive_power["reactive_power
(im(complex_power))
[var]"] + + complex_power["complex_power
{complex}
(voltage_phasor * electric_current_phasor)
(active_power + j * reactive_power)

[VA]"] + complex_power --- apparent_power["apparent_power
(voltage * electric_current)
(mod(complex_power))
"] +``` + +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 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 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["power
(mass * length2 / time3)
[W]"] + power --- mechanical_power["mechanical_power
(scalar_product(force, velocity))"] + power --- electromagnetism_power["electromagnetism_power | instantaneous_power
(instantaneous_voltage * instantaneous_electric_current)"] + power --- apparent_power["apparent_power
(voltage * electric_current)
(mod(complex_power))

[VA]"] + apparent_power --- active_power["active_power
(1 / period * instantaneous_power * time)
(re(complex_power))
"] apparent_power --- nonactive_power["nonactive_power
(sqrt(apparent_power2 - active_power2))
"] nonactive_power --- reactive_power["reactive_power
(im(complex_power))
[var]"] apparent_power --- complex_power["complex_power
{complex}
(voltage_phasor * electric_current_phasor)
(active_power + j * reactive_power)
"] ``` -This will mean that we will not be able to add or compare _active power_ with _apparent power_, -which probably makes a lot of sense. Again, ISQ does not provide a direct answer here. +However, the above allows direct addition and comparison of _active power_ and _nonactive power_, +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? From 78204c7e5f81b148657eb470ef43e1ab9df6cfb7 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 11:27:52 +0100 Subject: [PATCH 06/27] test: runtime unit tests refactored to have a bigger granularity (less top level tests) --- test/runtime/distribution_test.cpp | 949 +++++++++++------------ test/runtime/fixed_string_test.cpp | 25 +- test/runtime/fmt_test.cpp | 1138 ++++++++++++++-------------- test/runtime/math_test.cpp | 876 ++++++++++----------- test/runtime/quantity_test.cpp | 35 +- 5 files changed, 1528 insertions(+), 1495 deletions(-) diff --git a/test/runtime/distribution_test.cpp b/test/runtime/distribution_test.cpp index 9df1eb3b..e18d5a9e 100644 --- a/test/runtime/distribution_test.cpp +++ b/test/runtime/distribution_test.cpp @@ -43,618 +43,621 @@ import mp_units; using namespace mp_units; -TEST_CASE("uniform_int_distribution") +TEST_CASE("distributions", "[random][distribution]") { - using rep = std::int64_t; - using q = quantity; - - SECTION("default") + SECTION("uniform_int_distribution") { - auto dist = mp_units::uniform_int_distribution(); + using rep = std::int64_t; + using q = quantity; - CHECK(dist.a() == q::zero()); - CHECK(dist.b() == q::max()); + SECTION("default") + { + auto dist = mp_units::uniform_int_distribution(); + + CHECK(dist.a() == q::zero()); + CHECK(dist.b() == q::max()); + } + + SECTION("parametrized") + { + constexpr rep a = 2; + constexpr rep b = 5; + + auto stl_dist = std::uniform_int_distribution(a, b); + auto units_dist = mp_units::uniform_int_distribution(a * si::metre, b * si::metre); + + CHECK(units_dist.a() == stl_dist.a() * si::metre); + CHECK(units_dist.b() == stl_dist.b() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("uniform_real_distribution") { - constexpr rep a = 2; - constexpr rep b = 5; + using rep = long double; + using q = quantity; - auto stl_dist = std::uniform_int_distribution(a, b); - auto units_dist = mp_units::uniform_int_distribution(a * si::metre, b * si::metre); + SECTION("default") + { + auto dist = mp_units::uniform_real_distribution(); - CHECK(units_dist.a() == stl_dist.a() * si::metre); - CHECK(units_dist.b() == stl_dist.b() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.a() == q::zero()); + CHECK(dist.b() == q::one()); + } -TEST_CASE("uniform_real_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep a = 2.0; + constexpr rep b = 5.0; - SECTION("default") - { - auto dist = mp_units::uniform_real_distribution(); + auto stl_dist = std::uniform_real_distribution(a, b); + auto units_dist = mp_units::uniform_real_distribution(a * si::metre, b * si::metre); - CHECK(dist.a() == q::zero()); - CHECK(dist.b() == q::one()); + CHECK(units_dist.a() == stl_dist.a() * si::metre); + CHECK(units_dist.b() == stl_dist.b() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("binomial_distribution") { - constexpr rep a = 2.0; - constexpr rep b = 5.0; + using rep = std::int64_t; + using q = quantity; - auto stl_dist = std::uniform_real_distribution(a, b); - auto units_dist = mp_units::uniform_real_distribution(a * si::metre, b * si::metre); + SECTION("default") + { + auto dist = mp_units::binomial_distribution(); - CHECK(units_dist.a() == stl_dist.a() * si::metre); - CHECK(units_dist.b() == stl_dist.b() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.p() == 0.5); + CHECK(dist.t() == q::one()); + } -TEST_CASE("binomial_distribution") -{ - using rep = std::int64_t; - using q = quantity; + SECTION("parametrized") + { + constexpr rep t = 5; + constexpr double p = 0.25; - SECTION("default") - { - auto dist = mp_units::binomial_distribution(); + auto stl_dist = std::binomial_distribution(t, p); + auto units_dist = mp_units::binomial_distribution(t * si::metre, p); - CHECK(dist.p() == 0.5); - CHECK(dist.t() == q::one()); + CHECK(units_dist.p() == stl_dist.p()); + CHECK(units_dist.t() == stl_dist.t() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("negative_binomial_distribution") { - constexpr rep t = 5; - constexpr double p = 0.25; + using rep = std::int64_t; + using q = quantity; - auto stl_dist = std::binomial_distribution(t, p); - auto units_dist = mp_units::binomial_distribution(t * si::metre, p); + SECTION("default") + { + auto dist = mp_units::negative_binomial_distribution(); - CHECK(units_dist.p() == stl_dist.p()); - CHECK(units_dist.t() == stl_dist.t() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.p() == 0.5); + CHECK(dist.k() == q::one()); + } -TEST_CASE("negative_binomial_distribution") -{ - using rep = std::int64_t; - using q = quantity; + SECTION("parametrized") + { + constexpr rep k = 5; + constexpr double p = 0.25; - SECTION("default") - { - auto dist = mp_units::negative_binomial_distribution(); + auto stl_dist = std::negative_binomial_distribution(k, p); + auto units_dist = mp_units::negative_binomial_distribution(k * si::metre, p); - CHECK(dist.p() == 0.5); - CHECK(dist.k() == q::one()); + CHECK(units_dist.p() == stl_dist.p()); + CHECK(units_dist.k() == stl_dist.k() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("geometric_distribution") { - constexpr rep k = 5; - constexpr double p = 0.25; + using rep = std::int64_t; + using q = quantity; - auto stl_dist = std::negative_binomial_distribution(k, p); - auto units_dist = mp_units::negative_binomial_distribution(k * si::metre, p); + SECTION("default") + { + auto dist = mp_units::geometric_distribution(); - CHECK(units_dist.p() == stl_dist.p()); - CHECK(units_dist.k() == stl_dist.k() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.p() == 0.5); + } -TEST_CASE("geometric_distribution") -{ - using rep = std::int64_t; - using q = quantity; + SECTION("parametrized") + { + constexpr double p = 0.25; - SECTION("default") - { - auto dist = mp_units::geometric_distribution(); + auto stl_dist = std::geometric_distribution(p); + auto units_dist = mp_units::geometric_distribution(p); - CHECK(dist.p() == 0.5); + CHECK(units_dist.p() == stl_dist.p()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("poisson_distribution") { - constexpr double p = 0.25; + using rep = std::int64_t; + using q = quantity; - auto stl_dist = std::geometric_distribution(p); - auto units_dist = mp_units::geometric_distribution(p); + SECTION("default") + { + auto dist = mp_units::poisson_distribution(); - CHECK(units_dist.p() == stl_dist.p()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.mean() == 1.0); + } -TEST_CASE("poisson_distribution") -{ - using rep = std::int64_t; - using q = quantity; + SECTION("parametrized") + { + constexpr double mean = 5.0; - SECTION("default") - { - auto dist = mp_units::poisson_distribution(); + auto stl_dist = std::poisson_distribution(mean); + auto units_dist = mp_units::poisson_distribution(mean); - CHECK(dist.mean() == 1.0); + CHECK(units_dist.mean() == stl_dist.mean()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("exponential_distribution") { - constexpr double mean = 5.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::poisson_distribution(mean); - auto units_dist = mp_units::poisson_distribution(mean); + SECTION("default") + { + auto dist = mp_units::exponential_distribution(); - CHECK(units_dist.mean() == stl_dist.mean()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.lambda() == 1.0); + } -TEST_CASE("exponential_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr double lambda = 2.0; - SECTION("default") - { - auto dist = mp_units::exponential_distribution(); + auto stl_dist = std::exponential_distribution(lambda); + auto units_dist = mp_units::exponential_distribution(lambda); - CHECK(dist.lambda() == 1.0); + CHECK(units_dist.lambda() == stl_dist.lambda()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("gamma_distribution") { - constexpr double lambda = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::exponential_distribution(lambda); - auto units_dist = mp_units::exponential_distribution(lambda); + SECTION("default") + { + auto dist = mp_units::gamma_distribution(); - CHECK(units_dist.lambda() == stl_dist.lambda()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.alpha() == 1.0); + CHECK(dist.beta() == 1.0); + } -TEST_CASE("gamma_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr double alpha = 5.0; + constexpr double beta = 2.0; - SECTION("default") - { - auto dist = mp_units::gamma_distribution(); + auto stl_dist = std::gamma_distribution(alpha, beta); + auto units_dist = mp_units::gamma_distribution(alpha, beta); - CHECK(dist.alpha() == 1.0); - CHECK(dist.beta() == 1.0); + CHECK(units_dist.alpha() == stl_dist.alpha()); + CHECK(units_dist.beta() == stl_dist.beta()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("weibull_distribution") { - constexpr double alpha = 5.0; - constexpr double beta = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::gamma_distribution(alpha, beta); - auto units_dist = mp_units::gamma_distribution(alpha, beta); + SECTION("default") + { + auto dist = mp_units::weibull_distribution(); - CHECK(units_dist.alpha() == stl_dist.alpha()); - CHECK(units_dist.beta() == stl_dist.beta()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.a() == 1.0); + CHECK(dist.b() == 1.0); + } -TEST_CASE("weibull_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep a = 5.0; + constexpr rep b = 2.0; - SECTION("default") - { - auto dist = mp_units::weibull_distribution(); + auto stl_dist = std::weibull_distribution(a, b); + auto units_dist = mp_units::weibull_distribution(a, b); - CHECK(dist.a() == 1.0); - CHECK(dist.b() == 1.0); + CHECK(units_dist.a() == stl_dist.a()); + CHECK(units_dist.b() == stl_dist.b()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("extreme_value_distribution") { - constexpr rep a = 5.0; - constexpr rep b = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::weibull_distribution(a, b); - auto units_dist = mp_units::weibull_distribution(a, b); + SECTION("default") + { + auto dist = mp_units::extreme_value_distribution(); - CHECK(units_dist.a() == stl_dist.a()); - CHECK(units_dist.b() == stl_dist.b()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.a() == q::zero()); + CHECK(dist.b() == 1.0); + } -TEST_CASE("extreme_value_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep a = 5.0; + constexpr rep b = 2.0; - SECTION("default") - { - auto dist = mp_units::extreme_value_distribution(); + auto stl_dist = std::extreme_value_distribution(a, b); + auto units_dist = mp_units::extreme_value_distribution(a * si::metre, b); - CHECK(dist.a() == q::zero()); - CHECK(dist.b() == 1.0); + CHECK(units_dist.a() == stl_dist.a() * si::metre); + CHECK(units_dist.b() == stl_dist.b()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("normal_distribution") { - constexpr rep a = 5.0; - constexpr rep b = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::extreme_value_distribution(a, b); - auto units_dist = mp_units::extreme_value_distribution(a * si::metre, b); + SECTION("default") + { + auto dist = mp_units::normal_distribution(); - CHECK(units_dist.a() == stl_dist.a() * si::metre); - CHECK(units_dist.b() == stl_dist.b()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.mean() == q::zero()); + CHECK(dist.stddev() == q::one()); + } -TEST_CASE("normal_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep mean = 5.0; + constexpr rep stddev = 2.0; - SECTION("default") - { - auto dist = mp_units::normal_distribution(); + auto stl_dist = std::normal_distribution(mean, stddev); + auto units_dist = mp_units::normal_distribution(mean * si::metre, stddev * si::metre); - CHECK(dist.mean() == q::zero()); - CHECK(dist.stddev() == q::one()); + CHECK(units_dist.mean() == stl_dist.mean() * si::metre); + CHECK(units_dist.stddev() == stl_dist.stddev() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("lognormal_distribution") { - constexpr rep mean = 5.0; - constexpr rep stddev = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::normal_distribution(mean, stddev); - auto units_dist = mp_units::normal_distribution(mean * si::metre, stddev * si::metre); + SECTION("default") + { + auto dist = mp_units::lognormal_distribution(); - CHECK(units_dist.mean() == stl_dist.mean() * si::metre); - CHECK(units_dist.stddev() == stl_dist.stddev() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.m() == q::zero()); + CHECK(dist.s() == q::one()); + } -TEST_CASE("lognormal_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep m = 5.0; + constexpr rep s = 2.0; - SECTION("default") - { - auto dist = mp_units::lognormal_distribution(); + auto stl_dist = std::lognormal_distribution(m, s); + auto units_dist = mp_units::lognormal_distribution(m * si::metre, s * si::metre); - CHECK(dist.m() == q::zero()); - CHECK(dist.s() == q::one()); + CHECK(units_dist.m() == stl_dist.m() * si::metre); + CHECK(units_dist.s() == stl_dist.s() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("chi_squared_distribution") { - constexpr rep m = 5.0; - constexpr rep s = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::lognormal_distribution(m, s); - auto units_dist = mp_units::lognormal_distribution(m * si::metre, s * si::metre); + SECTION("default") + { + auto dist = mp_units::chi_squared_distribution(); - CHECK(units_dist.m() == stl_dist.m() * si::metre); - CHECK(units_dist.s() == stl_dist.s() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.n() == 1.0); + } -TEST_CASE("chi_squared_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep n = 5.0; - SECTION("default") - { - auto dist = mp_units::chi_squared_distribution(); + auto stl_dist = std::chi_squared_distribution(n); + auto units_dist = mp_units::chi_squared_distribution(n); - CHECK(dist.n() == 1.0); + CHECK(units_dist.n() == stl_dist.n()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("cauchy_distribution") { - constexpr rep n = 5.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::chi_squared_distribution(n); - auto units_dist = mp_units::chi_squared_distribution(n); + SECTION("default") + { + auto dist = mp_units::cauchy_distribution(); - CHECK(units_dist.n() == stl_dist.n()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.a() == q::zero()); + CHECK(dist.b() == q::one()); + } -TEST_CASE("cauchy_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep a = 5.0; + constexpr rep b = 2.0; - SECTION("default") - { - auto dist = mp_units::cauchy_distribution(); + auto stl_dist = std::cauchy_distribution(a, b); + auto units_dist = mp_units::cauchy_distribution(a * si::metre, b * si::metre); - CHECK(dist.a() == q::zero()); - CHECK(dist.b() == q::one()); + CHECK(units_dist.a() == stl_dist.a() * si::metre); + CHECK(units_dist.b() == stl_dist.b() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("fisher_f_distribution") { - constexpr rep a = 5.0; - constexpr rep b = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::cauchy_distribution(a, b); - auto units_dist = mp_units::cauchy_distribution(a * si::metre, b * si::metre); + SECTION("default") + { + auto dist = mp_units::fisher_f_distribution(); - CHECK(units_dist.a() == stl_dist.a() * si::metre); - CHECK(units_dist.b() == stl_dist.b() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.m() == 1.0); + CHECK(dist.n() == 1.0); + } -TEST_CASE("fisher_f_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep m = 5.0; + constexpr rep n = 2.0; - SECTION("default") - { - auto dist = mp_units::fisher_f_distribution(); + auto stl_dist = std::fisher_f_distribution(m, n); + auto units_dist = mp_units::fisher_f_distribution(m, n); - CHECK(dist.m() == 1.0); - CHECK(dist.n() == 1.0); + CHECK(units_dist.m() == stl_dist.m()); + CHECK(units_dist.n() == stl_dist.n()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("student_t_distribution") { - constexpr rep m = 5.0; - constexpr rep n = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::fisher_f_distribution(m, n); - auto units_dist = mp_units::fisher_f_distribution(m, n); + SECTION("default") + { + auto dist = mp_units::student_t_distribution(); - CHECK(units_dist.m() == stl_dist.m()); - CHECK(units_dist.n() == stl_dist.n()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(dist.n() == 1.0); + } -TEST_CASE("student_t_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep n = 2.0; - SECTION("default") - { - auto dist = mp_units::student_t_distribution(); + auto stl_dist = std::student_t_distribution(n); + auto units_dist = mp_units::student_t_distribution(n); - CHECK(dist.n() == 1.0); + CHECK(units_dist.n() == stl_dist.n()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("discrete_distribution") { - constexpr rep n = 2.0; + using rep = std::int64_t; + using q = quantity; - auto stl_dist = std::student_t_distribution(n); - auto units_dist = mp_units::student_t_distribution(n); + SECTION("default") + { + auto stl_dist = std::discrete_distribution(); + auto units_dist = mp_units::discrete_distribution(); - CHECK(units_dist.n() == stl_dist.n()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + CHECK(units_dist.probabilities() == stl_dist.probabilities()); + } -TEST_CASE("discrete_distribution") -{ - using rep = std::int64_t; - using q = quantity; + SECTION("parametrized_input_it") + { + constexpr std::array weights = {1.0, 2.0, 3.0}; - SECTION("default") - { - auto stl_dist = std::discrete_distribution(); - auto units_dist = mp_units::discrete_distribution(); + auto stl_dist = std::discrete_distribution(weights.cbegin(), weights.cend()); + auto units_dist = mp_units::discrete_distribution(weights.cbegin(), weights.cend()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - CHECK(units_dist.probabilities() == stl_dist.probabilities()); + CHECK(units_dist.probabilities() == stl_dist.probabilities()); + } + + SECTION("parametrized_initializer_list") + { + const std::initializer_list weights = {1.0, 2.0, 3.0}; + + auto stl_dist = std::discrete_distribution(weights); + auto units_dist = mp_units::discrete_distribution(weights); + + CHECK(units_dist.probabilities() == stl_dist.probabilities()); + } + + SECTION("parametrized_range") + { + constexpr std::size_t count = 3; + constexpr double xmin = 1, xmax = 3; + + auto stl_dist = std::discrete_distribution(count, xmin, xmax, [](double val) { return val; }); + auto units_dist = mp_units::discrete_distribution(count, xmin, xmax, [](double val) { return val; }); + + CHECK(units_dist.probabilities() == stl_dist.probabilities()); + } } - SECTION("parametrized_input_it") + SECTION("piecewise_constant_distribution") { - constexpr std::array weights = {1.0, 2.0, 3.0}; + using rep = long double; + using q = quantity; - auto stl_dist = std::discrete_distribution(weights.cbegin(), weights.cend()); - auto units_dist = mp_units::discrete_distribution(weights.cbegin(), weights.cend()); + std::vector intervals_rep_vec = {1.0, 2.0, 3.0}; + std::vector intervals_qty_vec = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], + 3.0 * isq::length[si::metre]}; - CHECK(units_dist.probabilities() == stl_dist.probabilities()); + SECTION("default") + { + auto stl_dist = std::piecewise_constant_distribution(); + auto units_dist = mp_units::piecewise_constant_distribution(); + + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + CHECK(stl_dist.intervals().size() == 2); + CHECK(units_dist.intervals().size() == 2); + CHECK(stl_dist.densities().size() == 1); + CHECK(units_dist.densities().size() == 1); + } + + SECTION("parametrized_input_it") + { + constexpr std::array intervals_rep = {1.0, 2.0, 3.0}; + constexpr std::array intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], + 3.0 * isq::length[si::metre]}; + constexpr std::array weights = {1.0, 2.0, 3.0}; + + auto stl_dist = + std::piecewise_constant_distribution(intervals_rep.cbegin(), intervals_rep.cend(), weights.cbegin()); + auto units_dist = + mp_units::piecewise_constant_distribution(intervals_qty.cbegin(), intervals_qty.cend(), weights.cbegin()); + + CHECK(stl_dist.intervals() == intervals_rep_vec); + CHECK(units_dist.intervals() == intervals_qty_vec); + CHECK(units_dist.densities() == stl_dist.densities()); + } + + SECTION("parametrized_initializer_list") + { + const std::initializer_list intervals_rep = {1.0, 2.0, 3.0}; + const std::initializer_list intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], + 3.0 * isq::length[si::metre]}; + + auto stl_dist = std::piecewise_constant_distribution(intervals_rep, [](rep val) { return val; }); + auto units_dist = mp_units::piecewise_constant_distribution( + intervals_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); + + CHECK(units_dist.intervals() == intervals_qty_vec); + CHECK(units_dist.densities() == stl_dist.densities()); + } + + SECTION("parametrized_range") + { + constexpr std::size_t nw = 2; + constexpr rep xmin_rep = 1.0, xmax_rep = 3.0; + constexpr q xmin_qty = 1.0 * isq::length[si::metre], xmax_qty = 3.0 * isq::length[si::metre]; + + auto stl_dist = std::piecewise_constant_distribution(nw, xmin_rep, xmax_rep, [](rep val) { return val; }); + auto units_dist = mp_units::piecewise_constant_distribution( + nw, xmin_qty, xmax_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); + + CHECK(units_dist.intervals() == intervals_qty_vec); + CHECK(units_dist.densities() == stl_dist.densities()); + } } - SECTION("parametrized_initializer_list") + SECTION("piecewise_linear_distribution") { - const std::initializer_list weights = {1.0, 2.0, 3.0}; + using rep = long double; + using q = quantity; - auto stl_dist = std::discrete_distribution(weights); - auto units_dist = mp_units::discrete_distribution(weights); + std::vector intervals_rep_vec = {1.0, 2.0, 3.0}; + std::vector intervals_qty_vec = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], + 3.0 * isq::length[si::metre]}; - CHECK(units_dist.probabilities() == stl_dist.probabilities()); + SECTION("default") + { + auto stl_dist = std::piecewise_linear_distribution(); + auto units_dist = mp_units::piecewise_linear_distribution(); + + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + CHECK(stl_dist.intervals().size() == 2); + CHECK(units_dist.intervals().size() == 2); + CHECK(stl_dist.densities().size() == 2); + CHECK(units_dist.densities().size() == 2); + } + + SECTION("parametrized_input_it") + { + constexpr std::array intervals_rep = {1.0, 2.0, 3.0}; + constexpr std::array intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], + 3.0 * isq::length[si::metre]}; + constexpr std::array weights = {1.0, 2.0, 3.0}; + + auto stl_dist = + std::piecewise_linear_distribution(intervals_rep.cbegin(), intervals_rep.cend(), weights.cbegin()); + auto units_dist = + mp_units::piecewise_linear_distribution(intervals_qty.cbegin(), intervals_qty.cend(), weights.cbegin()); + + CHECK(stl_dist.intervals() == intervals_rep_vec); + CHECK(units_dist.intervals() == intervals_qty_vec); + CHECK(units_dist.densities() == stl_dist.densities()); + } + + SECTION("parametrized_initializer_list") + { + const std::initializer_list intervals_rep = {1.0, 2.0, 3.0}; + const std::initializer_list intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], + 3.0 * isq::length[si::metre]}; + + auto stl_dist = std::piecewise_linear_distribution(intervals_rep, [](rep val) { return val; }); + auto units_dist = mp_units::piecewise_linear_distribution( + intervals_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); + + CHECK(units_dist.intervals() == intervals_qty_vec); + CHECK(units_dist.densities() == stl_dist.densities()); + } + + SECTION("parametrized_range") + { + constexpr std::size_t nw = 2; + constexpr rep xmin_rep = 1.0, xmax_rep = 3.0; + constexpr q xmin_qty = 1.0 * isq::length[si::metre], xmax_qty = 3.0 * isq::length[si::metre]; + + auto stl_dist = std::piecewise_linear_distribution(nw, xmin_rep, xmax_rep, [](rep val) { return val; }); + auto units_dist = mp_units::piecewise_linear_distribution( + nw, xmin_qty, xmax_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); + + CHECK(units_dist.intervals() == intervals_qty_vec); + CHECK(units_dist.densities() == stl_dist.densities()); + } } - - SECTION("parametrized_range") - { - constexpr std::size_t count = 3; - constexpr double xmin = 1, xmax = 3; - - auto stl_dist = std::discrete_distribution(count, xmin, xmax, [](double val) { return val; }); - auto units_dist = mp_units::discrete_distribution(count, xmin, xmax, [](double val) { return val; }); - - CHECK(units_dist.probabilities() == stl_dist.probabilities()); - } -} - -TEST_CASE("piecewise_constant_distribution") -{ - using rep = long double; - using q = quantity; - - std::vector intervals_rep_vec = {1.0, 2.0, 3.0}; - std::vector intervals_qty_vec = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], - 3.0 * isq::length[si::metre]}; - - SECTION("default") - { - auto stl_dist = std::piecewise_constant_distribution(); - auto units_dist = mp_units::piecewise_constant_distribution(); - - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - CHECK(stl_dist.intervals().size() == 2); - CHECK(units_dist.intervals().size() == 2); - CHECK(stl_dist.densities().size() == 1); - CHECK(units_dist.densities().size() == 1); - } - - SECTION("parametrized_input_it") - { - constexpr std::array intervals_rep = {1.0, 2.0, 3.0}; - constexpr std::array intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], - 3.0 * isq::length[si::metre]}; - constexpr std::array weights = {1.0, 2.0, 3.0}; - - auto stl_dist = - std::piecewise_constant_distribution(intervals_rep.cbegin(), intervals_rep.cend(), weights.cbegin()); - auto units_dist = - mp_units::piecewise_constant_distribution(intervals_qty.cbegin(), intervals_qty.cend(), weights.cbegin()); - - CHECK(stl_dist.intervals() == intervals_rep_vec); - CHECK(units_dist.intervals() == intervals_qty_vec); - CHECK(units_dist.densities() == stl_dist.densities()); - } - - SECTION("parametrized_initializer_list") - { - const std::initializer_list intervals_rep = {1.0, 2.0, 3.0}; - const std::initializer_list intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], - 3.0 * isq::length[si::metre]}; - - auto stl_dist = std::piecewise_constant_distribution(intervals_rep, [](rep val) { return val; }); - auto units_dist = mp_units::piecewise_constant_distribution( - intervals_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); - - CHECK(units_dist.intervals() == intervals_qty_vec); - CHECK(units_dist.densities() == stl_dist.densities()); - } - - SECTION("parametrized_range") - { - constexpr std::size_t nw = 2; - constexpr rep xmin_rep = 1.0, xmax_rep = 3.0; - constexpr q xmin_qty = 1.0 * isq::length[si::metre], xmax_qty = 3.0 * isq::length[si::metre]; - - auto stl_dist = std::piecewise_constant_distribution(nw, xmin_rep, xmax_rep, [](rep val) { return val; }); - auto units_dist = mp_units::piecewise_constant_distribution( - nw, xmin_qty, xmax_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); - - CHECK(units_dist.intervals() == intervals_qty_vec); - CHECK(units_dist.densities() == stl_dist.densities()); - } -} - -TEST_CASE("piecewise_linear_distribution") -{ - using rep = long double; - using q = quantity; - - std::vector intervals_rep_vec = {1.0, 2.0, 3.0}; - std::vector intervals_qty_vec = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], - 3.0 * isq::length[si::metre]}; - - SECTION("default") - { - auto stl_dist = std::piecewise_linear_distribution(); - auto units_dist = mp_units::piecewise_linear_distribution(); - - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - CHECK(stl_dist.intervals().size() == 2); - CHECK(units_dist.intervals().size() == 2); - CHECK(stl_dist.densities().size() == 2); - CHECK(units_dist.densities().size() == 2); - } - - SECTION("parametrized_input_it") - { - constexpr std::array intervals_rep = {1.0, 2.0, 3.0}; - constexpr std::array intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], - 3.0 * isq::length[si::metre]}; - constexpr std::array weights = {1.0, 2.0, 3.0}; - - auto stl_dist = - std::piecewise_linear_distribution(intervals_rep.cbegin(), intervals_rep.cend(), weights.cbegin()); - auto units_dist = - mp_units::piecewise_linear_distribution(intervals_qty.cbegin(), intervals_qty.cend(), weights.cbegin()); - - CHECK(stl_dist.intervals() == intervals_rep_vec); - CHECK(units_dist.intervals() == intervals_qty_vec); - CHECK(units_dist.densities() == stl_dist.densities()); - } - - SECTION("parametrized_initializer_list") - { - const std::initializer_list intervals_rep = {1.0, 2.0, 3.0}; - const std::initializer_list intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], - 3.0 * isq::length[si::metre]}; - - auto stl_dist = std::piecewise_linear_distribution(intervals_rep, [](rep val) { return val; }); - auto units_dist = mp_units::piecewise_linear_distribution( - intervals_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); - - CHECK(units_dist.intervals() == intervals_qty_vec); - CHECK(units_dist.densities() == stl_dist.densities()); - } - - SECTION("parametrized_range") - { - constexpr std::size_t nw = 2; - constexpr rep xmin_rep = 1.0, xmax_rep = 3.0; - constexpr q xmin_qty = 1.0 * isq::length[si::metre], xmax_qty = 3.0 * isq::length[si::metre]; - - auto stl_dist = std::piecewise_linear_distribution(nw, xmin_rep, xmax_rep, [](rep val) { return val; }); - auto units_dist = mp_units::piecewise_linear_distribution( - nw, xmin_qty, xmax_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); - - CHECK(units_dist.intervals() == intervals_qty_vec); - CHECK(units_dist.densities() == stl_dist.densities()); - } -} +} \ No newline at end of file diff --git a/test/runtime/fixed_string_test.cpp b/test/runtime/fixed_string_test.cpp index b1b01f21..f3bc737a 100644 --- a/test/runtime/fixed_string_test.cpp +++ b/test/runtime/fixed_string_test.cpp @@ -38,19 +38,22 @@ import 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("in range") + SECTION("fixed_string::at") { - CHECK(txt.at(0) == 'a'); - CHECK(txt.at(1) == 'b'); - CHECK(txt.at(2) == 'c'); - } - 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")); + basic_fixed_string txt = "abc"; + SECTION("in range") + { + CHECK(txt.at(0) == 'a'); + CHECK(txt.at(1) == 'b'); + CHECK(txt.at(2) == 'c'); + } + 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")); + } } } diff --git a/test/runtime/fmt_test.cpp b/test/runtime/fmt_test.cpp index 03eef676..4011588e 100644 --- a/test/runtime/fmt_test.cpp +++ b/test/runtime/fmt_test.cpp @@ -55,7 +55,271 @@ constexpr bool mp_units::is_vector = true; using namespace mp_units; using namespace mp_units::si::unit_symbols; -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(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(m / s2); + CHECK(os.str() == "m/s^2"); + } + + SECTION("solidus") + { + os << unit_symbol(m / s2); + CHECK(os.str() == "m s⁻²"); + } + + SECTION("separator") + { + os << unit_symbol(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) == "kΩ"); + 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) == "kΩ"); + 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) == "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; @@ -375,266 +639,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]") -{ - 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::quiet_NaN() * isq::length[m]) == "nan"); - } - - SECTION("inf") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits::infinity() * isq::length[m]) == "inf"); - } - - SECTION("-inf") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N}", -std::numeric_limits::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]) == "kΩ"); - 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) == "kΩ"); - 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) == "kΩ"); - 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) == "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]") +TEST_CASE("quantity fill and align specification", "[quantity][ostream][fmt]") { SECTION("ostream") { @@ -726,356 +731,373 @@ TEST_CASE("quantity fill and align specification", "[text][fmt][ostream]") } } -TEST_CASE("sign specification", "[text][fmt]") +TEST_CASE("quantity subentities selection", "[quantity][fmt]") { - auto inf = std::numeric_limits::infinity() * si::metre; - auto nan = std::numeric_limits::quiet_NaN() * si::metre; - - SECTION("full format {:%N%?%U} on a quantity") + SECTION("quantity format string with only %N should print quantity value only") { - CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", 1 * isq::length[m]) == - "1m,+1m,1m, 1m"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", -1 * isq::length[m]) == - "-1m,-1m,-1m,-1m"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", inf) == - "infm,+infm,infm, infm"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", nan) == - "nanm,+nanm,nanm, nanm"); + 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::quiet_NaN() * isq::length[m]) == "nan"); + } + + SECTION("inf") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits::infinity() * isq::length[m]) == "inf"); + } + + SECTION("-inf") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N}", -std::numeric_limits::infinity() * isq::length[m]) == "-inf"); + } + } } - SECTION("value only format {:%N} on a quantity") + SECTION("quantity format string with only %U should print quantity unit symbol only") { - 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[ ]}", nan) == "nan,+nan,nan, nan"); + 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]) == "kΩ"); + 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) == "%"); + } + + 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"); } } } -TEST_CASE("precision specification", "[text][fmt]") +// 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("full format on a quantity") + SECTION("sign specification") { - SECTION("default spec") + auto inf = std::numeric_limits::infinity() * si::metre; + auto nan = std::numeric_limits::quiet_NaN() * si::metre; + + SECTION("full format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{::N[.0f]}", 1.2345 * isq::length[m]) == "1 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.1f]}", 1.2345 * isq::length[m]) == "1.2 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.2f]}", 1.2345 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3f]}", 1.2345 * isq::length[m]) == "1.234 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.4f]}", 1.2345 * isq::length[m]) == "1.2345 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.5f]}", 1.2345 * isq::length[m]) == "1.23450 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000 m"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", 1 * isq::length[m]) == + "1m,+1m,1m, 1m"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", -1 * isq::length[m]) == + "-1m,-1m,-1m,-1m"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", inf) == + "infm,+infm,infm, infm"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", nan) == + "nanm,+nanm,nanm, nanm"); } - SECTION("explicit spec") + SECTION("value only format {:%N} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.0f]}", 1.2345 * isq::length[m]) == "1 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.1f]}", 1.2345 * isq::length[m]) == "1.2 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.2f]}", 1.2345 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3f]}", 1.2345 * isq::length[m]) == "1.234 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000 m"); - } - - SECTION("modified spec") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.0f]}", 1.2345 * isq::length[m]) == "1m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.1f]}", 1.2345 * isq::length[m]) == "1.2m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.2f]}", 1.2345 * isq::length[m]) == "1.23m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3f]}", 1.2345 * isq::length[m]) == "1.234m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000m"); + 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[ ]}", nan) == "nan,+nan,nan, nan"); } } - SECTION("value only format {:%N} on a quantity") + SECTION("precision specification") { - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.0f]}", 1.2345 * isq::length[m]) == "1"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.1f]}", 1.2345 * isq::length[m]) == "1.2"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.2f]}", 1.2345 * isq::length[m]) == "1.23"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3f]}", 1.2345 * isq::length[m]) == "1.234"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345"); - 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"); - } -} - -TEST_CASE("type specification", "[text][fmt]") -{ - SECTION("full format {:%N%?%U} on a quantity") - { - SECTION("default spec") + SECTION("full format on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{::N[b]}", 42 * isq::length[m]) == "101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[B]}", 42 * isq::length[m]) == "101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[d]}", 42 * isq::length[m]) == "42 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[o]}", 42 * isq::length[m]) == "52 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[x]}", 42 * isq::length[m]) == "2a m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[X]}", 42 * isq::length[m]) == "2A m"); + SECTION("default spec") + { + CHECK(MP_UNITS_STD_FMT::format("{::N[.0f]}", 1.2345 * isq::length[m]) == "1 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.1f]}", 1.2345 * isq::length[m]) == "1.2 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.2f]}", 1.2345 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3f]}", 1.2345 * isq::length[m]) == "1.234 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.4f]}", 1.2345 * isq::length[m]) == "1.2345 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.5f]}", 1.2345 * isq::length[m]) == "1.23450 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000 m"); + } + + SECTION("explicit spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.0f]}", 1.2345 * isq::length[m]) == "1 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.1f]}", 1.2345 * isq::length[m]) == "1.2 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.2f]}", 1.2345 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3f]}", 1.2345 * isq::length[m]) == "1.234 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000 m"); + } + + SECTION("modified spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.0f]}", 1.2345 * isq::length[m]) == "1m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.1f]}", 1.2345 * isq::length[m]) == "1.2m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.2f]}", 1.2345 * isq::length[m]) == "1.23m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3f]}", 1.2345 * isq::length[m]) == "1.234m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000m"); + } + } + + SECTION("value only format {:%N} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.0f]}", 1.2345 * isq::length[m]) == "1"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.1f]}", 1.2345 * isq::length[m]) == "1.2"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.2f]}", 1.2345 * isq::length[m]) == "1.23"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3f]}", 1.2345 * isq::length[m]) == "1.234"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345"); + 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"); + } + } + + SECTION("type specification") + { + SECTION("full format {:%N%?%U} on a quantity") + { + SECTION("default spec") + { + CHECK(MP_UNITS_STD_FMT::format("{::N[b]}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[B]}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[d]}", 42 * isq::length[m]) == "42 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[o]}", 42 * isq::length[m]) == "52 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[x]}", 42 * isq::length[m]) == "2a m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[X]}", 42 * isq::length[m]) == "2A m"); #if MP_UNITS_USE_FMTLIB - CHECK(MP_UNITS_STD_FMT::format("{::N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); #else - CHECK(MP_UNITS_STD_FMT::format("{::N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0 m"); #endif - CHECK(MP_UNITS_STD_FMT::format("{::N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[g]}", 1.2345678 * isq::length[m]) == "1.23457 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3g]}", 1.2345678 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[G]}", 1.2345678 * isq::length[m]) == "1.23457 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3G]}", 1.2345678 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); - } + CHECK(MP_UNITS_STD_FMT::format("{::N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[g]}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3g]}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[G]}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3G]}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); + } - SECTION("explicit spec") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[b]}", 42 * isq::length[m]) == "101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[B]}", 42 * isq::length[m]) == "101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[d]}", 42 * isq::length[m]) == "42 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[o]}", 42 * isq::length[m]) == "52 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[x]}", 42 * isq::length[m]) == "2a m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[X]}", 42 * isq::length[m]) == "2A m"); + SECTION("explicit spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[b]}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[B]}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[d]}", 42 * isq::length[m]) == "42 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[o]}", 42 * isq::length[m]) == "52 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[x]}", 42 * isq::length[m]) == "2a m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[X]}", 42 * isq::length[m]) == "2A m"); #if MP_UNITS_USE_FMTLIB - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); #else - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0 m"); #endif - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[g]}", 1.2345678 * isq::length[m]) == "1.23457 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3g]}", 1.2345678 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[G]}", 1.2345678 * isq::length[m]) == "1.23457 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); - } + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[g]}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3g]}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[G]}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); + } - SECTION("modified spec") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[b]}", 42 * isq::length[m]) == "101010m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[B]}", 42 * isq::length[m]) == "101010m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[d]}", 42 * isq::length[m]) == "42m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[o]}", 42 * isq::length[m]) == "52m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[x]}", 42 * isq::length[m]) == "2am"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[X]}", 42 * isq::length[m]) == "2Am"); + SECTION("modified spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[b]}", 42 * isq::length[m]) == "101010m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[B]}", 42 * isq::length[m]) == "101010m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[d]}", 42 * isq::length[m]) == "42m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[o]}", 42 * isq::length[m]) == "52m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[x]}", 42 * isq::length[m]) == "2am"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[X]}", 42 * isq::length[m]) == "2Am"); #if MP_UNITS_USE_FMTLIB - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0m"); #else - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0m"); #endif - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[g]}", 1.2345678 * isq::length[m]) == "1.23457m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3g]}", 1.2345678 * isq::length[m]) == "1.23m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[G]}", 1.2345678 * isq::length[m]) == "1.23457m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[g]}", 1.2345678 * isq::length[m]) == "1.23457m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3g]}", 1.2345678 * isq::length[m]) == "1.23m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[G]}", 1.2345678 * isq::length[m]) == "1.23457m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08m"); + } } - } - SECTION("value only format {:%N} on a quantity") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[b]}", 42 * isq::length[m]) == "101010"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[B]}", 42 * isq::length[m]) == "101010"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[d]}", 42 * isq::length[m]) == "42"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[o]}", 42 * isq::length[m]) == "52"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[x]}", 42 * isq::length[m]) == "2a"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[X]}", 42 * isq::length[m]) == "2A"); + SECTION("value only format {:%N} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[b]}", 42 * isq::length[m]) == "101010"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[B]}", 42 * isq::length[m]) == "101010"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[d]}", 42 * isq::length[m]) == "42"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[o]}", 42 * isq::length[m]) == "52"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[x]}", 42 * isq::length[m]) == "2a"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[X]}", 42 * isq::length[m]) == "2A"); #if MP_UNITS_USE_FMTLIB - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0"); #else - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0"); #endif - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[g]}", 1.2345678 * isq::length[m]) == "1.23457"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08"); - 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[G]}", 1.2345678 * isq::length[m]) == "1.23457"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08"); - 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"); - } -} - -TEST_CASE("different base types with the # specifier", "[text][fmt]") -{ - SECTION("full format {:%N%?%U} on a quantity") - { - SECTION("default spec") - { - CHECK(MP_UNITS_STD_FMT::format("{::N[#b]}", 42 * isq::length[m]) == "0b101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[#B]}", 42 * isq::length[m]) == "0B101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[#o]}", 42 * isq::length[m]) == "052 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[#x]}", 42 * isq::length[m]) == "0x2a m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[#X]}", 42 * isq::length[m]) == "0X2A m"); - } - - SECTION("explicit spec") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#b]}", 42 * isq::length[m]) == "0b101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#B]}", 42 * isq::length[m]) == "0B101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#o]}", 42 * isq::length[m]) == "052 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#x]}", 42 * isq::length[m]) == "0x2a m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#X]}", 42 * isq::length[m]) == "0X2A m"); - } - - SECTION("modified spec") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#b]}", 42 * isq::length[m]) == "0b101010m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#B]}", 42 * isq::length[m]) == "0B101010m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#o]}", 42 * isq::length[m]) == "052m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#x]}", 42 * isq::length[m]) == "0x2am"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#X]}", 42 * isq::length[m]) == "0X2Am"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[g]}", 1.2345678 * isq::length[m]) == "1.23457"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08"); + 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[G]}", 1.2345678 * isq::length[m]) == "1.23457"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08"); + 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"); } } - SECTION("value only format {:%N} on a quantity") + SECTION("different base types with the # specifier") { - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#b]}", 42 * isq::length[m]) == "0b101010"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#B]}", 42 * isq::length[m]) == "0B101010"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#o]}", 42 * isq::length[m]) == "052"); - 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"); + SECTION("full format {:%N%?%U} on a quantity") + { + SECTION("default spec") + { + CHECK(MP_UNITS_STD_FMT::format("{::N[#b]}", 42 * isq::length[m]) == "0b101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[#B]}", 42 * isq::length[m]) == "0B101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[#o]}", 42 * isq::length[m]) == "052 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[#x]}", 42 * isq::length[m]) == "0x2a m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[#X]}", 42 * isq::length[m]) == "0X2A m"); + } + + SECTION("explicit spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#b]}", 42 * isq::length[m]) == "0b101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#B]}", 42 * isq::length[m]) == "0B101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#o]}", 42 * isq::length[m]) == "052 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#x]}", 42 * isq::length[m]) == "0x2a m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#X]}", 42 * isq::length[m]) == "0X2A m"); + } + + SECTION("modified spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#b]}", 42 * isq::length[m]) == "0b101010m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#B]}", 42 * isq::length[m]) == "0B101010m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#o]}", 42 * isq::length[m]) == "052m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#x]}", 42 * isq::length[m]) == "0x2am"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#X]}", 42 * isq::length[m]) == "0X2Am"); + } + } + + SECTION("value only format {:%N} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#b]}", 42 * isq::length[m]) == "0b101010"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#B]}", 42 * isq::length[m]) == "0B101010"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#o]}", 42 * isq::length[m]) == "052"); + 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]") -{ - struct group2 : std::numpunct { - [[nodiscard]] char do_thousands_sep() const override { return '_'; } - [[nodiscard]] std::string do_grouping() const override { return "\2"; } - }; - - struct group3 : std::numpunct { - [[nodiscard]] char do_thousands_sep() const override { return '\''; } - [[nodiscard]] std::string do_grouping() const override { return "\3"; } - }; - - const std::locale grp2{std::locale::classic(), new group2}; - const std::locale grp3{std::locale::classic(), new group3}; - - SECTION("full format on a quantity") + SECTION("localization with the 'L' specifier") { - SECTION("default spec") - { - CHECK(MP_UNITS_STD_FMT::format(grp2, "{::N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); - CHECK(MP_UNITS_STD_FMT::format(grp3, "{::N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); - } + struct group2 : std::numpunct { + [[nodiscard]] char do_thousands_sep() const override { return '_'; } + [[nodiscard]] std::string do_grouping() const override { return "\2"; } + }; - SECTION("explicit spec") - { - CHECK(MP_UNITS_STD_FMT::format(grp2, "{:%N%?%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); - CHECK(MP_UNITS_STD_FMT::format(grp3, "{:%N%?%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); - } + struct group3 : std::numpunct { + [[nodiscard]] char do_thousands_sep() const override { return '\''; } + [[nodiscard]] std::string do_grouping() const override { return "\3"; } + }; - SECTION("modified spec") + const std::locale grp2{std::locale::classic(), new group2}; + const std::locale grp3{std::locale::classic(), new group3}; + + SECTION("full format on a quantity") { - CHECK(MP_UNITS_STD_FMT::format(grp2, "{:%N%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58m/s"); - CHECK(MP_UNITS_STD_FMT::format(grp3, "{:%N%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458m/s"); + SECTION("default spec") + { + CHECK(MP_UNITS_STD_FMT::format(grp2, "{::N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); + CHECK(MP_UNITS_STD_FMT::format(grp3, "{::N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); + } + + SECTION("explicit spec") + { + CHECK(MP_UNITS_STD_FMT::format(grp2, "{:%N%?%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); + CHECK(MP_UNITS_STD_FMT::format(grp3, "{:%N%?%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); + } + + SECTION("modified spec") + { + CHECK(MP_UNITS_STD_FMT::format(grp2, "{:%N%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58m/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(m / s2); - CHECK(os.str() == "m/s^2"); - } - - SECTION("solidus") - { - os << unit_symbol(m / s2); - CHECK(os.str() == "m s⁻²"); - } - - SECTION("separator") - { - os << unit_symbol(m / s2); - CHECK(os.str() == "m⋅s⁻²"); - } -} - -TEST_CASE("dimension_symbol", "[text]") -{ - 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(isq::power.dimension); - CHECK(os.str() == "L^2MT^-3"); - } -} - -TEST_CASE("value_cast", "[text][ostream]") +TEST_CASE("check if `value_cast` properly changes the numerical value of a quantity", "[value_cast][ostream]") { std::ostringstream os; diff --git a/test/runtime/math_test.cpp b/test/runtime/math_test.cpp index 79018bff..994c9eeb 100644 --- a/test/runtime/math_test.cpp +++ b/test/runtime/math_test.cpp @@ -43,486 +43,488 @@ using namespace mp_units::si::unit_symbols; // classical -TEST_CASE("'pow()' 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<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") + SECTION("'pow()' on quantity changes the value and the dimension accordingly") { - 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]); - } -} - -TEST_CASE("'sqrt()' on quantity changes the value and the dimension accordingly", "[math][sqrt]") -{ - REQUIRE(sqrt(4 * isq::area[m2]) == 2 * isq::length[m]); -} - -TEST_CASE("'cbrt()' on quantity changes the value and the dimension accordingly", "[math][cbrt]") -{ - REQUIRE(cbrt(8 * isq::volume[m3]) == 2 * isq::length[m]); -} - -TEST_CASE("'fma()' on quantity changes the value and the dimension accordingly", "[math][fma]") -{ - 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)); -} - -TEST_CASE("fmod functions", "[math][fmod]") -{ - SECTION("fmod should work on the same quantities") - { - 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(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 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]); - 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]); - } -} - -TEST_CASE("remainder functions", "[math][remainder]") -{ - 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]); - 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 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]); - 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]); - } -} - -TEST_CASE("'isfinite()' accepts dimensioned arguments", "[math][isfinite]") { REQUIRE(isfinite(4.0 * isq::length[m])); } - -TEST_CASE("'isinf()' accepts dimensioned arguments", "[math][isinf]") { REQUIRE(!isinf(4.0 * isq::length[m])); } - -TEST_CASE("'isnan()' accepts dimensioned arguments", "[math][isnan]") { REQUIRE(!isnan(4.0 * isq::length[m])); } - - -TEST_CASE("'pow()' on quantity changes the value and the dimension accordingly", "[math][pow]") -{ - REQUIRE(pow<1, 4>(16 * isq::area[m2]) == sqrt(4 * isq::length[m])); -} - -// TODO add tests for exp() - -TEST_CASE("absolute functions on quantity returns the absolute value", "[math][abs][fabs]") -{ - SECTION("'abs()' on a negative quantity returns the abs") - { - 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]); } + REQUIRE(sqrt(4 * isq::area[m2]) == 2 * isq::length[m]); } - SECTION("'abs()' on a positive quantity returns the abs") + SECTION("'cbrt()' on quantity changes the value and the dimension accordingly") { - 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]); } - } -} - -TEST_CASE("numeric_limits functions", "[limits]") -{ - SECTION("'epsilon' works as expected using default floating type") - { - REQUIRE(epsilon(isq::length[m]).numerical_value_in(m) == - std::numeric_limits::epsilon()); - } - SECTION("'epsilon' works as expected using integers") - { - REQUIRE(epsilon(isq::length[m]).numerical_value_in(m) == - std::numeric_limits::epsilon()); - } -} - -TEST_CASE("floor functions", "[floor]") -{ - SECTION("floor 1 second with target unit second should be 1 second") - { - REQUIRE(floor(1 * isq::time[s]) == 1 * isq::time[s]); - } - SECTION("floor 1000 milliseconds with target unit second should be 1 second") - { - REQUIRE(floor(1000 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("floor 1001 milliseconds with target unit second should be 1 second") - { - REQUIRE(floor(1001 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("floor 1999 milliseconds with target unit second should be 1 second") - { - REQUIRE(floor(1999 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("floor -1000 milliseconds with target unit second should be -1 second") - { - REQUIRE(floor(-1000 * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("floor -999 milliseconds with target unit second should be -1 second") - { - REQUIRE(floor(-999 * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("floor 1.3 seconds with target unit second should be 1 second") - { - REQUIRE(floor(1.3 * isq::time[s]) == 1 * isq::time[s]); - } - SECTION("floor -1.3 seconds with target unit second should be -2 seconds") - { - REQUIRE(floor(-1.3 * isq::time[s]) == -2 * isq::time[s]); - } - SECTION("floor 1001. milliseconds with target unit second should be 1 second") - { - REQUIRE(floor(1001. * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("floor 1999. milliseconds with target unit second should be 1 second") - { - REQUIRE(floor(1999. * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("floor -1000. milliseconds with target unit second should be -1 second") - { - REQUIRE(floor(-1000. * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("floor -999. milliseconds with target unit second should be -1 second") - { - REQUIRE(floor(-999. * isq::time[ms]) == -1 * isq::time[s]); + REQUIRE(cbrt(8 * isq::volume[m3]) == 2 * isq::length[m]); } - // TODO Add tests for `N`, `kN` and `kg * m / s2` i `kg * km / s2` -} - -TEST_CASE("ceil functions", "[ceil]") -{ - SECTION("ceil 1 second with target unit second should be 1 second") + SECTION("'fma()' on quantity changes the value and the dimension accordingly") { - REQUIRE(ceil(1 * isq::time[s]) == 1 * isq::time[s]); - } - SECTION("ceil 1000 milliseconds with target unit second should be 1 second") - { - REQUIRE(ceil(1000 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("ceil 1001 milliseconds with target unit second should be 2 seconds") - { - REQUIRE(ceil(1001 * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("ceil 1999 milliseconds with target unit second should be 2 seconds") - { - REQUIRE(ceil(1999 * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("ceil -1000 milliseconds with target unit second should be -1 second") - { - REQUIRE(ceil(-1000 * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("ceil -999 milliseconds with target unit second should be 0 seconds") - { - REQUIRE(ceil(-999 * isq::time[ms]) == 0 * isq::time[s]); - } - SECTION("ceil 1.3 seconds with target unit second should be 2 seconds") - { - REQUIRE(ceil(1.3 * isq::time[s]) == 2 * isq::time[s]); - } - SECTION("ceil -1.3 seconds with target unit second should be -1 second") - { - REQUIRE(ceil(-1.3 * isq::time[s]) == -1 * isq::time[s]); - } - SECTION("ceil 1001. milliseconds with target unit second should be 2 seconds") - { - REQUIRE(ceil(1001. * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("ceil 1999. milliseconds with target unit second should be 2 seconds") - { - REQUIRE(ceil(1999. * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("ceil -1000. milliseconds with target unit second should be -1 second") - { - REQUIRE(ceil(-1000. * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("ceil -999. milliseconds with target unit second should be 0 seconds") - { - REQUIRE(ceil(-999. * isq::time[ms]) == 0 * isq::time[s]); - } -} - -TEST_CASE("round functions", "[round]") -{ - SECTION("round 1 second with target unit second should be 1 second") - { - REQUIRE(round(1 * isq::time[s]) == 1 * isq::time[s]); - } - SECTION("round 1000 milliseconds with target unit second should be 1 second") - { - REQUIRE(round(1000 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("round 1001 milliseconds with target unit second should be 1 second") - { - REQUIRE(round(1001 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("round 1499 milliseconds with target unit second should be 1 second") - { - REQUIRE(round(1499 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("round 1500 milliseconds with target unit second should be 2 seconds") - { - REQUIRE(round(1500 * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("round 1999 milliseconds with target unit second should be 2 seconds") - { - REQUIRE(round(1999 * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("round -1000 milliseconds with target unit second should be -1 second") - { - REQUIRE(round(-1000 * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("round -1001 milliseconds with target unit second should be -1 second") - { - REQUIRE(round(-1001 * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("round -1499 milliseconds with target unit second should be -1 second") - { - REQUIRE(round(-1499 * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("round -1500 milliseconds with target unit second should be -2 seconds") - { - REQUIRE(round(-1500 * isq::time[ms]) == -2 * isq::time[s]); - } - SECTION("round -1999 milliseconds with target unit second should be -2 seconds") - { - REQUIRE(round(-1999 * isq::time[ms]) == -2 * isq::time[s]); - } - SECTION("round 1000. milliseconds with target unit second should be 1 second") - { - REQUIRE(round(1000. * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("round 1001. milliseconds with target unit second should be 1 second") - { - REQUIRE(round(1001. * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("round 1499. milliseconds with target unit second should be 1 second") - { - REQUIRE(round(1499. * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("round 1500. milliseconds with target unit second should be 2 seconds") - { - REQUIRE(round(1500. * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("round 1999. milliseconds with target unit second should be 2 seconds") - { - REQUIRE(round(1999. * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("round -1000. milliseconds with target unit second should be -1 second") - { - REQUIRE(round(-1000. * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("round -1001. milliseconds with target unit second should be -1 second") - { - REQUIRE(round(-1001. * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("round -1499. milliseconds with target unit second should be -1 second") - { - REQUIRE(round(-1499. * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("round -1500. milliseconds with target unit second should be -2 seconds") - { - REQUIRE(round(-1500. * isq::time[ms]) == -2 * isq::time[s]); - } - SECTION("round -1999. milliseconds with target unit second should be -2 seconds") - { - REQUIRE(round(-1999. * isq::time[ms]) == -2 * isq::time[s]); - } -} - -TEST_CASE("hypot functions", "[hypot]") -{ - 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]); - } - SECTION("hypot should work with different units of the same dimension") - { - 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]); - } -} - -TEST_CASE("SI trigonometric functions", "[trig][si]") -{ - SECTION("sin") - { - REQUIRE_THAT(si::sin(0 * deg), AlmostEquals(0. * one)); - REQUIRE_THAT(si::sin(90 * deg), AlmostEquals(1. * one)); - REQUIRE_THAT(si::sin(180 * deg), AlmostEquals(0. * one)); - REQUIRE_THAT(si::sin(270 * deg), AlmostEquals(-1. * one)); + 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)); } - SECTION("cos") + SECTION("fmod functions") { - REQUIRE_THAT(si::cos(0 * 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("fmod should work on the same quantities") + { + 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(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 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]); + 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("tan") + SECTION("remainder functions") { - REQUIRE_THAT(si::tan(0 * 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)); - } -} - -TEST_CASE("SI inverse trigonometric functions", "[inv trig][si]") -{ - SECTION("asin") - { - 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("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]); + 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 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]); + 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("acos") + SECTION("'isfinite()' accepts dimensioned arguments") { REQUIRE(isfinite(4.0 * 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])); } + + + SECTION("'pow()' on quantity changes the value and the dimension accordingly") { - 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(pow<1, 4>(16 * isq::area[m2]) == sqrt(4 * isq::length[m])); } - SECTION("atan") + // TODO add tests for exp() + + SECTION("absolute functions on quantity returns the absolute value") { - REQUIRE_THAT(si::atan(-1 * one), AlmostEquals(-45. * deg)); - REQUIRE_THAT(si::atan(0 * one), AlmostEquals(0. * deg)); - REQUIRE_THAT(si::atan(1 * one), AlmostEquals(45. * deg)); - } -} + SECTION("'abs()' on a negative quantity returns the abs") + { + SECTION("integral representation") { REQUIRE(abs(-1 * isq::length[m]) == 1 * isq::length[m]); } -TEST_CASE("SI atan2 functions", "[atan2][si]") -{ - 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)); - REQUIRE_THAT(si::atan2(1. * isq::length[km], 1. * isq::length[km]), 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)); - 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)); - } -} + 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]); } -TEST_CASE("Angle trigonometric functions", "[trig][angle]") -{ - using namespace mp_units::angular; - using namespace mp_units::angular::unit_symbols; - using mp_units::angular::unit_symbols::deg; - - SECTION("sin") - { - REQUIRE_THAT(sin(0 * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(sin(90 * angle[deg]), AlmostEquals(1. * one)); - REQUIRE_THAT(sin(180 * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(sin(270 * angle[deg]), AlmostEquals(-1. * one)); - - REQUIRE_THAT(sin(0 * angle[grad]), AlmostEquals(0. * one)); - REQUIRE_THAT(sin(100 * angle[grad]), AlmostEquals(1. * one)); - REQUIRE_THAT(sin(200 * angle[grad]), AlmostEquals(0. * one, 2)); - REQUIRE_THAT(sin(300 * angle[grad]), AlmostEquals(-1. * one)); + SECTION("floating-point representation") { REQUIRE(abs(1. * isq::length[m]) == 1 * isq::length[m]); } + } } - SECTION("cos") + SECTION("numeric_limits functions") { - REQUIRE_THAT(cos(0 * angle[deg]), AlmostEquals(1. * one)); - REQUIRE_THAT(cos(90 * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(cos(180 * angle[deg]), AlmostEquals(-1. * one)); - REQUIRE_THAT(cos(270 * angle[deg]), AlmostEquals(0. * one)); - - REQUIRE_THAT(cos(0 * angle[grad]), AlmostEquals(1. * one)); - REQUIRE_THAT(cos(100 * angle[grad]), AlmostEquals(0. * one)); - REQUIRE_THAT(cos(200 * angle[grad]), AlmostEquals(-1. * one)); - REQUIRE_THAT(cos(300 * angle[grad]), AlmostEquals(0. * one)); + SECTION("'epsilon' works as expected using default floating type") + { + REQUIRE(epsilon(isq::length[m]).numerical_value_in(m) == + std::numeric_limits::epsilon()); + } + SECTION("'epsilon' works as expected using integers") + { + REQUIRE(epsilon(isq::length[m]).numerical_value_in(m) == + std::numeric_limits::epsilon()); + } } - SECTION("tan") + SECTION("floor functions") { - REQUIRE_THAT(tan(0 * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(tan(45 * angle[deg]), AlmostEquals(1. * one)); - REQUIRE_THAT(tan(135 * angle[deg]), AlmostEquals(-1. * one)); - REQUIRE_THAT(tan(180 * angle[deg]), AlmostEquals(0. * one)); + SECTION("floor 1 second with target unit second should be 1 second") + { + REQUIRE(floor(1 * isq::time[s]) == 1 * isq::time[s]); + } + SECTION("floor 1000 milliseconds with target unit second should be 1 second") + { + REQUIRE(floor(1000 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("floor 1001 milliseconds with target unit second should be 1 second") + { + REQUIRE(floor(1001 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("floor 1999 milliseconds with target unit second should be 1 second") + { + REQUIRE(floor(1999 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("floor -1000 milliseconds with target unit second should be -1 second") + { + REQUIRE(floor(-1000 * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("floor -999 milliseconds with target unit second should be -1 second") + { + REQUIRE(floor(-999 * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("floor 1.3 seconds with target unit second should be 1 second") + { + REQUIRE(floor(1.3 * isq::time[s]) == 1 * isq::time[s]); + } + SECTION("floor -1.3 seconds with target unit second should be -2 seconds") + { + REQUIRE(floor(-1.3 * isq::time[s]) == -2 * isq::time[s]); + } + SECTION("floor 1001. milliseconds with target unit second should be 1 second") + { + REQUIRE(floor(1001. * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("floor 1999. milliseconds with target unit second should be 1 second") + { + REQUIRE(floor(1999. * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("floor -1000. milliseconds with target unit second should be -1 second") + { + REQUIRE(floor(-1000. * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("floor -999. milliseconds with target unit second should be -1 second") + { + REQUIRE(floor(-999. * isq::time[ms]) == -1 * isq::time[s]); + } - REQUIRE_THAT(tan(0 * angle[grad]), AlmostEquals(0. * one)); - REQUIRE_THAT(tan(50 * angle[grad]), AlmostEquals(1. * one)); - REQUIRE_THAT(tan(150 * angle[grad]), AlmostEquals(-1. * one)); - REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one, 2)); - } -} - -TEST_CASE("Angle inverse trigonometric functions", "[inv trig][angle]") -{ - using namespace mp_units::angular; - using namespace mp_units::angular::unit_symbols; - using mp_units::angular::unit_symbols::deg; - - SECTION("asin") - { - REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg])); - REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg])); - REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg])); + // TODO Add tests for `N`, `kN` and `kg * m / s2` i `kg * km / s2` } - SECTION("acos") + SECTION("ceil functions") { - REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg])); - REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg])); - REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg])); + SECTION("ceil 1 second with target unit second should be 1 second") + { + REQUIRE(ceil(1 * isq::time[s]) == 1 * isq::time[s]); + } + SECTION("ceil 1000 milliseconds with target unit second should be 1 second") + { + REQUIRE(ceil(1000 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("ceil 1001 milliseconds with target unit second should be 2 seconds") + { + REQUIRE(ceil(1001 * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("ceil 1999 milliseconds with target unit second should be 2 seconds") + { + REQUIRE(ceil(1999 * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("ceil -1000 milliseconds with target unit second should be -1 second") + { + REQUIRE(ceil(-1000 * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("ceil -999 milliseconds with target unit second should be 0 seconds") + { + REQUIRE(ceil(-999 * isq::time[ms]) == 0 * isq::time[s]); + } + SECTION("ceil 1.3 seconds with target unit second should be 2 seconds") + { + REQUIRE(ceil(1.3 * isq::time[s]) == 2 * isq::time[s]); + } + SECTION("ceil -1.3 seconds with target unit second should be -1 second") + { + REQUIRE(ceil(-1.3 * isq::time[s]) == -1 * isq::time[s]); + } + SECTION("ceil 1001. milliseconds with target unit second should be 2 seconds") + { + REQUIRE(ceil(1001. * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("ceil 1999. milliseconds with target unit second should be 2 seconds") + { + REQUIRE(ceil(1999. * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("ceil -1000. milliseconds with target unit second should be -1 second") + { + REQUIRE(ceil(-1000. * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("ceil -999. milliseconds with target unit second should be 0 seconds") + { + REQUIRE(ceil(-999. * isq::time[ms]) == 0 * isq::time[s]); + } } - SECTION("atan") + SECTION("round functions") { - REQUIRE_THAT(atan(-1 * one), AlmostEquals(-45. * angle[deg])); - REQUIRE_THAT(atan(0 * one), AlmostEquals(0. * angle[deg])); - REQUIRE_THAT(atan(1 * one), AlmostEquals(45. * angle[deg])); + SECTION("round 1 second with target unit second should be 1 second") + { + REQUIRE(round(1 * isq::time[s]) == 1 * isq::time[s]); + } + SECTION("round 1000 milliseconds with target unit second should be 1 second") + { + REQUIRE(round(1000 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("round 1001 milliseconds with target unit second should be 1 second") + { + REQUIRE(round(1001 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("round 1499 milliseconds with target unit second should be 1 second") + { + REQUIRE(round(1499 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("round 1500 milliseconds with target unit second should be 2 seconds") + { + REQUIRE(round(1500 * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("round 1999 milliseconds with target unit second should be 2 seconds") + { + REQUIRE(round(1999 * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("round -1000 milliseconds with target unit second should be -1 second") + { + REQUIRE(round(-1000 * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("round -1001 milliseconds with target unit second should be -1 second") + { + REQUIRE(round(-1001 * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("round -1499 milliseconds with target unit second should be -1 second") + { + REQUIRE(round(-1499 * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("round -1500 milliseconds with target unit second should be -2 seconds") + { + REQUIRE(round(-1500 * isq::time[ms]) == -2 * isq::time[s]); + } + SECTION("round -1999 milliseconds with target unit second should be -2 seconds") + { + REQUIRE(round(-1999 * isq::time[ms]) == -2 * isq::time[s]); + } + SECTION("round 1000. milliseconds with target unit second should be 1 second") + { + REQUIRE(round(1000. * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("round 1001. milliseconds with target unit second should be 1 second") + { + REQUIRE(round(1001. * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("round 1499. milliseconds with target unit second should be 1 second") + { + REQUIRE(round(1499. * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("round 1500. milliseconds with target unit second should be 2 seconds") + { + REQUIRE(round(1500. * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("round 1999. milliseconds with target unit second should be 2 seconds") + { + REQUIRE(round(1999. * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("round -1000. milliseconds with target unit second should be -1 second") + { + REQUIRE(round(-1000. * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("round -1001. milliseconds with target unit second should be -1 second") + { + REQUIRE(round(-1001. * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("round -1499. milliseconds with target unit second should be -1 second") + { + REQUIRE(round(-1499. * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("round -1500. milliseconds with target unit second should be -2 seconds") + { + REQUIRE(round(-1500. * isq::time[ms]) == -2 * isq::time[s]); + } + SECTION("round -1999. milliseconds with target unit second should be -2 seconds") + { + REQUIRE(round(-1999. * isq::time[ms]) == -2 * isq::time[s]); + } } -} -TEST_CASE("Angle atan2 functions", "[atan2][angle]") -{ - using namespace mp_units::angular; - using namespace mp_units::angular::unit_symbols; - using mp_units::angular::unit_symbols::deg; + SECTION("hypot functions") + { + 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]); + } + SECTION("hypot should work with different units of the same dimension") + { + 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("atan2 should work on the same quantities") + SECTION("SI trigonometric functions") { - REQUIRE_THAT(atan2(-1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(-45. * angle[deg])); - REQUIRE_THAT(atan2(0. * isq::length[km], 1. * isq::length[km]), AlmostEquals(0. * angle[deg])); - REQUIRE_THAT(atan2(1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(45. * angle[deg])); + SECTION("sin") + { + REQUIRE_THAT(si::sin(0 * deg), AlmostEquals(0. * one)); + REQUIRE_THAT(si::sin(90 * deg), AlmostEquals(1. * one)); + REQUIRE_THAT(si::sin(180 * deg), AlmostEquals(0. * one)); + REQUIRE_THAT(si::sin(270 * deg), AlmostEquals(-1. * one)); + } + + SECTION("cos") + { + REQUIRE_THAT(si::cos(0 * 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("tan") + { + REQUIRE_THAT(si::tan(0 * 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("atan2 should work with different units of the same dimension") + + SECTION("SI inverse trigonometric functions") { - 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])); + SECTION("asin") + { + 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") + { + 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("atan") + { + REQUIRE_THAT(si::atan(-1 * one), AlmostEquals(-45. * deg)); + REQUIRE_THAT(si::atan(0 * one), AlmostEquals(0. * deg)); + REQUIRE_THAT(si::atan(1 * one), AlmostEquals(45. * deg)); + } } -} + + SECTION("SI atan2 functions") + { + 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)); + REQUIRE_THAT(si::atan2(1. * isq::length[km], 1. * isq::length[km]), 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)); + 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)); + } + } + + SECTION("Angle trigonometric functions") + { + using namespace mp_units::angular; + using namespace mp_units::angular::unit_symbols; + using mp_units::angular::unit_symbols::deg; + + SECTION("sin") + { + REQUIRE_THAT(sin(0 * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(sin(90 * angle[deg]), AlmostEquals(1. * one)); + REQUIRE_THAT(sin(180 * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(sin(270 * angle[deg]), AlmostEquals(-1. * one)); + + REQUIRE_THAT(sin(0 * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(sin(100 * angle[grad]), AlmostEquals(1. * one)); + REQUIRE_THAT(sin(200 * angle[grad]), AlmostEquals(0. * one, 2)); + REQUIRE_THAT(sin(300 * angle[grad]), AlmostEquals(-1. * one)); + } + + SECTION("cos") + { + REQUIRE_THAT(cos(0 * angle[deg]), AlmostEquals(1. * one)); + REQUIRE_THAT(cos(90 * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(cos(180 * angle[deg]), AlmostEquals(-1. * one)); + REQUIRE_THAT(cos(270 * angle[deg]), AlmostEquals(0. * one)); + + REQUIRE_THAT(cos(0 * angle[grad]), AlmostEquals(1. * one)); + REQUIRE_THAT(cos(100 * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(cos(200 * angle[grad]), AlmostEquals(-1. * one)); + REQUIRE_THAT(cos(300 * angle[grad]), AlmostEquals(0. * one)); + } + + SECTION("tan") + { + REQUIRE_THAT(tan(0 * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(tan(45 * angle[deg]), AlmostEquals(1. * one)); + 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)); + REQUIRE_THAT(tan(50 * angle[grad]), AlmostEquals(1. * one)); + REQUIRE_THAT(tan(150 * angle[grad]), AlmostEquals(-1. * one)); + REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one, 2)); + } + } + + SECTION("Angle inverse trigonometric functions") + { + using namespace mp_units::angular; + using namespace mp_units::angular::unit_symbols; + using mp_units::angular::unit_symbols::deg; + + SECTION("asin") + { + REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg])); + REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg])); + REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg])); + } + + SECTION("acos") + { + REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg])); + REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg])); + REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg])); + } + + SECTION("atan") + { + REQUIRE_THAT(atan(-1 * one), AlmostEquals(-45. * angle[deg])); + REQUIRE_THAT(atan(0 * one), AlmostEquals(0. * angle[deg])); + REQUIRE_THAT(atan(1 * one), AlmostEquals(45. * angle[deg])); + } + } + + SECTION("Angle atan2 functions") + { + using namespace mp_units::angular; + using namespace mp_units::angular::unit_symbols; + using mp_units::angular::unit_symbols::deg; + + SECTION("atan2 should work on the same quantities") + { + REQUIRE_THAT(atan2(-1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(-45. * angle[deg])); + REQUIRE_THAT(atan2(0. * isq::length[km], 1. * isq::length[km]), AlmostEquals(0. * 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])); + } + } +} \ No newline at end of file diff --git a/test/runtime/quantity_test.cpp b/test/runtime/quantity_test.cpp index 14dec787..fcfdbad8 100644 --- a/test/runtime/quantity_test.cpp +++ b/test/runtime/quantity_test.cpp @@ -60,22 +60,25 @@ constexpr bool within_4_ulps(T a, T b) } // namespace -// conversion requiring radical magnitudes -TEST_CASE("unit conversions support radical magnitudes", "[conversion][radical]") +TEST_CASE("quantity operations", "[quantity]") { - 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: -TEST_CASE("Issue 474 is fixed", "[conversion][radical]") -{ - 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), - sqrt(val_issue_474.numerical_value_in(m * m / s / s)))); -} + // Reproducing issue #474 exactly: + SECTION("Issue 474 is fixed") + { + 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), + sqrt(val_issue_474.numerical_value_in(m * m / s / s)))); + } -TEST_CASE("Volatile representation type", "[volatile]") -{ - volatile std::int16_t vint = 123; - REQUIRE(quantity(vint * m).numerical_value_in(m) == 123); -} + SECTION("Volatile representation type") + { + volatile std::int16_t vint = 123; + REQUIRE(quantity(vint * m).numerical_value_in(m) == 123); + } +} \ No newline at end of file From c6344c26eed58e18f9019a3b8b9867fef195f0c7 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 11:31:26 +0100 Subject: [PATCH 07/27] style: missing empty line at the end of the file added --- test/runtime/distribution_test.cpp | 2 +- test/runtime/math_test.cpp | 2 +- test/runtime/quantity_test.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/runtime/distribution_test.cpp b/test/runtime/distribution_test.cpp index e18d5a9e..1ea3da1d 100644 --- a/test/runtime/distribution_test.cpp +++ b/test/runtime/distribution_test.cpp @@ -660,4 +660,4 @@ TEST_CASE("distributions", "[random][distribution]") CHECK(units_dist.densities() == stl_dist.densities()); } } -} \ No newline at end of file +} diff --git a/test/runtime/math_test.cpp b/test/runtime/math_test.cpp index 994c9eeb..bee5f7e8 100644 --- a/test/runtime/math_test.cpp +++ b/test/runtime/math_test.cpp @@ -527,4 +527,4 @@ TEST_CASE("math operations", "[math]") REQUIRE_THAT(atan2(1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(45. * angle[deg])); } } -} \ No newline at end of file +} diff --git a/test/runtime/quantity_test.cpp b/test/runtime/quantity_test.cpp index fcfdbad8..0984373e 100644 --- a/test/runtime/quantity_test.cpp +++ b/test/runtime/quantity_test.cpp @@ -81,4 +81,4 @@ TEST_CASE("quantity operations", "[quantity]") volatile std::int16_t vint = 123; REQUIRE(quantity(vint * m).numerical_value_in(m) == 123); } -} \ No newline at end of file +} From 5ddbd62ea483dc92e5ef5a37adb12917e3cbc1f5 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 14:19:39 +0100 Subject: [PATCH 08/27] feat(example): `is_vector` specialization no longer needed for `si_constants` --- docs/users_guide/examples/si_constants.md | 14 +++----------- example/si_constants.cpp | 4 ---- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/docs/users_guide/examples/si_constants.md b/docs/users_guide/examples/si_constants.md index f5feb153..847520f4 100644 --- a/docs/users_guide/examples/si_constants.md +++ b/docs/users_guide/examples/si_constants.md @@ -16,19 +16,11 @@ work in practice. --8<-- "example/si_constants.cpp:28:40" ``` -As always, we start with the inclusion of all the needed header files. After that, for -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" -``` - +As always, we start with the inclusion of all the needed header files. The main part of the example prints all of the SI-defining constants: -```cpp title="si_constants.cpp" linenums="17" ---8<-- "example/si_constants.cpp:45:" +```cpp title="si_constants.cpp" linenums="14" +--8<-- "example/si_constants.cpp:42:" ``` While analyzing the output of this program (provided below), we can easily notice that a direct diff --git a/example/si_constants.cpp b/example/si_constants.cpp index e09588c4..a43779ab 100644 --- a/example/si_constants.cpp +++ b/example/si_constants.cpp @@ -39,10 +39,6 @@ import mp_units; #include #endif -template - requires mp_units::is_scalar -constexpr bool mp_units::is_vector = true; - int main() { using namespace mp_units; From e4044f0e4be85bda8e9464d5ef5f35e488067841 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 14:20:19 +0100 Subject: [PATCH 09/27] feat: `ComplexRepresentation` extended with complex operations --- .../mp-units/framework/representation_concepts.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/core/include/mp-units/framework/representation_concepts.h b/src/core/include/mp-units/framework/representation_concepts.h index 4ddbcb88..8be28d05 100644 --- a/src/core/include/mp-units/framework/representation_concepts.h +++ b/src/core/include/mp-units/framework/representation_concepts.h @@ -118,12 +118,11 @@ concept ComplexRepresentation = Complex && WeaklyRegular && requires(T a, { a - b } -> Complex; { a* b } -> Complex; { a / b } -> Complex; - // TBD - // { re(a) } -> Scalar; - // { im(a) } -> Scalar; - // { mod(a) } -> Scalar; - // { arg(a) } -> Scalar; - // { conj(a) } -> Complex; + { real(a) } -> Scalar; + { imag(a) } -> Scalar; + { abs(a) } -> Scalar; + { arg(a) } -> Scalar; + { conj(a) } -> Complex; }; // TODO how to check for a complex(Scalar, Scalar) -> Complex? From d8574022f1d1ba2ab1b676e159bac085778fa85a Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 14:21:36 +0100 Subject: [PATCH 10/27] test: _surface tension_ replaced with _entropy_ in an fmt test --- test/runtime/fmt_test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/runtime/fmt_test.cpp b/test/runtime/fmt_test.cpp index 4011588e..bda19763 100644 --- a/test/runtime/fmt_test.cpp +++ b/test/runtime/fmt_test.cpp @@ -402,12 +402,12 @@ TEST_CASE("default quantity formatting", "[quantity][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] / (2 * isq::thermodynamic_temperature[K]); 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()); } From 6c3c1fe5f72d690c89637f22f603a46abc03fdb3 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 14:34:18 +0100 Subject: [PATCH 11/27] feat: `cartesian_vector` added --- src/core/CMakeLists.txt | 1 + src/core/include/mp-units/cartesian_vector.h | 230 ++++++++++++ src/core/include/mp-units/core.h | 1 + test/runtime/CMakeLists.txt | 5 +- test/runtime/cartesian_vector_test.cpp | 374 +++++++++++++++++++ 5 files changed, 609 insertions(+), 2 deletions(-) create mode 100644 src/core/include/mp-units/cartesian_vector.h create mode 100644 test/runtime/cartesian_vector_test.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 2132d71a..e6251cf2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -91,6 +91,7 @@ if(NOT ${projectPrefix}API_FREESTANDING) include/mp-units/bits/fmt.h include/mp-units/bits/requires_hosted.h include/mp-units/ext/format.h + include/mp-units/cartesian_vector.h include/mp-units/complex.h include/mp-units/format.h include/mp-units/math.h diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h new file mode 100644 index 00000000..7227f193 --- /dev/null +++ b/src/core/include/mp-units/cartesian_vector.h @@ -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 +// +#include +#include + +#if MP_UNITS_HOSTED +#include +#endif + +#ifndef MP_UNITS_IN_MODULE_INTERFACE +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#include +#include +#if MP_UNITS_HOSTED +#include +#endif +#endif +#endif + +namespace mp_units { + +MP_UNITS_EXPORT template +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 Arg1, std::convertible_to... Args> + constexpr cartesian_vector(Arg1&& arg1, Args&&... args) : + _coordinates_(std::forward(arg1), std::forward(args)...) + { + } + + template U> + constexpr cartesian_vector(const cartesian_vector& other) : _coordinates_{other[0], other[1], other[2]} + { + } + + template U> + constexpr cartesian_vector(cartesian_vector&& other) : + _coordinates_{std::move(other[0]), std::move(other[1]), std::move(other[2])} + { + } + + template U> + constexpr cartesian_vector& operator=(const cartesian_vector& other) + { + _coordinates_[0] = other[0]; + _coordinates_[1] = other[1]; + _coordinates_[2] = other[2]; + return *this; + } + + template U> + constexpr cartesian_vector& operator=(cartesian_vector&& 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 + { + return std::hypot(_coordinates_[0], _coordinates_[1], _coordinates_[2]); + } + + [[nodiscard]] constexpr cartesian_vector unit() const + requires treat_as_floating_point + { + 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 U, typename V> + requires requires(U u, V v) { u + v; } + [[nodiscard]] friend constexpr auto operator+(const cartesian_vector& lhs, const cartesian_vector& 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 U, typename V> + requires requires(U u, V v) { u - v; } + [[nodiscard]] friend constexpr auto operator-(const cartesian_vector& lhs, const cartesian_vector& 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 U> + requires requires(U u, T t) { u* t; } + [[nodiscard]] friend constexpr auto operator*(const cartesian_vector& lhs, const T& rhs) + { + return ::mp_units::cartesian_vector{lhs._coordinates_[0] * rhs, lhs._coordinates_[1] * rhs, + lhs._coordinates_[2] * rhs}; + } + + template U> + requires requires(T t, U u) { t* u; } + [[nodiscard]] friend constexpr auto operator*(const T& lhs, const cartesian_vector& rhs) + { + return rhs * lhs; + } + + template U> + requires requires(U u, T t) { u / t; } + [[nodiscard]] friend constexpr auto operator/(const cartesian_vector& lhs, const T& rhs) + { + return ::mp_units::cartesian_vector{lhs._coordinates_[0] / rhs, lhs._coordinates_[1] / rhs, + lhs._coordinates_[2] / rhs}; + } + + template U, std::equality_comparable_with V> + [[nodiscard]] friend constexpr bool operator==(const cartesian_vector& lhs, const cartesian_vector& 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& v) + requires treat_as_floating_point + { + return v.magnitude(); + } + + [[nodiscard]] friend constexpr cartesian_vector unit_vector(const cartesian_vector& v) + requires treat_as_floating_point + { + return v.unit(); + } + + template 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& lhs, const cartesian_vector& rhs) + { + return lhs._coordinates_[0] * rhs._coordinates_[0] + lhs._coordinates_[1] * rhs._coordinates_[1] + + lhs._coordinates_[2] * rhs._coordinates_[2]; + } + + template 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& lhs, const cartesian_vector& 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& v) + { + return os << '[' << v[0] << ", " << v[1] << ", " << v[2] << ']'; + } +#endif +}; + +template + requires(sizeof...(Args) <= 2) && requires { typename std::common_type_t; } +cartesian_vector(Arg, Args...) -> cartesian_vector>; + +template +constexpr bool is_vector> = true; + +} // namespace mp_units + +#if MP_UNITS_HOSTED +// TODO use parse and use formatter for the underlying type +template +struct MP_UNITS_STD_FMT::formatter, Char> : + formatter, Char> { + template + auto format(const mp_units::cartesian_vector& v, FormatContext& ctx) const + { + return format_to(ctx.out(), "[{}, {}, {}]", v[0], v[1], v[2]); + } +}; +#endif diff --git a/src/core/include/mp-units/core.h b/src/core/include/mp-units/core.h index a99978f8..702f3b6b 100644 --- a/src/core/include/mp-units/core.h +++ b/src/core/include/mp-units/core.h @@ -28,6 +28,7 @@ #include #if MP_UNITS_HOSTED +#include #include #include #include diff --git a/test/runtime/CMakeLists.txt b/test/runtime/CMakeLists.txt index d79ed54b..c1d990ba 100644 --- a/test/runtime/CMakeLists.txt +++ b/test/runtime/CMakeLists.txt @@ -24,13 +24,14 @@ find_package(Catch2 3 REQUIRED) add_executable( unit_tests_runtime + atomic_test.cpp + cartesian_vector_test.cpp distribution_test.cpp fixed_string_test.cpp fmt_test.cpp math_test.cpp - atomic_test.cpp - truncation_test.cpp quantity_test.cpp + truncation_test.cpp ) if(${projectPrefix}BUILD_CXX_MODULES) target_compile_definitions(unit_tests_runtime PUBLIC ${projectPrefix}MODULES) diff --git a/test/runtime/cartesian_vector_test.cpp b/test/runtime/cartesian_vector_test.cpp new file mode 100644 index 00000000..04dfe28b --- /dev/null +++ b/test/runtime/cartesian_vector_test.cpp @@ -0,0 +1,374 @@ +// 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 +#include +#ifdef MP_UNITS_MODULES +import mp_units; +#else +#include +#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()); } + } +} From 5a206c3ef1d684df22e5a44800cc43f3346b3ab8 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 14:38:51 +0100 Subject: [PATCH 12/27] test: `cartesian_vector` used in fmt_test --- test/runtime/fmt_test.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/test/runtime/fmt_test.cpp b/test/runtime/fmt_test.cpp index bda19763..d375f091 100644 --- a/test/runtime/fmt_test.cpp +++ b/test/runtime/fmt_test.cpp @@ -39,6 +39,7 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else +#include #include #include // IWYU pragma: keep #include @@ -48,12 +49,9 @@ import mp_units; #include #endif -template - requires mp_units::is_scalar -constexpr bool mp_units::is_vector = true; - using namespace mp_units; using namespace mp_units::si::unit_symbols; +using v = cartesian_vector; TEST_CASE("dimension_symbol", "[dimension][symbol]") { @@ -437,10 +435,10 @@ TEST_CASE("default quantity formatting", "[quantity][ostream][fmt]") 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; - 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()); } @@ -467,10 +465,10 @@ TEST_CASE("default quantity formatting", "[quantity][ostream][fmt]") 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; - 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()); } @@ -779,7 +777,7 @@ TEST_CASE("quantity subentities selection", "[quantity][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]) == "kΩ"); 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}", v{1, 2, 3} * isq::acceleration[m / s2]) == "m/s²"); CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * percent) == "%"); } From 520610ab2f91e9ecbfcbc19f754bfc4767936616 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 14:39:04 +0100 Subject: [PATCH 13/27] test: `cartesian_vector` used in cgs_test --- test/static/cgs_test.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/static/cgs_test.cpp b/test/static/cgs_test.cpp index c027b601..9ea79b1f 100644 --- a/test/static/cgs_test.cpp +++ b/test/static/cgs_test.cpp @@ -25,24 +25,28 @@ #include #include #include - -template - requires mp_units::is_scalar -constexpr bool mp_units::is_vector = true; +#if MP_UNITS_HOSTED +#include +#endif namespace { using namespace mp_units; using namespace mp_units::cgs; using namespace mp_units::cgs::unit_symbols; +#if MP_UNITS_HOSTED +using v = cartesian_vector; +#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 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::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::acceleration(100 * Gal) == isq::acceleration(1 * si::metre / square(si::second))); -static_assert(isq::force(100'000 * dyn) == isq::force(1 * si::newton)); +#if MP_UNITS_HOSTED +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::power(10'000'000 * erg / s) == isq::power(1 * si::watt)); static_assert(isq::pressure(10 * Ba) == isq::pressure(1 * si::pascal)); From 73ad1f08d46f752f934a7106a49cb64fd279111c Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 16:20:51 +0100 Subject: [PATCH 14/27] fix: fmt_test fixed to use `delta` to create a quantity of `thermodynamic_temperature` --- test/runtime/fmt_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtime/fmt_test.cpp b/test/runtime/fmt_test.cpp index d375f091..a163c187 100644 --- a/test/runtime/fmt_test.cpp +++ b/test/runtime/fmt_test.cpp @@ -402,7 +402,7 @@ TEST_CASE("default quantity formatting", "[quantity][ostream][fmt]") SECTION("entropy") { - const auto q = 20 * isq::kinetic_energy[J] / (2 * isq::thermodynamic_temperature[K]); + const auto q = 20 * isq::kinetic_energy[J] / (delta(2)); os << q; SECTION("iostream") { CHECK(os.str() == "10 J/K"); } From 1ee824423e1c28c91a28e28765990123f201ec3d Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 16:22:14 +0100 Subject: [PATCH 15/27] refactor: usage of `v` for the function argument name changed to prevent shadowing errors --- example/include/validated_type.h | 4 +- src/core/include/mp-units/cartesian_vector.h | 16 ++++---- .../include/mp-units/ext/inplace_vector.h | 8 ++-- .../include/mp-units/framework/quantity.h | 40 +++++++++---------- src/core/include/mp-units/ostream.h | 12 +++--- .../include/mp-units/systems/si/chrono.h | 8 ++-- 6 files changed, 44 insertions(+), 44 deletions(-) diff --git a/example/include/validated_type.h b/example/include/validated_type.h index c8b5dc6a..70be6dac 100644 --- a/example/include/validated_type.h +++ b/example/include/validated_type.h @@ -129,8 +129,8 @@ std::basic_ostream& operator<<(std::basic_ostream& template struct MP_UNITS_STD_FMT::formatter, Char> : formatter { template - auto format(const validated_type& v, FormatContext& ctx) const -> decltype(ctx.out()) + auto format(const validated_type& val, FormatContext& ctx) const -> decltype(ctx.out()) { - return formatter::format(v.value(), ctx); + return formatter::format(val.value(), ctx); } }; diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index 7227f193..0749fae0 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -163,16 +163,16 @@ public: lhs._coordinates_[2] == rhs._coordinates_[2]; } - [[nodiscard]] friend constexpr T norm(const cartesian_vector& v) + [[nodiscard]] friend constexpr T norm(const cartesian_vector& vec) requires treat_as_floating_point { - return v.magnitude(); + return vec.magnitude(); } - [[nodiscard]] friend constexpr cartesian_vector unit_vector(const cartesian_vector& v) + [[nodiscard]] friend constexpr cartesian_vector unit_vector(const cartesian_vector& vec) requires treat_as_floating_point { - return v.unit(); + return vec.unit(); } template U, typename V> @@ -200,9 +200,9 @@ public: } #if MP_UNITS_HOSTED - friend constexpr std::ostream& operator<<(std::ostream& os, const cartesian_vector& v) + friend constexpr std::ostream& operator<<(std::ostream& os, const cartesian_vector& vec) { - return os << '[' << v[0] << ", " << v[1] << ", " << v[2] << ']'; + return os << '[' << vec[0] << ", " << vec[1] << ", " << vec[2] << ']'; } #endif }; @@ -222,9 +222,9 @@ template struct MP_UNITS_STD_FMT::formatter, Char> : formatter, Char> { template - auto format(const mp_units::cartesian_vector& v, FormatContext& ctx) const + auto format(const mp_units::cartesian_vector& vec, FormatContext& ctx) const { - return format_to(ctx.out(), "[{}, {}, {}]", v[0], v[1], v[2]); + return format_to(ctx.out(), "[{}, {}, {}]", vec[0], vec[1], vec[2]); } }; #endif diff --git a/src/core/include/mp-units/ext/inplace_vector.h b/src/core/include/mp-units/ext/inplace_vector.h index 1e93caf4..8074b09b 100644 --- a/src/core/include/mp-units/ext/inplace_vector.h +++ b/src/core/include/mp-units/ext/inplace_vector.h @@ -90,16 +90,16 @@ public: constexpr T* data() 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 { - return emplace_back(v); + return emplace_back(val); } - constexpr reference push_back(T&& v) + constexpr reference push_back(T&& val) requires std::constructible_from { - return emplace_back(std::forward(v)); + return emplace_back(std::forward(val)); } template diff --git a/src/core/include/mp-units/framework/quantity.h b/src/core/include/mp-units/framework/quantity.h index bfd55b92..71bbc699 100644 --- a/src/core/include/mp-units/framework/quantity.h +++ b/src/core/include/mp-units/framework/quantity.h @@ -174,21 +174,21 @@ public: template requires detail::SameValueAs, Rep> - constexpr quantity(FwdValue&& v, R2) : numerical_value_is_an_implementation_detail_(std::forward(v)) + constexpr quantity(FwdValue&& val, R2) : numerical_value_is_an_implementation_detail_(std::forward(val)) { } template> requires(!detail::SameValueAs) && detail::QuantityConvertibleTo, quantity> - constexpr quantity(FwdValue&& v, R2) : quantity(quantity{std::forward(v), R2{}}) + constexpr quantity(FwdValue&& val, R2) : quantity(quantity{std::forward(val), R2{}}) { } template FwdValue> requires(unit == ::mp_units::one) - constexpr explicit(false) quantity(FwdValue&& v) : - numerical_value_is_an_implementation_detail_(std::forward(v)) + constexpr explicit(false) quantity(FwdValue&& val) : + numerical_value_is_an_implementation_detail_(std::forward(val)) { } @@ -214,9 +214,9 @@ public: template FwdValue> requires(unit == ::mp_units::one) - constexpr quantity& operator=(FwdValue&& v) + constexpr quantity& operator=(FwdValue&& val) { - numerical_value_is_an_implementation_detail_ = std::forward(v); + numerical_value_is_an_implementation_detail_ = std::forward(val); return *this; } @@ -422,11 +422,11 @@ public: requires(!Quantity) && requires(rep a, Value b) { { a *= b } -> std::same_as; } - 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: // 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(lhs); } @@ -444,12 +444,12 @@ public: requires(!Quantity) && requires(rep a, Value b) { { a /= b } -> std::same_as; } - 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::zero()); + MP_UNITS_EXPECTS_DEBUG(val != quantity_values::zero()); // TODO use /= when compiler bug is resolved: // 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(lhs); } @@ -551,17 +551,17 @@ public: template Q, typename Value> requires(!Quantity) && (!Reference) && detail::InvokeResultOf, Rep, const Value&> - [[nodiscard]] friend constexpr QuantityOf auto operator*(const Q& q, const Value& v) + [[nodiscard]] friend constexpr QuantityOf 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 Q> requires(!Quantity) && (!Reference) && detail::InvokeResultOf, const Value&, Rep> - [[nodiscard]] friend constexpr QuantityOf auto operator*(const Value& v, const Q& q) + [[nodiscard]] friend constexpr QuantityOf 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 Q, auto R2, typename Rep2> @@ -575,18 +575,18 @@ public: template Q, typename Value> requires(!Quantity) && (!Reference) && detail::InvokeResultOf, Rep, const Value&> - [[nodiscard]] friend constexpr QuantityOf auto operator/(const Q& q, const Value& v) + [[nodiscard]] friend constexpr QuantityOf auto operator/(const Q& q, const Value& val) { - MP_UNITS_EXPECTS_DEBUG(v != quantity_values::zero()); - return ::mp_units::quantity{q.numerical_value_ref_in(unit) / v, R}; + MP_UNITS_EXPECTS_DEBUG(val != quantity_values::zero()); + return ::mp_units::quantity{q.numerical_value_ref_in(unit) / val, R}; } template Q> requires(!Quantity) && (!Reference) && detail::InvokeResultOf, const Value&, Rep> - [[nodiscard]] friend constexpr QuantityOf auto operator/(const Value& v, const Q& q) + [[nodiscard]] friend constexpr QuantityOf 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 Q, auto R2, typename Rep2> diff --git a/src/core/include/mp-units/ostream.h b/src/core/include/mp-units/ostream.h index 15b6127b..1b3f63ac 100644 --- a/src/core/include/mp-units/ostream.h +++ b/src/core/include/mp-units/ostream.h @@ -68,8 +68,8 @@ void to_stream_impl(std::basic_ostream& os, const quantity -std::basic_ostream& to_stream(std::basic_ostream& os, const T& v) - requires requires { detail::to_stream_impl(os, v); } +std::basic_ostream& to_stream(std::basic_ostream& os, const T& val) + requires requires { detail::to_stream_impl(os, val); } { if (os.width()) { // std::setw() applies to the whole output so it has to be first put into std::string @@ -77,11 +77,11 @@ std::basic_ostream& to_stream(std::basic_ostream& oss.flags(os.flags()); oss.imbue(os.getloc()); oss.precision(os.precision()); - detail::to_stream_impl(oss, v); + detail::to_stream_impl(oss, val); return os << std::move(oss).str(); } - detail::to_stream_impl(os, v); + detail::to_stream_impl(os, val); return os; } @@ -93,10 +93,10 @@ constexpr bool is_mp_units_stream = requires(OStream os, T v) { detail::to_strea MP_UNITS_EXPORT_BEGIN template -std::basic_ostream& operator<<(std::basic_ostream& os, const T& v) +std::basic_ostream& operator<<(std::basic_ostream& os, const T& val) requires detail::is_mp_units_stream, T> { - return detail::to_stream(os, v); + return detail::to_stream(os, val); } MP_UNITS_EXPORT_END diff --git a/src/systems/include/mp-units/systems/si/chrono.h b/src/systems/include/mp-units/systems/si/chrono.h index a856fc15..51434421 100644 --- a/src/systems/include/mp-units/systems/si/chrono.h +++ b/src/systems/include/mp-units/systems/si/chrono.h @@ -83,10 +83,10 @@ struct quantity_like_traits> { 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) { - return T(v); + return T(val); } }; @@ -113,10 +113,10 @@ struct quantity_point_like_traits) { - return T(std::chrono::duration(v)); + return T(std::chrono::duration(val)); } }; From 35ed4729757e6526b5066e4d11dd1dc9f45e3c90 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 18:07:52 +0100 Subject: [PATCH 16/27] first attempt at generating sparse CI run matrix in python; also, cancel previous runs on the same branch --- .github/generate-job-matrix.py | 168 ++++++++++++++++++++ .github/job_matrix.py | 113 +++++++++++++ .github/workflows/ci-clang-tidy.yml | 41 ++--- .github/workflows/ci-conan.yml | 158 +++--------------- .github/workflows/ci-freestanding.yml | 55 +++---- .github/workflows/ci-test-package-cmake.yml | 144 +++-------------- 6 files changed, 367 insertions(+), 312 deletions(-) create mode 100644 .github/generate-job-matrix.py create mode 100644 .github/job_matrix.py diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py new file mode 100644 index 00000000..b2465d05 --- /dev/null +++ b/.github/generate-job-matrix.py @@ -0,0 +1,168 @@ +import argparse +import json +import typing +import itertools +import random +import dataclasses +from types import SimpleNamespace +from dataclasses import dataclass + +from job_matrix import Configuration, Compiler, MatrixElement, CombinationCollector, dataclass_to_json + +def make_gcc_config(version: int) -> Configuration: + return Configuration( + name=f"GCC-{version}", + os="ubuntu-24.04", + compiler=Compiler( + type="GCC", + version=version, + cc=f"gcc-{version}", + cxx=f"g++-{version}", + ), + cxx_modules=False, + std_format_support=version >= 13, + ) + + +def make_clang_config(version: int, platform: typing.Literal["x86-64", "arm64"] = "x86-64") -> Configuration: + ret = SimpleNamespace( + name=f"Clang-{version} ({platform})", + os=None, # replaced below + compiler=SimpleNamespace( + type="CLANG", + version=version, + cc=f"clang-{version}", + cxx=f"clang++-{version}", + ), + lib="libc++", + cxx_modules=version >= 17, + std_format_support=version >= 17, + ) + match platform: + case "x86-64": + ret.os = "ubuntu-24.04" + case "arm64": + ret.os = "macos-14" + pfx = f"/opt/homebrew/opt/llvm@{version}/bin/" + ret.compiler.cc = pfx + ret.compiler.cc + ret.compiler.cxx = pfx + ret.compiler.cxx + case _: + raise KeyError(f"Unsupported platform {platform!r} for Clang") + ret.compiler = Compiler(**vars(ret.compiler)) + return Configuration(**vars(ret)) + + +def make_apple_clang_config(version: int) -> Configuration: + ret = Configuration( + name=f"Apple Clang {version}", + os="macos-13", + compiler=Compiler( + type="APPLE_CLANG", + version=f"{version}.0", + cc="clang", + cxx="clang++", + ), + cxx_modules=False, + std_format_support=False, + ) + return ret + + +def make_msvc_config(release: str, version: int) -> Configuration: + ret = Configuration( + name=f"MSVC {release}", + os="windows-2022", + compiler=Compiler( + type="MSVC", + version=version, + cc="", + cxx="", + ), + cxx_modules=False, + std_format_support=True, + ) + return ret + + +configs = {c.name: c for c in [make_gcc_config(ver) for ver in [12, 13, 14]] + + [make_clang_config(ver, platform) for ver in [16, 17, 18, 19] for platform in ["x86-64", "arm64"]] + + [make_apple_clang_config(ver) for ver in [15]] + + [make_msvc_config(release="14.4", version=194)]} + +full_matrix = dict( + config=list(configs.values()), + std=[20, 23], + formatting=["std::format", "fmtlib"], + contracts=["none", "gsl-lite", "ms-gsl"], + build_type=["Release", "Debug"], +) + + +def main(): + parser = argparse.ArgumentParser() + # parser.add_argument("-I","--include",nargs="+",action="append") + # parser.add_argument("-X","--exclude",nargs="+",action="append") + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--preset", default=None) + parser.add_argument("--debug", nargs="+", default=["combinations"]) + parser.add_argument("--suppress-output", default=False, action="store_true") + + args = parser.parse_args() + + rgen = random.Random(args.seed) + + collector = CombinationCollector(full_matrix) + match args.preset: + case None: + pass + case "all": + collector.all_combinations() + case "conan" | "cmake": + collector.all_combinations(formatting="std::format", contracts="gsl-lite", build_type="Debug", std=20) + collector.all_combinations( + filter=lambda me: not me.config.std_format_support, + formatting="fmtlib", contracts="gsl-lite", build_type="Debug", std=20, + ) + collector.sample_combinations(rgen=rgen, min_samples_per_value=2) + case "clang-tidy": + collector.all_combinations(config=configs["Clang-18 (x86-64)"]) + case "freestanding": + collector.all_combinations( + config=[configs[c] for c in ["GCC-14", "Clang-18 (x86-64)"]], + contracts="none", + std=23, + ) + case _: + raise KeyError(f"Unsupported preset {args.preset!r}") + + if not collector.combinations: + raise ValueError(f"No combination has been produced") + + data = sorted(collector.combinations) + + if not args.suppress_output: + print(f"::set-output name=matrix::{json.dumps(data, default=dataclass_to_json)}") + + for dbg in args.debug: + match dbg: + case "yaml": + import yaml + json_data = json.loads(json.dumps(data, default=dataclass_to_json)) + print(yaml.safe_dump(json_data)) + case "json": + print(json.dumps(data, default=dataclass_to_json, indent=4)) + case "combinations": + for e in data: + print(f"{e.config!s:17s} c++{e.std:2d} {e.formatting:11s} {e.contracts:8s} {e.build_type:8s}") + case "counts": + print(f"Total combinations {len(data)}") + for (k, v), n in sorted(collector.per_value_counts.items()): + print(f" {k}={v}: {n}") + case "none": + pass + case _: + raise KeyError(f"Unknown debug mode {dbg!r}") + + +if __name__ == "__main__": + main() diff --git a/.github/job_matrix.py b/.github/job_matrix.py new file mode 100644 index 00000000..9065f389 --- /dev/null +++ b/.github/job_matrix.py @@ -0,0 +1,113 @@ +import argparse +import json +import typing +import itertools +import random +import dataclasses +from types import SimpleNamespace +from dataclasses import dataclass + + +@dataclass(frozen=True, order=True) +class Compiler: + type: typing.Literal["GCC", "CLANG", "APPLE_CLANG", "MSVC"] + version: str | int + cc: str + cxx: str + + +@dataclass(frozen=True, order=True) +class Configuration: + name: str + os: str + compiler: Compiler + cxx_modules: bool + std_format_support: bool + conan_config: str = "" + lib: typing.Literal["libc++", "libstdc++"] | None = None + + def __str__(self): + return self.name + +@dataclass(frozen=True, order=True) +class MatrixElement: + config: Configuration + std: typing.Literal[20, 23] + formatting: typing.Literal["std::format", "fmtlib"] + contracts: typing.Literal["none", "gsl-lite", "ms-gsl"] + build_type: typing.Literal["Release", "Debug"] + + +def dataclass_to_json(obj): + """ Convert dataclasses to something json-serialisable """ + if dataclasses.is_dataclass(obj): + return dataclasses.asdict(obj) + raise TypeError(f"Unknown object of type {type(obj).__name__}") + + +class CombinationCollector: + """ Incremental builder of MatrixElements, allowing successive selection of entries. + """ + + def __init__(self, full_matrix: dict[str, list[typing.Any]]): + self.full_matrix = full_matrix + self.combinations: set[MatrixElement] = set() + self.per_value_counts: dict[tuple[str, typing.Any], int] = {(k, v): 0 for k, options in full_matrix.items() for + v in options} + + def _make_submatrix(self, **overrides): + new_matrix = dict(self.full_matrix) + for k, v in overrides.items(): + if not isinstance(v, list): + v = [v] + new_matrix[k] = v + return new_matrix + + def _add_combination(self, e: MatrixElement): + if e in self.combinations or (e.formatting == "std::format" and not e.config.std_format_support): + return + self.combinations.add(e) + # update per_value_counts + for k, v in vars(e).items(): + idx = (k, v) + self.per_value_counts[idx] = self.per_value_counts.get(idx, 0) + 1 + + def all_combinations(self, *, filter: typing.Callable[[MatrixElement], bool] | None = None, **overrides): + """ Adds all combinations in the submatrix defined by `overrides`. """ + matrix = self._make_submatrix(**overrides) + keys = tuple(matrix.keys()) + for combination in itertools.product(*matrix.values()): + cand = MatrixElement(**dict(zip(keys, combination))) + if filter and not filter(cand): + continue + self._add_combination(cand) + + def sample_combinations(self, *, rgen: random.Random, min_samples_per_value: int = 1, + filter: typing.Callable[[MatrixElement], bool] | None = None, **overrides): + """ Adds samples from the submatrix defined by `overrides`, ensuring each individual value appears at least n times. """ + matrix = self._make_submatrix(**overrides) + missing: dict[tuple[str, typing.Any], int] = {} + for key, options in matrix.items(): + for value in options: + idx = (key, value) + missing[idx] = min_samples_per_value - self.per_value_counts.get(idx, 0) + while missing: + (force_key, force_option), remaining = next(iter(missing.items())) + if remaining <= 0: + missing.pop((force_key, force_option)) + continue + choice = {} + for key, options in matrix.items(): + choice[key] = force_option if key == force_key else rgen.choice(options) + cand = MatrixElement(**choice) + if filter and not filter(cand): + continue + self._add_combination(cand) + for idx in choice.items(): + if missing.pop(idx, 0) <= 0: + continue + remaining = min_samples_per_value - self.per_value_counts.get(idx, 0) + if remaining > 0: + missing[idx] = remaining + + diff --git a/.github/workflows/ci-clang-tidy.yml b/.github/workflows/ci-clang-tidy.yml index 015d4229..ddf7f23f 100644 --- a/.github/workflows/ci-clang-tidy.yml +++ b/.github/workflows/ci-clang-tidy.yml @@ -35,32 +35,33 @@ on: - "docs/**" jobs: + cancel-previous: + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + steps: + - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" + generate-matrix: + name: "Generate build matrix for ${{ github.workflow }}" + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + runs-on: ubuntu-24.04 + needs: cancel-previous + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - id: set-matrix + run: python .github/generate-job-matrix.py --preset clang-tidy --seed 42 --debug combinations counts build: name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} + needs: generate-matrix strategy: fail-fast: false matrix: - formatting: ["std::format", "fmtlib"] - contracts: ["none", "gsl-lite", "ms-gsl"] - std: [20, 23] - config: - - { - name: "Clang-18", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 18, - cc: "clang-18", - cxx: "clang++-18", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - build_type: ["Release", "Debug"] + include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} env: CC: ${{ matrix.config.compiler.cc }} diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index 8d5fc75a..f5377fc2 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -34,148 +34,36 @@ env: CHANNEL: ${{ fromJSON('["testing", "stable"]')[github.ref_type == 'tag' && startsWith(github.ref_name, 'v')] }} jobs: + cancel-previous: + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + steps: + - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" + generate-matrix: + name: "Generate build matrix for ${{ github.workflow }}" + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + runs-on: ubuntu-24.04 + needs: cancel-previous + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - id: set-matrix + run: python .github/generate-job-matrix.py --preset conan --seed 42 --debug combinations counts build: name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} + needs: generate-matrix strategy: fail-fast: false matrix: - formatting: ["std::format", "fmtlib"] - contracts: ["none", "gsl-lite", "ms-gsl"] - std: [20, 23] - config: - - { - name: "MSVC 14.4", - os: windows-2022, - compiler: { type: MSVC, version: 194, cc: "", cxx: "" }, - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - - { - name: "GCC-12", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 12, - cc: "gcc-12", - cxx: "g++-12", - }, - cxx_modules: "False", - std_format_support: "False", - conan-config: "", - } - - { - name: "GCC-13", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 13, - cc: "gcc-13", - cxx: "g++-13", - }, - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - - { - name: "GCC-14", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 14, - cc: "gcc-14", - cxx: "g++-14", - }, - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - - { - name: "Clang-16", - os: ubuntu-22.04, - compiler: - { - type: CLANG, - version: 16, - cc: "clang-16", - cxx: "clang++-16", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "False", - conan-config: "", - } - - { - name: "Clang-17", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 17, - cc: "clang-17", - cxx: "clang++-17", - }, - lib: "libc++", - cxx_modules: "True", - std_format_support: "True", - conan-config: "", - } - - { - name: "Clang-18", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 18, - cc: "clang-18", - cxx: "clang++-18", - }, - lib: "libc++", - cxx_modules: "True", - std_format_support: "True", - conan-config: "", - } - - { - name: "Clang-18 on Apple M1 (arm64)", - os: macos-14, - compiler: - { - type: CLANG, - version: 18, - cc: "/opt/homebrew/opt/llvm@18/bin/clang-18", - cxx: "/opt/homebrew/opt/llvm@18/bin/clang++", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True" - } - - { - name: "Apple Clang 15", - os: macos-13, - compiler: - { - type: APPLE_CLANG, - version: "15.0", - cc: "clang", - cxx: "clang++", - }, - cxx_modules: "False", - std_format_support: "False", - conan-config: "", - } - build_type: ["Release", "Debug"] - exclude: - - formatting: "std::format" - config: { std_format_support: "False" } - + include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} env: CC: ${{ matrix.config.compiler.cc }} CXX: ${{ matrix.config.compiler.cxx }} - steps: - uses: actions/checkout@v4 - name: Generate unique cache id @@ -265,14 +153,14 @@ jobs: run: | conan create . --user mpusz --channel ${CHANNEL} --lockfile-out=package.lock \ -b mp-units/* -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" -c user.mp-units.build:all=True \ - -o '&:cxx_modules=${{ matrix.config.cxx_modules }}' -o '&:import_std=${{ env.import_std }}' -o '&:std_format=${{ env.std_format }}' -o '&:contracts=${{ matrix.contracts }}' ${{ matrix.config.conan-config }} + -o '&:cxx_modules=${{ matrix.config.cxx_modules }}' -o '&:import_std=${{ env.import_std }}' -o '&:std_format=${{ env.std_format }}' -o '&:contracts=${{ matrix.contracts }}' ${{ matrix.config.conan_config }} - name: Create Conan package if: matrix.config.compiler.type == 'MSVC' shell: bash run: | conan create . --user mpusz --channel ${CHANNEL} --lockfile-out=package.lock \ -b mp-units/* -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" -c user.mp-units.build:all=False \ - -o '&:cxx_modules=${{ matrix.config.cxx_modules }}' -o '&:import_std=${{ env.import_std }}' -o '&:std_format=${{ env.std_format }}' -o '&:contracts=${{ matrix.contracts }}' ${{ matrix.config.conan-config }} + -o '&:cxx_modules=${{ matrix.config.cxx_modules }}' -o '&:import_std=${{ env.import_std }}' -o '&:std_format=${{ env.std_format }}' -o '&:contracts=${{ matrix.contracts }}' ${{ matrix.config.conan_config }} - name: Obtain package reference id: get-package-ref shell: bash diff --git a/.github/workflows/ci-freestanding.yml b/.github/workflows/ci-freestanding.yml index cde058db..5ce2662c 100644 --- a/.github/workflows/ci-freestanding.yml +++ b/.github/workflows/ci-freestanding.yml @@ -35,46 +35,33 @@ on: - "docs/**" jobs: + cancel-previous: + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + steps: + - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" + generate-matrix: + name: "Generate build matrix for ${{ github.workflow }}" + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + runs-on: ubuntu-24.04 + needs: cancel-previous + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - id: set-matrix + run: python .github/generate-job-matrix.py --preset freestanding --seed 42 --debug combinations counts build: name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} + needs: generate-matrix strategy: fail-fast: false matrix: - formatting: ["std::format", "fmtlib"] - contracts: ["none"] - std: [23] - config: - - { - name: "GCC-14", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 14, - cc: "gcc-14", - cxx: "g++-14", - }, - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - - { - name: "Clang-18", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 18, - cc: "clang-18", - cxx: "clang++-18", - }, - lib: "libc++", - cxx_modules: "True", - std_format_support: "True", - conan-config: "", - } - build_type: ["Release", "Debug"] + include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} # TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler exclude: - build_type: "Debug" diff --git a/.github/workflows/ci-test-package-cmake.yml b/.github/workflows/ci-test-package-cmake.yml index d5bf5df0..1644c495 100644 --- a/.github/workflows/ci-test-package-cmake.yml +++ b/.github/workflows/ci-test-package-cmake.yml @@ -39,135 +39,33 @@ on: - "test/**" jobs: + cancel-previous: + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + steps: + - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" + generate-matrix: + name: "Generate build matrix for ${{ github.workflow }}" + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + runs-on: ubuntu-24.04 + needs: cancel-previous + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - id: set-matrix + run: python .github/generate-job-matrix.py --preset conan --seed 42 --debug combinations counts test_package: name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} + needs: generate-matrix strategy: fail-fast: false matrix: - formatting: ["std::format", "fmtlib"] - contracts: ["none", "gsl-lite", "ms-gsl"] - std: [20, 23] - config: - - { - name: "MSVC 14.4", - os: windows-2022, - compiler: { type: MSVC, version: 194, cc: "", cxx: "" }, - cxx_modules: "False", - std_format_support: "True", - } - - { - name: "GCC-12", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 12, - cc: "gcc-12", - cxx: "g++-12", - }, - cxx_modules: "False", - std_format_support: "False", - } - - { - name: "GCC-13", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 13, - cc: "gcc-13", - cxx: "g++-13", - }, - cxx_modules: "False", - std_format_support: "True", - } - - { - name: "GCC-14", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 14, - cc: "gcc-14", - cxx: "g++-14", - }, - cxx_modules: "False", - std_format_support: "True" - } - - { - name: "Clang-16", - os: ubuntu-22.04, - compiler: - { - type: CLANG, - version: 16, - cc: "clang-16", - cxx: "clang++-16", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "False", - } - - { - name: "Clang-17", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 17, - cc: "clang-17", - cxx: "clang++-17", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True", - } - - { - name: "Clang-18", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 18, - cc: "clang-18", - cxx: "clang++-18", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True" - } - - { - name: "Clang-18 on Apple M1 (arm64)", - os: macos-14, - compiler: - { - type: CLANG, - version: 18, - cc: "/opt/homebrew/opt/llvm@18/bin/clang-18", - cxx: "/opt/homebrew/opt/llvm@18/bin/clang++", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True" - } - - { - name: "Apple Clang 15", - os: macos-14, - compiler: - { - type: APPLE_CLANG, - version: "15.0", - cc: "clang", - cxx: "clang++", - }, - cxx_modules: "False", - std_format_support: "False", - } - build_type: ["Release", "Debug"] - exclude: - - formatting: "std::format" - config: { std_format_support: "False" } + include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} env: CC: ${{ matrix.config.compiler.cc }} From 420ffef6c57a7f0b2cd67e5e252ee38f96f56ddb Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 12 Nov 2024 18:12:36 +0100 Subject: [PATCH 17/27] fix(test): missing header files added --- test/runtime/cartesian_vector_test.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/runtime/cartesian_vector_test.cpp b/test/runtime/cartesian_vector_test.cpp index 04dfe28b..252134d0 100644 --- a/test/runtime/cartesian_vector_test.cpp +++ b/test/runtime/cartesian_vector_test.cpp @@ -23,6 +23,13 @@ #include "almost_equals.h" #include #include +#include +#include +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#endif #ifdef MP_UNITS_MODULES import mp_units; #else From 7fa15d224d272462d2228f989a25cf813cff4bc9 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 18:43:50 +0100 Subject: [PATCH 18/27] fix formatting --- .flake8 | 3 ++ .github/generate-job-matrix.py | 50 ++++++++++++++------- .github/job_matrix.py | 47 +++++++++++-------- .github/workflows/ci-clang-tidy.yml | 1 + .github/workflows/ci-conan.yml | 1 + .github/workflows/ci-freestanding.yml | 1 + .github/workflows/ci-test-package-cmake.yml | 1 + 7 files changed, 71 insertions(+), 33 deletions(-) diff --git a/.flake8 b/.flake8 index 4592939f..14040456 100644 --- a/.flake8 +++ b/.flake8 @@ -7,3 +7,6 @@ ignore = E712, # line break before binary operator W503 +per-file-ignores = + # flake8 is just plain wrong here, contradicting black + .github/generate-job-matrix.py:E225,E231 diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index b2465d05..9ba3b8f3 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -1,13 +1,11 @@ import argparse import json -import typing -import itertools import random -import dataclasses +import typing from types import SimpleNamespace -from dataclasses import dataclass -from job_matrix import Configuration, Compiler, MatrixElement, CombinationCollector, dataclass_to_json +from job_matrix import CombinationCollector, Compiler, Configuration, dataclass_to_json + def make_gcc_config(version: int) -> Configuration: return Configuration( @@ -24,7 +22,9 @@ def make_gcc_config(version: int) -> Configuration: ) -def make_clang_config(version: int, platform: typing.Literal["x86-64", "arm64"] = "x86-64") -> Configuration: +def make_clang_config( + version: int, platform: typing.Literal["x86-64", "arm64"] = "x86-64" +) -> Configuration: ret = SimpleNamespace( name=f"Clang-{version} ({platform})", os=None, # replaced below @@ -84,10 +84,17 @@ def make_msvc_config(release: str, version: int) -> Configuration: return ret -configs = {c.name: c for c in [make_gcc_config(ver) for ver in [12, 13, 14]] - + [make_clang_config(ver, platform) for ver in [16, 17, 18, 19] for platform in ["x86-64", "arm64"]] - + [make_apple_clang_config(ver) for ver in [15]] - + [make_msvc_config(release="14.4", version=194)]} +configs = { + c.name: c + for c in [make_gcc_config(ver) for ver in [12, 13, 14]] + + [ + make_clang_config(ver, platform) + for ver in [16, 17, 18, 19] + for platform in ["x86-64", "arm64"] + ] + + [make_apple_clang_config(ver) for ver in [15]] + + [make_msvc_config(release="14.4", version=194)] +} full_matrix = dict( config=list(configs.values()), @@ -118,10 +125,18 @@ def main(): case "all": collector.all_combinations() case "conan" | "cmake": - collector.all_combinations(formatting="std::format", contracts="gsl-lite", build_type="Debug", std=20) + collector.all_combinations( + formatting="std::format", + contracts="gsl-lite", + build_type="Debug", + std=20, + ) collector.all_combinations( filter=lambda me: not me.config.std_format_support, - formatting="fmtlib", contracts="gsl-lite", build_type="Debug", std=20, + formatting="fmtlib", + contracts="gsl-lite", + build_type="Debug", + std=20, ) collector.sample_combinations(rgen=rgen, min_samples_per_value=2) case "clang-tidy": @@ -136,24 +151,29 @@ def main(): raise KeyError(f"Unsupported preset {args.preset!r}") if not collector.combinations: - raise ValueError(f"No combination has been produced") + raise ValueError("No combination has been produced") data = sorted(collector.combinations) if not args.suppress_output: - print(f"::set-output name=matrix::{json.dumps(data, default=dataclass_to_json)}") + print( + f"::set-output name=matrix::{json.dumps(data, default=dataclass_to_json)}" + ) for dbg in args.debug: match dbg: case "yaml": import yaml + json_data = json.loads(json.dumps(data, default=dataclass_to_json)) print(yaml.safe_dump(json_data)) case "json": print(json.dumps(data, default=dataclass_to_json, indent=4)) case "combinations": for e in data: - print(f"{e.config!s:17s} c++{e.std:2d} {e.formatting:11s} {e.contracts:8s} {e.build_type:8s}") + print( + f"{e.config!s:17s} c++{e.std:2d} {e.formatting:11s} {e.contracts:8s} {e.build_type:8s}" + ) case "counts": print(f"Total combinations {len(data)}") for (k, v), n in sorted(collector.per_value_counts.items()): diff --git a/.github/job_matrix.py b/.github/job_matrix.py index 9065f389..eb956268 100644 --- a/.github/job_matrix.py +++ b/.github/job_matrix.py @@ -1,10 +1,7 @@ -import argparse -import json -import typing +import dataclasses import itertools import random -import dataclasses -from types import SimpleNamespace +import typing from dataclasses import dataclass @@ -29,6 +26,7 @@ class Configuration: def __str__(self): return self.name + @dataclass(frozen=True, order=True) class MatrixElement: config: Configuration @@ -39,21 +37,21 @@ class MatrixElement: def dataclass_to_json(obj): - """ Convert dataclasses to something json-serialisable """ + """Convert dataclasses to something json-serialisable""" if dataclasses.is_dataclass(obj): return dataclasses.asdict(obj) raise TypeError(f"Unknown object of type {type(obj).__name__}") class CombinationCollector: - """ Incremental builder of MatrixElements, allowing successive selection of entries. - """ + """Incremental builder of MatrixElements, allowing successive selection of entries.""" def __init__(self, full_matrix: dict[str, list[typing.Any]]): self.full_matrix = full_matrix self.combinations: set[MatrixElement] = set() - self.per_value_counts: dict[tuple[str, typing.Any], int] = {(k, v): 0 for k, options in full_matrix.items() for - v in options} + self.per_value_counts: dict[tuple[str, typing.Any], int] = { + (k, v): 0 for k, options in full_matrix.items() for v in options + } def _make_submatrix(self, **overrides): new_matrix = dict(self.full_matrix) @@ -64,7 +62,9 @@ class CombinationCollector: return new_matrix def _add_combination(self, e: MatrixElement): - if e in self.combinations or (e.formatting == "std::format" and not e.config.std_format_support): + if e in self.combinations or ( + e.formatting == "std::format" and not e.config.std_format_support + ): return self.combinations.add(e) # update per_value_counts @@ -72,8 +72,13 @@ class CombinationCollector: idx = (k, v) self.per_value_counts[idx] = self.per_value_counts.get(idx, 0) + 1 - def all_combinations(self, *, filter: typing.Callable[[MatrixElement], bool] | None = None, **overrides): - """ Adds all combinations in the submatrix defined by `overrides`. """ + def all_combinations( + self, + *, + filter: typing.Callable[[MatrixElement], bool] | None = None, + **overrides, + ): + """Adds all combinations in the submatrix defined by `overrides`.""" matrix = self._make_submatrix(**overrides) keys = tuple(matrix.keys()) for combination in itertools.product(*matrix.values()): @@ -82,9 +87,17 @@ class CombinationCollector: continue self._add_combination(cand) - def sample_combinations(self, *, rgen: random.Random, min_samples_per_value: int = 1, - filter: typing.Callable[[MatrixElement], bool] | None = None, **overrides): - """ Adds samples from the submatrix defined by `overrides`, ensuring each individual value appears at least n times. """ + def sample_combinations( + self, + *, + rgen: random.Random, + min_samples_per_value: int = 1, + filter: typing.Callable[[MatrixElement], bool] | None = None, + **overrides, + ): + """Adds samples from the submatrix defined by `overrides`, + ensuring each individual value appears at least n times. + """ matrix = self._make_submatrix(**overrides) missing: dict[tuple[str, typing.Any], int] = {} for key, options in matrix.items(): @@ -109,5 +122,3 @@ class CombinationCollector: remaining = min_samples_per_value - self.per_value_counts.get(idx, 0) if remaining > 0: missing[idx] = remaining - - diff --git a/.github/workflows/ci-clang-tidy.yml b/.github/workflows/ci-clang-tidy.yml index ddf7f23f..402455a1 100644 --- a/.github/workflows/ci-clang-tidy.yml +++ b/.github/workflows/ci-clang-tidy.yml @@ -39,6 +39,7 @@ jobs: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + runs-on: ubuntu-24.04 steps: - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index f5377fc2..f80f7776 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -38,6 +38,7 @@ jobs: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + runs-on: ubuntu-24.04 steps: - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: diff --git a/.github/workflows/ci-freestanding.yml b/.github/workflows/ci-freestanding.yml index 5ce2662c..2b2f97da 100644 --- a/.github/workflows/ci-freestanding.yml +++ b/.github/workflows/ci-freestanding.yml @@ -39,6 +39,7 @@ jobs: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + runs-on: ubuntu-24.04 steps: - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: diff --git a/.github/workflows/ci-test-package-cmake.yml b/.github/workflows/ci-test-package-cmake.yml index 1644c495..bb1e9609 100644 --- a/.github/workflows/ci-test-package-cmake.yml +++ b/.github/workflows/ci-test-package-cmake.yml @@ -43,6 +43,7 @@ jobs: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + runs-on: ubuntu-24.04 steps: - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: From e464677200cf8df116d05335370096696c5f6c28 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 18:58:06 +0100 Subject: [PATCH 19/27] don't test Clang 19 just yet; fix cancel-in-progres --- .github/generate-job-matrix.py | 2 +- .github/workflows/ci-clang-tidy.yml | 13 +++++-------- .github/workflows/ci-conan.yml | 12 ++++-------- .github/workflows/ci-freestanding.yml | 12 ++++-------- .github/workflows/ci-test-package-cmake.yml | 12 ++++-------- 5 files changed, 18 insertions(+), 33 deletions(-) diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index 9ba3b8f3..ae06b9ce 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -89,7 +89,7 @@ configs = { for c in [make_gcc_config(ver) for ver in [12, 13, 14]] + [ make_clang_config(ver, platform) - for ver in [16, 17, 18, 19] + for ver in [16, 17, 18] for platform in ["x86-64", "arm64"] ] + [make_apple_clang_config(ver) for ver in [15]] diff --git a/.github/workflows/ci-clang-tidy.yml b/.github/workflows/ci-clang-tidy.yml index 402455a1..849442b0 100644 --- a/.github/workflows/ci-clang-tidy.yml +++ b/.github/workflows/ci-clang-tidy.yml @@ -34,20 +34,17 @@ on: paths-ignore: - "docs/**" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - cancel-previous: - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - runs-on: ubuntu-24.04 - steps: - - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: name: "Generate build matrix for ${{ github.workflow }}" outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 - needs: cancel-previous steps: - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index f80f7776..b439fa28 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -33,20 +33,16 @@ on: env: CHANNEL: ${{ fromJSON('["testing", "stable"]')[github.ref_type == 'tag' && startsWith(github.ref_name, 'v')] }} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - cancel-previous: - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - runs-on: ubuntu-24.04 - steps: - - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: name: "Generate build matrix for ${{ github.workflow }}" outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 - needs: cancel-previous steps: - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/ci-freestanding.yml b/.github/workflows/ci-freestanding.yml index 2b2f97da..f97d7cfb 100644 --- a/.github/workflows/ci-freestanding.yml +++ b/.github/workflows/ci-freestanding.yml @@ -34,20 +34,16 @@ on: paths-ignore: - "docs/**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - cancel-previous: - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - runs-on: ubuntu-24.04 - steps: - - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: name: "Generate build matrix for ${{ github.workflow }}" outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 - needs: cancel-previous steps: - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/ci-test-package-cmake.yml b/.github/workflows/ci-test-package-cmake.yml index bb1e9609..396ca2a4 100644 --- a/.github/workflows/ci-test-package-cmake.yml +++ b/.github/workflows/ci-test-package-cmake.yml @@ -38,20 +38,16 @@ on: - "example/**" - "test/**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - cancel-previous: - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - runs-on: ubuntu-24.04 - steps: - - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: name: "Generate build matrix for ${{ github.workflow }}" outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 - needs: cancel-previous steps: - name: Set up Python uses: actions/setup-python@v5 From cc9ea9dd1a1dbf44372cb2623d24332777d5a751 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 19:05:04 +0100 Subject: [PATCH 20/27] add cancel-in-progress to all workflows --- .github/workflows/ci-formatting.yml | 4 ++++ .github/workflows/citation.yml | 4 ++++ .github/workflows/codeql.yml | 4 ++++ .github/workflows/documentation.yml | 5 +++++ 4 files changed, 17 insertions(+) diff --git a/.github/workflows/ci-formatting.yml b/.github/workflows/ci-formatting.yml index 086fe625..e61d5b83 100644 --- a/.github/workflows/ci-formatting.yml +++ b/.github/workflows/ci-formatting.yml @@ -24,6 +24,10 @@ name: Formatting CI on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: check: runs-on: ubuntu-24.04 diff --git a/.github/workflows/citation.yml b/.github/workflows/citation.yml index e5f22acf..de8eb51c 100644 --- a/.github/workflows/citation.yml +++ b/.github/workflows/citation.yml @@ -5,6 +5,10 @@ on: paths: - CITATION.cff +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: Validate-CITATION-cff: runs-on: ubuntu-latest diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c7e0abb5..e742e8a1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -22,6 +22,10 @@ on: paths-ignore: - "docs/**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: analyze: name: Analyze diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 3072cd79..09750e9d 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -36,6 +36,11 @@ on: - "mkdocs.yml" permissions: contents: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: deploy: runs-on: ubuntu-latest From a51462cc655a1be9ca960c36392aa18ebad97c45 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 19:09:12 +0100 Subject: [PATCH 21/27] missing checkout in generate-matrix step --- .github/workflows/ci-clang-tidy.yml | 1 + .github/workflows/ci-conan.yml | 1 + .github/workflows/ci-freestanding.yml | 1 + .github/workflows/ci-test-package-cmake.yml | 1 + 4 files changed, 4 insertions(+) diff --git a/.github/workflows/ci-clang-tidy.yml b/.github/workflows/ci-clang-tidy.yml index 849442b0..589a123a 100644 --- a/.github/workflows/ci-clang-tidy.yml +++ b/.github/workflows/ci-clang-tidy.yml @@ -46,6 +46,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 steps: + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index b439fa28..c5e0ad7f 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -44,6 +44,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 steps: + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/.github/workflows/ci-freestanding.yml b/.github/workflows/ci-freestanding.yml index f97d7cfb..4fb016dc 100644 --- a/.github/workflows/ci-freestanding.yml +++ b/.github/workflows/ci-freestanding.yml @@ -45,6 +45,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 steps: + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/.github/workflows/ci-test-package-cmake.yml b/.github/workflows/ci-test-package-cmake.yml index 396ca2a4..10e4e2ed 100644 --- a/.github/workflows/ci-test-package-cmake.yml +++ b/.github/workflows/ci-test-package-cmake.yml @@ -49,6 +49,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 steps: + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: From f4c8e901ac418a6b5d7582d9b9149e6d19026946 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 21:18:36 +0100 Subject: [PATCH 22/27] fix boolean conan options in dynamic CI matrix --- .github/generate-job-matrix.py | 12 ++++++------ .github/job_matrix.py | 19 ++++++++++++++----- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index ae06b9ce..d711d3cc 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -4,7 +4,7 @@ import random import typing from types import SimpleNamespace -from job_matrix import CombinationCollector, Compiler, Configuration, dataclass_to_json +from job_matrix import CombinationCollector, Compiler, Configuration def make_gcc_config(version: int) -> Configuration: @@ -155,20 +155,20 @@ def main(): data = sorted(collector.combinations) + json_data = [e.as_json() for e in data] + if not args.suppress_output: - print( - f"::set-output name=matrix::{json.dumps(data, default=dataclass_to_json)}" - ) + print(f"::set-output name=matrix::{json.dumps(json_data)}") for dbg in args.debug: match dbg: case "yaml": import yaml - json_data = json.loads(json.dumps(data, default=dataclass_to_json)) + json_data = json.loads(json.dumps(json_data)) print(yaml.safe_dump(json_data)) case "json": - print(json.dumps(data, default=dataclass_to_json, indent=4)) + print(json.dumps(json_data, indent=4)) case "combinations": for e in data: print( diff --git a/.github/job_matrix.py b/.github/job_matrix.py index eb956268..204be679 100644 --- a/.github/job_matrix.py +++ b/.github/job_matrix.py @@ -35,12 +35,21 @@ class MatrixElement: contracts: typing.Literal["none", "gsl-lite", "ms-gsl"] build_type: typing.Literal["Release", "Debug"] + def as_json(self): + def dataclass_to_json(obj): + """Convert dataclasses to something json-serialisable""" + if dataclasses.is_dataclass(obj): + return { + k: dataclass_to_json(v) for k, v in dataclasses.asdict(obj).items() + } + return obj -def dataclass_to_json(obj): - """Convert dataclasses to something json-serialisable""" - if dataclasses.is_dataclass(obj): - return dataclasses.asdict(obj) - raise TypeError(f"Unknown object of type {type(obj).__name__}") + ret = dataclass_to_json(self) + # patch boolean conan configuration options + config = ret["config"] + for k in ["cxx_modules"]: + config[k] = "True" if config[k] else "False" + return ret class CombinationCollector: From 01f44c66fad892180fa9a706ab2506794afd7402 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 21:29:42 +0100 Subject: [PATCH 23/27] heed github warning, and use output file instead of set-output command; also, fix freestanding --- .github/generate-job-matrix.py | 9 ++++++++- .github/workflows/ci-freestanding.yml | 4 ---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index d711d3cc..ec819ede 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -1,5 +1,6 @@ import argparse import json +import os import random import typing from types import SimpleNamespace @@ -157,8 +158,14 @@ def main(): json_data = [e.as_json() for e in data] + output_file = os.environ.get("GITHUB_OUTPUT") if not args.suppress_output: - print(f"::set-output name=matrix::{json.dumps(json_data)}") + if output_file: + print(f"Writing outputs to {output_file}") + with open(output_file, "wt") as fh: + fh.write(f"matrix={json.dumps(json_data)}") + else: + print("No output file received!") for dbg in args.debug: match dbg: diff --git a/.github/workflows/ci-freestanding.yml b/.github/workflows/ci-freestanding.yml index 4fb016dc..7a234e02 100644 --- a/.github/workflows/ci-freestanding.yml +++ b/.github/workflows/ci-freestanding.yml @@ -60,10 +60,6 @@ jobs: fail-fast: false matrix: include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} - # TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler - exclude: - - build_type: "Debug" - config: { name: "Clang-18" } env: CC: ${{ matrix.config.compiler.cc }} From 5713243d075be0add1dec594c19caeaa48fac4f4 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 21:41:09 +0100 Subject: [PATCH 24/27] fix clang 16 --- .github/generate-job-matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index ec819ede..b30ea573 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -41,7 +41,7 @@ def make_clang_config( ) match platform: case "x86-64": - ret.os = "ubuntu-24.04" + ret.os = "ubuntu-22.04" if version < 17 else "ubuntu-24.04" case "arm64": ret.os = "macos-14" pfx = f"/opt/homebrew/opt/llvm@{version}/bin/" From ff118784fa544057e3752e6c0e9291e68f8c800e Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 21:48:59 +0100 Subject: [PATCH 25/27] exclude clang18+debug from freestanding again --- .github/generate-job-matrix.py | 11 ++++++++++- .github/job_matrix.py | 10 ++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index b30ea573..e2033b99 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -119,7 +119,12 @@ def main(): rgen = random.Random(args.seed) - collector = CombinationCollector(full_matrix) + collector = CombinationCollector( + full_matrix, + hard_excludes=lambda e: ( + e.formatting == "std::format" and not e.config.std_format_support + ), + ) match args.preset: case None: pass @@ -143,7 +148,11 @@ def main(): case "clang-tidy": collector.all_combinations(config=configs["Clang-18 (x86-64)"]) case "freestanding": + # TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler collector.all_combinations( + filter=lambda e: not ( + e.config.name.startswith("Clang-18") and e.build_type == "Debug" + ), config=[configs[c] for c in ["GCC-14", "Clang-18 (x86-64)"]], contracts="none", std=23, diff --git a/.github/job_matrix.py b/.github/job_matrix.py index 204be679..0458a1e9 100644 --- a/.github/job_matrix.py +++ b/.github/job_matrix.py @@ -55,8 +55,14 @@ class MatrixElement: class CombinationCollector: """Incremental builder of MatrixElements, allowing successive selection of entries.""" - def __init__(self, full_matrix: dict[str, list[typing.Any]]): + def __init__( + self, + full_matrix: dict[str, list[typing.Any]], + *, + hard_excludes: typing.Callable[[MatrixElement], bool] | None = None, + ): self.full_matrix = full_matrix + self.hard_excludes = hard_excludes self.combinations: set[MatrixElement] = set() self.per_value_counts: dict[tuple[str, typing.Any], int] = { (k, v): 0 for k, options in full_matrix.items() for v in options @@ -72,7 +78,7 @@ class CombinationCollector: def _add_combination(self, e: MatrixElement): if e in self.combinations or ( - e.formatting == "std::format" and not e.config.std_format_support + self.hard_excludes is not None and self.hard_excludes(e) ): return self.combinations.add(e) From b35e241a98b1f3dc8451b7dbcd7980ea09df59f0 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 21:58:38 +0100 Subject: [PATCH 26/27] fix clang on macos-14 (arm64) --- .github/generate-job-matrix.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index e2033b99..bd14ee00 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -26,14 +26,11 @@ def make_gcc_config(version: int) -> Configuration: def make_clang_config( version: int, platform: typing.Literal["x86-64", "arm64"] = "x86-64" ) -> Configuration: - ret = SimpleNamespace( + cfg = SimpleNamespace( name=f"Clang-{version} ({platform})", - os=None, # replaced below compiler=SimpleNamespace( type="CLANG", version=version, - cc=f"clang-{version}", - cxx=f"clang++-{version}", ), lib="libc++", cxx_modules=version >= 17, @@ -41,15 +38,18 @@ def make_clang_config( ) match platform: case "x86-64": - ret.os = "ubuntu-22.04" if version < 17 else "ubuntu-24.04" + cfg.os = "ubuntu-22.04" if version < 17 else "ubuntu-24.04" + cfg.compiler.cc = f"clang-{version}" + cfg.compiler.cxx = f"clang++-{version}" case "arm64": - ret.os = "macos-14" - pfx = f"/opt/homebrew/opt/llvm@{version}/bin/" - ret.compiler.cc = pfx + ret.compiler.cc - ret.compiler.cxx = pfx + ret.compiler.cxx + cfg.os = "macos-14" + pfx = f"/opt/homebrew/opt/llvm@{version}/bin" + cfg.compiler.cc = f"{pfx}/clang" + cfg.compiler.cxx = f"{pfx}/clang++" case _: raise KeyError(f"Unsupported platform {platform!r} for Clang") - ret.compiler = Compiler(**vars(ret.compiler)) + ret = cfg + ret.compiler = Compiler(**vars(cfg.compiler)) return Configuration(**vars(ret)) @@ -92,6 +92,8 @@ configs = { make_clang_config(ver, platform) for ver in [16, 17, 18] for platform in ["x86-64", "arm64"] + # arm64 runners are expensive; only consider one version + if ver == 18 or platform != "arm64" ] + [make_apple_clang_config(ver) for ver in [15]] + [make_msvc_config(release="14.4", version=194)] From 9dd59e845c475f0f8789098abc746a9566764b7b Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Wed, 13 Nov 2024 12:34:13 +0100 Subject: [PATCH 27/27] ci: `import_std` now checks if at least C++23 is being used --- .github/workflows/ci-clang-tidy.yml | 7 +++++-- .github/workflows/ci-conan.yml | 2 +- .github/workflows/ci-test-package-cmake.yml | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-clang-tidy.yml b/.github/workflows/ci-clang-tidy.yml index 589a123a..41abb0eb 100644 --- a/.github/workflows/ci-clang-tidy.yml +++ b/.github/workflows/ci-clang-tidy.yml @@ -127,8 +127,11 @@ jobs: sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.cppstd=.*/compiler.cppstd=${{ matrix.std }}/' ~/.conan2/profiles/default sed -i.backup '/^\[settings\]$/,/^\[/ s/^build_type=.*/build_type=${{ matrix.build_type }}/' ~/.conan2/profiles/default conan profile show -pr default - - run: echo "std_format=$([ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV - - run: echo "import_std=$([ "${{ matrix.config.cxx_modules }}" == "True" ] && [ "${{ matrix.config.contracts }}" == "none" ] && [ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV + - name: Set 'std_format' and 'import_std' environment variables + shell: bash + run: | + echo "std_format=$([ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV + echo "import_std=$([ "${{ matrix.std }}" -ge "23" ] && [ "${{ matrix.config.cxx_modules }}" == "True" ] && [ "${{ matrix.config.contracts }}" == "none" ] && [ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV - name: Run clang-tidy shell: bash run: | diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index c5e0ad7f..75abee7e 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -144,7 +144,7 @@ jobs: shell: bash run: | echo "std_format=$([ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV - echo "import_std=$([ "${{ matrix.config.cxx_modules }}" == "True" ] && [ "${{ matrix.config.contracts }}" == "none" ] && [ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV + echo "import_std=$([ "${{ matrix.std }}" -ge "23" ] && [ "${{ matrix.config.cxx_modules }}" == "True" ] && [ "${{ matrix.config.contracts }}" == "none" ] && [ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV - name: Create Conan package if: matrix.config.compiler.type != 'MSVC' shell: bash diff --git a/.github/workflows/ci-test-package-cmake.yml b/.github/workflows/ci-test-package-cmake.yml index 10e4e2ed..d8680291 100644 --- a/.github/workflows/ci-test-package-cmake.yml +++ b/.github/workflows/ci-test-package-cmake.yml @@ -151,8 +151,8 @@ jobs: - name: Set 'std_format' and 'import_std' environment variables shell: bash run: | - echo "std_format=$([ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV - echo "import_std=$([ "${{ matrix.config.cxx_modules }}" == "True" ] && [ "${{ matrix.config.contracts }}" == "none" ] && [ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV + echo "std_format=$([ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV + echo "import_std=$([ "${{ matrix.std }}" -ge "23" ] && [ "${{ matrix.config.cxx_modules }}" == "True" ] && [ "${{ matrix.config.contracts }}" == "none" ] && [ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV - name: Install Conan dependencies shell: bash run: |