diff --git a/doc/DESIGN.md b/doc/DESIGN.md index 88b85fc4..77040cc9 100644 --- a/doc/DESIGN.md +++ b/doc/DESIGN.md @@ -183,8 +183,8 @@ contained base dimensions. Beside providing ordering to base dimensions it also `derived_dimension` is also able to form a dimension type based not only on base dimensions but it can take other derived dimensions as well. In such a case units defined with a -`deduced_derived_unit` helper will get symbols of units for those derived dimension rather -than system base units. +`coherent_derived_unit` and `deduced_derived_unit` helper will get symbols of units for those +derived dimension (if those are named units) rather than system base units. For example to form `pressure` user can provide the following two definitions: @@ -264,12 +264,14 @@ or `coherent_derived_unit` class templates: ```cpp template struct named_coherent_derived_unit : downcast_child>> { + static constexpr bool is_named = true; static constexpr auto symbol = Symbol; using prefix_type = PT; }; template struct coherent_derived_unit : downcast_child>> { + static constexpr bool is_named = false; static constexpr auto symbol = /* ... */; using prefix_type = no_prefix; }; @@ -284,7 +286,7 @@ template concept PrefixType = std::derived_from; ``` -For example to define the coherent unit of `length`: +For example to define the named coherent unit of `length`: ```cpp struct metre : named_coherent_derived_unit {}; @@ -293,11 +295,21 @@ struct metre : named_coherent_derived_unit {}; Again, similarly to `derived_dimension`, the first class template parameter is a CRTP idiom used to provide downcasting facility (described below). +`coherent_derived_unit` also provides a synthetized unit symbol. If all coherent units of +the recipe provided in a `derived_dimension` are named than the recipe is used to built a +unit symbol. Otherwise, the symbol will be created based on ingredient base units. + +```cpp +struct surface_tension : derived_dimension, exp> {}; +struct newton_per_metre : coherent_derived_unit {}; +``` + To create scaled unit the following template should be used: ```cpp template struct named_scaled_derived_unit : downcast_child> { + static constexpr bool is_named = true; static constexpr auto symbol = Symbol; using prefix_type = PT; }; @@ -318,6 +330,7 @@ template struct prefixed_derived_unit : downcast_child>> { + static constexpr bool is_named = true; static constexpr auto symbol = P::symbol + U::symbol; using prefix_type = P::prefix_type; }; @@ -353,12 +366,15 @@ For the cases where determining the exact ratio is not trivial another helper ca ```cpp template struct named_deduced_derived_unit : downcast_child { + static constexpr bool is_named = true; static constexpr auto symbol = Symbol; using prefix_type = PT; }; template + requires U::is_named && (Us::is_named && ... && true) struct deduced_derived_unit : downcast_child { + static constexpr bool is_named = false; static constexpr auto symbol = /* even more magic to get the correct unit symbol */; using prefix_type = no_prefix; }; diff --git a/src/include/units/unit.h b/src/include/units/unit.h index 53696969..ec7b3563 100644 --- a/src/include/units/unit.h +++ b/src/include/units/unit.h @@ -231,10 +231,20 @@ namespace units { } } + template + constexpr auto dimension_symbol() + { + if constexpr(BaseDimension) + return Dim::symbol; + else + // coherent derived unit + return downcast>>::symbol; + } + template constexpr auto base_symbol_text_impl(dimension, std::index_sequence) { - return (exp_text() + ...); + return (exp_text(), Idxs>() + ...); } template @@ -243,6 +253,22 @@ namespace units { return base_symbol_text_impl(d, std::index_sequence_for()); } + template + constexpr bool all_named(dimension) + { + return (downcast>>::is_named && ...); + } + + template + constexpr auto base_symbol_text() + { + using recipe = typename Dim::recipe; + if constexpr(all_named(recipe())) + return base_symbol_text(recipe()); + else + return base_symbol_text(Dim()); + } + template constexpr auto exp_validate_and_text() { @@ -303,30 +329,36 @@ namespace units { template struct named_coherent_derived_unit : downcast_child>> { + static constexpr bool is_named = true; static constexpr auto symbol = Symbol; using prefix_type = PT; }; template struct coherent_derived_unit : downcast_child>> { - static constexpr auto symbol = detail::base_symbol_text(Dim()); + static constexpr bool is_named = false; + static constexpr auto symbol = detail::base_symbol_text(); using prefix_type = no_prefix; }; template struct named_scaled_derived_unit : downcast_child> { + static constexpr bool is_named = true; static constexpr auto symbol = Symbol; using prefix_type = PT; }; template struct named_deduced_derived_unit : downcast_child> { + static constexpr bool is_named = true; static constexpr auto symbol = Symbol; using prefix_type = PT; }; template + requires U::is_named && (Us::is_named && ... && true) struct deduced_derived_unit : downcast_child> { + static constexpr bool is_named = false; static constexpr auto symbol = detail::deduced_symbol_text(); using prefix_type = no_prefix; }; @@ -334,6 +366,7 @@ namespace units { template requires (!std::same_as) struct prefixed_derived_unit : downcast_child>> { + static constexpr bool is_named = true; static constexpr auto symbol = P::symbol + U::symbol; using prefix_type = P::prefix_type; }; diff --git a/test/unit_test/runtime/CMakeLists.txt b/test/unit_test/runtime/CMakeLists.txt index b8652202..7a850e88 100644 --- a/test/unit_test/runtime/CMakeLists.txt +++ b/test/unit_test/runtime/CMakeLists.txt @@ -24,7 +24,8 @@ add_executable(unit_tests_runtime catch_main.cpp digital_information_test.cpp math_test.cpp - text_test.cpp + fmt_test.cpp + fmt_units_test.cpp ) target_link_libraries(unit_tests_runtime PRIVATE diff --git a/test/unit_test/runtime/text_test.cpp b/test/unit_test/runtime/fmt_test.cpp similarity index 97% rename from test/unit_test/runtime/text_test.cpp rename to test/unit_test/runtime/fmt_test.cpp index 8c324915..ed027052 100644 --- a/test/unit_test/runtime/text_test.cpp +++ b/test/unit_test/runtime/fmt_test.cpp @@ -194,6 +194,27 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); } } + + SECTION("surface tension") + { + const auto q = 20.N / 2m; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "10 N/m"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } + } } SECTION("deduced derived unit") diff --git a/test/unit_test/runtime/fmt_units_test.cpp b/test/unit_test/runtime/fmt_units_test.cpp new file mode 100644 index 00000000..ef296e33 --- /dev/null +++ b/test/unit_test/runtime/fmt_units_test.cpp @@ -0,0 +1,114 @@ +// 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 "units/dimensions/area.h" +#include "units/dimensions/frequency.h" +#include "units/dimensions/power.h" +#include "units/dimensions/velocity.h" +#include "units/dimensions/volume.h" +#include "units/dimensions/surface_tension.h" +#include "units/format.h" +#include + +using namespace units; + +TEST_CASE("fmt::format on synthesized unit symbols", "[text][fmt]") +{ + SECTION("time") + { + REQUIRE(fmt::format("{}", 1ns) == "1 ns"); + REQUIRE(fmt::format("{}", 1us) == "1 µs"); + REQUIRE(fmt::format("{}", 1ms) == "1 ms"); + } + + SECTION("length") + { + REQUIRE(fmt::format("{}", 1mm) == "1 mm"); + REQUIRE(fmt::format("{}", 1cm) == "1 cm"); + REQUIRE(fmt::format("{}", 1km) == "1 km"); + } + + SECTION("mass") + { + REQUIRE(fmt::format("{}", 1kg) == "1 kg"); + } + + SECTION("area") + { + REQUIRE(fmt::format("{}", 1sq_m) == "1 m²"); + REQUIRE(fmt::format("{}", 1sq_mm) == "1 mm²"); + REQUIRE(fmt::format("{}", 1sq_cm) == "1 cm²"); + REQUIRE(fmt::format("{}", 1sq_km) == "1 km²"); + REQUIRE(fmt::format("{}", 1sq_ft) == "1 ft²"); + } + + SECTION("volume") + { + REQUIRE(fmt::format("{}", 1cub_m) == "1 m³"); + REQUIRE(fmt::format("{}", 1cub_mm) == "1 mm³"); + REQUIRE(fmt::format("{}", 1cub_cm) == "1 cm³"); + REQUIRE(fmt::format("{}", 1cub_km) == "1 km³"); + REQUIRE(fmt::format("{}", 1cub_ft) == "1 ft³"); + } + + SECTION("frequency") + { + REQUIRE(fmt::format("{}", 1mHz) == "1 mHz"); + REQUIRE(fmt::format("{}", 1kHz) == "1 kHz"); + REQUIRE(fmt::format("{}", 1MHz) == "1 MHz"); + REQUIRE(fmt::format("{}", 1GHz) == "1 GHz"); + REQUIRE(fmt::format("{}", 1THz) == "1 THz"); + } + + SECTION("velocity") + { + REQUIRE(fmt::format("{}", 1mps) == "1 m/s"); + REQUIRE(fmt::format("{}", 1kmph) == "1 km/h"); + REQUIRE(fmt::format("{}", 1mph) == "1 mi/h"); + } + + SECTION("acceleration") + { + REQUIRE(fmt::format("{}", 1mps_sq) == "1 m/s²"); + } + + SECTION("energy") + { + REQUIRE(fmt::format("{}", 1mJ) == "1 mJ"); + REQUIRE(fmt::format("{}", 1kJ) == "1 kJ"); + REQUIRE(fmt::format("{}", 1MJ) == "1 MJ"); + REQUIRE(fmt::format("{}", 1GJ) == "1 GJ"); + } + + SECTION("power") + { + REQUIRE(fmt::format("{}", 1mW) == "1 mW"); + REQUIRE(fmt::format("{}", 1kW) == "1 kW"); + REQUIRE(fmt::format("{}", 1MW) == "1 MW"); + REQUIRE(fmt::format("{}", 1GW) == "1 GW"); + } + + SECTION("surface tension") + { + REQUIRE(fmt::format("{}", 1Npm) == "1 N/m"); + } +}