diff --git a/src/include/units/bits/fixed_string.h b/src/include/units/bits/fixed_string.h index 8d6654ce..b49a51a5 100644 --- a/src/include/units/bits/fixed_string.h +++ b/src/include/units/bits/fixed_string.h @@ -21,6 +21,7 @@ // SOFTWARE. #include +#include namespace units { @@ -68,6 +69,12 @@ namespace units { } return first1 == last1 && first2 != last2; } + + template + friend std::basic_ostream& operator<<(std::basic_ostream& os, const basic_fixed_string& txt) + { + return os << txt.c_str(); + } }; template diff --git a/src/include/units/format.h b/src/include/units/format.h new file mode 100644 index 00000000..1dbfc831 --- /dev/null +++ b/src/include/units/format.h @@ -0,0 +1,106 @@ +// 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 + +namespace units { + + namespace detail { + + template + void print_ratio(std::basic_ostream& os) + { + if constexpr(Ratio::num != 1 || Ratio::den != 1) { + if constexpr(Ratio::den == 1) { + os << "[" << Ratio::num << "]"; + } + else { + os << "[" << Ratio::num << "/" << Ratio::den << "]"; + } + } + } + + template + inline constexpr std::string_view ratio_txt = ""; + + template<> inline constexpr std::string_view ratio_txt> = "a"; + template<> inline constexpr std::string_view ratio_txt> = "f"; + template<> inline constexpr std::string_view ratio_txt> = "p"; + template<> inline constexpr std::string_view ratio_txt> = "n"; + template<> inline constexpr std::string_view ratio_txt> = "\u00b5\u0073"; + template<> inline constexpr std::string_view ratio_txt> = "m"; + template<> inline constexpr std::string_view ratio_txt> = "c"; + template<> inline constexpr std::string_view ratio_txt> = "d"; + template<> inline constexpr std::string_view ratio_txt> = "da"; + template<> inline constexpr std::string_view ratio_txt> = "h"; + template<> inline constexpr std::string_view ratio_txt> = "k"; + template<> inline constexpr std::string_view ratio_txt> = "M"; + template<> inline constexpr std::string_view ratio_txt> = "G"; + template<> inline constexpr std::string_view ratio_txt> = "T"; + template<> inline constexpr std::string_view ratio_txt> = "P"; + template<> inline constexpr std::string_view ratio_txt> = "E"; + + template + void print_prefix_or_ratio(std::basic_ostream& os) + { + if constexpr(Ratio::num != 1 || Ratio::den != 1) { + constexpr auto prefix = ratio_txt; + + if constexpr(prefix != "") { + // print as a prefixed unit + os << prefix; + } + else { + // print as a ratio of the coherent unit + print_ratio(os); + } + } + } + + template + void print_dimensions(std::basic_ostream& os, dimension) + { + bool first = true; + auto ingr_printer = [&](E) { + if constexpr(E::num < 0) { + os << (first ? "1/" : "/"); + } + else { + os << (first ? "" : "*"); + } + os << E::dimension::symbol; + if constexpr(E::den != 1) { + os << "^(" << abs(E::num) << "/" << E::den << ")"; + } + else if constexpr(abs(E::num) != 1) { + os << "^" << abs(E::num); + } + first = false; + }; + (ingr_printer(Es{}), ...); + } + + } + +} // namespace units diff --git a/src/include/units/quantity.h b/src/include/units/quantity.h index 308deeec..edffe3d1 100644 --- a/src/include/units/quantity.h +++ b/src/include/units/quantity.h @@ -24,7 +24,9 @@ #include #include +#include #include +#include namespace units { @@ -275,6 +277,37 @@ namespace units { value_ %= q.count(); return *this; } + + template + friend std::basic_ostream& operator<<(std::basic_ostream& os, const quantity& q) + { + os << q.count() << " "; + if constexpr(!detail::is_unit) { + // print user-defined unit + os << unit::symbol::c_str(); + } + else { + using ratio = quantity::unit::ratio; + using dim = quantity::unit::dimension; + if constexpr(!detail::is_dimension) { + // print as a prefix or ratio of a coherent unit + detail::print_prefix_or_ratio(os); + + if constexpr(!detail::is_dimension) { + // print coherent unit symbol defined by the user + os << downcast_target>::symbol::c_str(); + } + } + else { + // print as a ratio of a coherent unit + detail::print_ratio(os); + + // print coherent unit dimensions and their exponents + detail::print_dimensions(os, dim{}); + } + } + return os; + } }; template @@ -442,9 +475,3 @@ namespace units { } } // namespace units - - -// template -// basic_ostream& -// operator<<(basic_ostream& os, -// const duration& d); \ No newline at end of file diff --git a/test/unit_test/runtime/CMakeLists.txt b/test/unit_test/runtime/CMakeLists.txt index 5242e8e5..cfce6f87 100644 --- a/test/unit_test/runtime/CMakeLists.txt +++ b/test/unit_test/runtime/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(unit_tests_runtime catch_main.cpp math_test.cpp + text_test.cpp ) target_link_libraries(unit_tests_runtime PRIVATE diff --git a/test/unit_test/runtime/math_test.cpp b/test/unit_test/runtime/math_test.cpp index 6868971b..5491397b 100644 --- a/test/unit_test/runtime/math_test.cpp +++ b/test/unit_test/runtime/math_test.cpp @@ -21,7 +21,6 @@ // SOFTWARE. #include "units/math.h" -#include "print_helpers.h" #include "units/dimensions/area.h" #include "units/dimensions/volume.h" #include diff --git a/test/unit_test/runtime/print_helpers.h b/test/unit_test/runtime/print_helpers.h deleted file mode 100644 index 3e799eb8..00000000 --- a/test/unit_test/runtime/print_helpers.h +++ /dev/null @@ -1,36 +0,0 @@ -// 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 - -// TODO Remove when a text formatting is implemented - -namespace units { - - template - std::ostream& operator<<(std::ostream& os, const quantity& value) - { - return os << value.count() << Unit::symbol::c_str(); - } - -} diff --git a/test/unit_test/runtime/text_test.cpp b/test/unit_test/runtime/text_test.cpp new file mode 100644 index 00000000..3ecfb4e5 --- /dev/null +++ b/test/unit_test/runtime/text_test.cpp @@ -0,0 +1,169 @@ +// 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/math.h" +#include +#include + +using namespace units; + +TEST_CASE("operator<< on a quantity", "[text][ostream]") +{ + std::stringstream stream; + + SECTION("quantity with a predefined unit") + { + SECTION("integral representation") + { + stream << 16sq_m; + REQUIRE(stream.str() == "16 m^2"); + } + + SECTION("floating-point representation") + { + stream << 72.5kmph; + REQUIRE(stream.str() == "72.5 km/h"); + } + } + + SECTION("quantity with a predefined dimension but unknown unit") + { + SECTION("unit::ratio as an SI prefix") + { + stream << 4.N * 2cm; + REQUIRE(stream.str() == "8 cJ"); + } + + SECTION("unit::ratio::num != 1 && unit::ratio::den == 1") + { + stream << 4 * 2min / (2s * 2s); + REQUIRE(stream.str() == "2 [60]Hz"); + } + + SECTION("unit::ratio::num == 1 && unit::ratio::den != 1") + { + stream << 20._J / 2min; + REQUIRE(stream.str() == "10 [1/60]W"); + } + + SECTION("unit::ratio::num != 1 && unit::ratio::den != 1") + { + stream << 60.kJ / 2min; + REQUIRE(stream.str() == "30 [50/3]W"); + } + } + + SECTION("quantity with an unkown dimension") + { + SECTION("unit::ratio::num == 1 && unit::ratio::den == 1") + { + stream << 2s * 2m * 2kg; + REQUIRE(stream.str() == "8 m*kg*s"); + } + + SECTION("unit::ratio as an SI prefix") + { + stream << 4km * 2s; + REQUIRE(stream.str() == "8 [1000]m*s"); + } + + SECTION("unit::ratio::num != 1 && unit::ratio::den == 1") + { + stream << 4kg * 2min / (2s * 2s); + REQUIRE(stream.str() == "2 [60]kg/s"); + } + + SECTION("unit::ratio::num == 1 && unit::ratio::den != 1") + { + stream << 20.kg / 2min; + REQUIRE(stream.str() == "10 [1/60]kg/s"); + } + + SECTION("unit::ratio::num != 1 && unit::ratio::den != 1") + { + stream << 60.min / 2km; + REQUIRE(stream.str() == "30 [3/50]1/m*s"); + } + + SECTION("exp::num == 1 && exp::den == 1") + { + stream << 4m * 2s; + REQUIRE(stream.str() == "8 m*s"); + } + + SECTION("exp::num == 2 && exp::den == 1 for positive exponent") + { + stream << 4m * 2s * 2s; + REQUIRE(stream.str() == "16 m*s^2"); + } + + SECTION("exp::num == 2 && exp::den == 1 for negative exponent (first dimension)") + { + stream << 8.s / 2m / 2m; + REQUIRE(stream.str() == "2 1/m^2*s"); + } + + SECTION("exp::num == 2 && exp::den == 1 for negative exponent (not first dimension)") + { + stream << 8.m / 2kg / 2kg; + REQUIRE(stream.str() == "2 m/kg^2"); + } + + SECTION("fractional positive exponent") + { + stream << sqrt(9.m); + REQUIRE(stream.str() == "3 m^(1/2)"); + } + + SECTION("fractional negative exponent") + { + stream << sqrt(9 / 1.m); + REQUIRE(stream.str() == "3 1/m^(1/2)"); + } + } +} + +// SCENARIO("default format '{}' produces the same output as operator<<", "[text][fmt]") +// { +// GIVEN("A quantity q") { +// auto q = 2m; +// REQUIRE(q.count() == 2); + +// WHEN("format(\"{}\", q) is called") { +// std::string fmtstr = format("{}", q); + +// THEN("the same output as operator<< is returned") { +// std::stringstream stream; +// stream << q; +// REQUIRE(fmtstr == stream.str()); +// } +// } +// } +// } + +// Restate operator<< definitions in terms of std::format to make I/O manipulators apply to whole objects +// rather than their parts \ No newline at end of file