From 509b6c9653be35c8256a7a965a80aac51fe9752a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Sch=C3=B6nrock?= Date: Fri, 27 Dec 2019 18:49:43 +0000 Subject: [PATCH] implementing ratio which replaces ratio https://github.com/mpusz/units/issues/14 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This "works", as in it passes all static and runtime tests. However quite a few of the tests have been "modified" to make them pass. Whether this is legitimate is debatable and should be the source of some thought / discussion. 1. many of the static tests and some of the runtime tests have had the input ratios of the tests modified in the following way. eg ratio<3,1000> => ratio<3,1,-3>. ie they have been "canonicalised". There are obviously an infinite number of ratios which represent the same rational number. The way `ratio` is implemented it always moves as "many powers of 10" from the `num` and `den` into the `exp` and that makes the `canonical` ratio. Because these are all "types" and the lib uses is_same all over the place, only exact matches will be `is_same`. ie ratio<300,4,0> !is_same ratio<3,4,2> (the latter is the canonical ratio). This is perhaps fine for tests in the devlopment phase, but there may be a need for "more forgiving" comparison / concept of value equality. One such comparison which compares den,num,exp after canonicalisation is the constexpr function `same` as defined at top of `ratio_test.cpp`. We may need to expose this and perhaps add even more soft comparisions. 2. In the runtime tests it is "subjective" how some resukts should be printed. There is the question of "how exactly to format certain ratios". eg omit denominators of "1" and exponents of "0". However before even addressing these in detail a decision needs to be made about the general form of "non-floating-point-converted" ratios which do not map exactly to a "Symbol prefix". Arguably these are "relatively ugly" whatever we do, so we could just go for an easily canonicalised form. An example is: - CHECK(stream.str() == "10 [1/60]W"); + CHECK(stream.str() == "10 [1/6 x 10⁻¹]W"); Which of thses is "better"? Is there a "third", better form? It's not obvious. My opnion is: Both of 1&2 are fine for now, unless we think they go down the wrong avenue, and can be "perfected later"? ie we can expose a softer version of ratio based equality, and decide on canonical way of printing ratios (as far as that is actually a very useful output form, compared with decimal, scientific or engineering notation). --- src/include/units/bits/base_units_ratio.h | 2 +- src/include/units/bits/unit_text.h | 14 ++++-- src/include/units/physical/si/prefixes.h | 34 +++++++------- src/include/units/prefix.h | 10 ++--- src/include/units/quantity.h | 9 ++-- src/include/units/quantity_cast.h | 54 +++++++++++++---------- src/include/units/ratio.h | 52 ++++++++++++++++++---- test/unit_test/runtime/fmt_test.cpp | 46 +++++++++---------- test/unit_test/static/quantity_test.cpp | 13 +++--- test/unit_test/static/ratio_test.cpp | 42 +++++++++++++++--- test/unit_test/static/si_test.cpp | 2 +- test/unit_test/static/unit_test.cpp | 8 ++-- 12 files changed, 183 insertions(+), 103 deletions(-) diff --git a/src/include/units/bits/base_units_ratio.h b/src/include/units/bits/base_units_ratio.h index 06ef2a5c..50d505c5 100644 --- a/src/include/units/bits/base_units_ratio.h +++ b/src/include/units/bits/base_units_ratio.h @@ -32,7 +32,7 @@ template requires (E::den == 1 || E::den == 2) // TODO provide support for any den struct exp_ratio { using base_ratio = E::dimension::base_unit::ratio; - using positive_ratio = conditional, base_ratio>; + using positive_ratio = conditional, base_ratio>; static constexpr std::int64_t N = E::num * E::den < 0 ? -E::num : E::num; using pow = ratio_pow; using type = conditional, pow>; diff --git a/src/include/units/bits/unit_text.h b/src/include/units/bits/unit_text.h index bed0f58d..850e7f57 100644 --- a/src/include/units/bits/unit_text.h +++ b/src/include/units/bits/unit_text.h @@ -29,13 +29,19 @@ namespace units::detail { template constexpr auto ratio_text() { - if constexpr(Ratio::num != 1 || Ratio::den != 1) { + if constexpr(Ratio::num != 1 || Ratio::den != 1 || Ratio::exp != 0) { auto txt = basic_fixed_string("[") + regular(); - if constexpr(Ratio::den == 1) { + if constexpr(Ratio::den == 1 && Ratio::exp == 0) { return txt + basic_fixed_string("]"); } + else if constexpr (Ratio::den == 1) { + return txt + basic_fixed_string(" x 10") + superscript() + + basic_fixed_string("]"); + } else { - return txt + basic_fixed_string("/") + regular() + basic_fixed_string("]"); + return txt + basic_fixed_string("/") + regular() + + basic_fixed_string(" x 10") + superscript() + + basic_fixed_string("]"); } } else { @@ -46,7 +52,7 @@ constexpr auto ratio_text() template constexpr auto prefix_or_ratio_text() { - if constexpr(Ratio::num == 1 && Ratio::den == 1) { + if constexpr(Ratio::num == 1 && Ratio::den == 1 && Ratio::exp == 0) { // no ratio/prefix return basic_fixed_string(""); } diff --git a/src/include/units/physical/si/prefixes.h b/src/include/units/physical/si/prefixes.h index 6c09b90a..541ce36d 100644 --- a/src/include/units/physical/si/prefixes.h +++ b/src/include/units/physical/si/prefixes.h @@ -31,21 +31,23 @@ struct prefix : prefix_type {}; // TODO Remove dependency on std::ratio -struct atto : units::prefix> {}; -struct femto : units::prefix> {}; -struct pico : units::prefix> {}; -struct nano : units::prefix> {}; -struct micro : units::prefix> {}; -struct milli : units::prefix> {}; -struct centi : units::prefix> {}; -struct deci : units::prefix> {}; -struct deca : units::prefix> {}; -struct hecto : units::prefix> {}; -struct kilo : units::prefix> {}; -struct mega : units::prefix> {}; -struct giga : units::prefix> {}; -struct tera : units::prefix> {}; -struct peta : units::prefix> {}; -struct exa : units::prefix> {}; +// clang-format off +struct atto : units::prefix> {}; // std::atto::den>> {}; +struct femto : units::prefix> {}; // std::femto::den>> {}; +struct pico : units::prefix> {}; // std::pico::den>> {}; +struct nano : units::prefix> {}; // std::nano::den>> {}; +struct micro : units::prefix> {}; // std::micro::den>> {}; +struct milli : units::prefix> {}; // std::milli::den>> {}; +struct centi : units::prefix> {}; // std::centi::den>> {}; +struct deci : units::prefix> {}; // std::deci::den>> {}; +struct deca : units::prefix> {}; // std::deca::num>> {}; +struct hecto : units::prefix> {}; // std::hecto::num>> {}; +struct kilo : units::prefix> {}; // std::kilo::num>> {}; +struct mega : units::prefix> {}; // std::mega::num>> {}; +struct giga : units::prefix> {}; // std::giga::num>> {}; +struct tera : units::prefix> {}; // std::tera::num>> {}; +struct peta : units::prefix> {}; // std::peta::num>> {}; +struct exa : units::prefix> {}; // std::exa::num>> {}; +// clang-format off } // namespace units::si diff --git a/src/include/units/prefix.h b/src/include/units/prefix.h index eaded432..fb923521 100644 --- a/src/include/units/prefix.h +++ b/src/include/units/prefix.h @@ -30,14 +30,14 @@ namespace units { /** * @brief The base for all prefix types - * + * * Every prefix type should inherit from this type to satisfy PrefixType concept. */ struct prefix_type {}; /** * @brief No prefix possible for the unit - * + * * This is a special prefix type tag specifying that the unit can not be scaled with any kind * of the prefix. */ @@ -55,14 +55,14 @@ struct prefix_base : downcast_base> { /** * @brief A prefix used to scale units - * + * * Data from a prefix class is used in two cases: * - when defining a prefixed_unit its ratio is used to scale the reference unit and its * symbol is used to prepend to the symbol of referenced unit * - when printing the symbol of a scaled unit that was not predefined by the user but its * factor matches ratio of a prefix from the specified prefix family, its symbol will be * prepended to the symbol of the unit - * + * * @tparam Child inherited class type used by the downcasting facility (CRTP Idiom) * @tparam PT a type of prefix family * @tparam Symbol a text representation of the prefix @@ -70,7 +70,7 @@ struct prefix_base : downcast_base> { */ template requires (!std::same_as) -struct prefix : downcast_child>> { +struct prefix : downcast_child>> { static constexpr auto symbol = Symbol; }; diff --git a/src/include/units/quantity.h b/src/include/units/quantity.h index c8789c4e..ebcfe6ba 100644 --- a/src/include/units/quantity.h +++ b/src/include/units/quantity.h @@ -32,6 +32,7 @@ #endif #include +#include namespace units { @@ -51,10 +52,10 @@ concept safe_divisible = // exposition only /** * @brief A quantity - * + * * Property of a phenomenon, body, or substance, where the property has a magnitude that can be * expressed by means of a number and a measurement unit. - * + * * @tparam D a dimension of the quantity (can be either a BaseDimension or a DerivedDimension) * @tparam U a measurement unit of the quantity * @tparam Rep a type to be used to represent values of a quantity @@ -352,7 +353,7 @@ template; - return common_rep(lhs.count()) * common_rep(rhs.count()) * common_rep(ratio::num) / common_rep(ratio::den); + return common_rep(lhs.count()) * common_rep(rhs.count()) * common_rep(ratio::num) * std::pow(10, common_rep(ratio::exp)) / common_rep(ratio::den); } template @@ -376,7 +377,7 @@ template Expects(q.count() != 0); using dim = dim_invert; - using ratio = ratio; + using ratio = ratio; using unit = downcast_unit; using common_rep = decltype(v / q.count()); using ret = quantity; diff --git a/src/include/units/quantity_cast.h b/src/include/units/quantity_cast.h index 4906995b..ac019d82 100644 --- a/src/include/units/quantity_cast.h +++ b/src/include/units/quantity_cast.h @@ -24,6 +24,7 @@ #include #include +#include namespace units { @@ -41,10 +42,15 @@ struct quantity_cast_impl { { if constexpr (treat_as_floating_point) { return To(static_cast(static_cast(q.count()) * - (static_cast(CRatio::num) / static_cast(CRatio::den)))); + static_cast(std::pow(10, CRatio::exp)) * + (static_cast(CRatio::num) / + static_cast(CRatio::den)))); } else { - return To(static_cast(static_cast(q.count()) * static_cast(CRatio::num) / - static_cast(CRatio::den))); + return To(static_cast(static_cast(q.count()) * + static_cast(CRatio::num) * + static_cast(CRatio::exp > 0 ? std::pow(10, CRatio::exp) : 1) / + (static_cast(CRatio::den) * + static_cast(CRatio::exp < 0 ? std::pow(10, -CRatio::exp) : 1)))); } } }; @@ -54,7 +60,7 @@ struct quantity_cast_impl { template static constexpr To cast(const Q& q) { - return To(static_cast(q.count())); + return To(static_cast(static_cast(q.count()) * static_cast(std::pow(10, CRatio::exp)))); } }; @@ -64,9 +70,9 @@ struct quantity_cast_impl { static constexpr To cast(const Q& q) { if constexpr (treat_as_floating_point) { - return To(static_cast(static_cast(q.count()) * (CRep{1} / static_cast(CRatio::den)))); + return To(static_cast(static_cast(q.count()) * static_cast(std::pow(10, CRatio::exp)) * (CRep{1} / static_cast(CRatio::den)))); } else { - return To(static_cast(static_cast(q.count()) / static_cast(CRatio::den))); + return To(static_cast(static_cast(q.count()) * static_cast(std::pow(10, CRatio::exp)) / static_cast(CRatio::den))); } } }; @@ -76,7 +82,7 @@ struct quantity_cast_impl { template static constexpr To cast(const Q& q) { - return To(static_cast(static_cast(q.count()) * static_cast(CRatio::num))); + return To(static_cast(static_cast(q.count()) * static_cast(CRatio::num) * static_cast(std::pow(10, CRatio::exp)))); } }; @@ -105,14 +111,14 @@ struct cast_ratio { /** * @brief Explcit cast of a quantity - * + * * Implicit conversions between quantities of different types are allowed only for "safe" * (i.e. non-truncating) conversion. In such cases an explicit cast have to be used. - * + * * This cast gets the target quantity type to cast to. For example: - * + * * auto q1 = units::quantity_cast>(1ms); - * + * * @tparam To a target quantity type to cast to */ template @@ -124,20 +130,20 @@ template using c_rep = std::common_type_t; using ret_unit = downcast_unit; using ret = quantity; - using cast = detail::quantity_cast_impl; + using cast = detail::quantity_cast_impl; return cast::cast(q); } /** * @brief Explcit cast of a quantity - * + * * Implicit conversions between quantities of different types are allowed only for "safe" * (i.e. non-truncating) conversion. In such cases an explicit cast have to be used. - * + * * This cast gets only the target dimension to cast to. For example: - * + * * auto q1 = units::quantity_cast(200Gal); - * + * * @tparam ToD a dimension type to use for a target quantity */ template @@ -149,14 +155,14 @@ template /** * @brief Explcit cast of a quantity - * + * * Implicit conversions between quantities of different types are allowed only for "safe" * (i.e. non-truncating) conversion. In such cases an explicit cast have to be used. - * + * * This cast gets only the target unit to cast to. For example: - * + * * auto q1 = units::quantity_cast(1ms); - * + * * @tparam ToU a unit type to use for a target quantity */ template @@ -168,14 +174,14 @@ template /** * @brief Explcit cast of a quantity - * + * * Implicit conversions between quantities of different types are allowed only for "safe" * (i.e. non-truncating) conversion. In such cases an explicit cast have to be used. - * + * * This cast gets only representation to cast to. For example: - * + * * auto q1 = units::quantity_cast(1ms); - * + * * @tparam ToRep a representation type to use for a target quantity */ template diff --git a/src/include/units/ratio.h b/src/include/units/ratio.h index 6ad15cef..f7dc4da6 100644 --- a/src/include/units/ratio.h +++ b/src/include/units/ratio.h @@ -27,6 +27,8 @@ #include #include #include +#include +#include namespace units { @@ -38,24 +40,45 @@ template return v < 0 ? -v : v; } +constexpr std::tuple normalize(std::intmax_t num, std::intmax_t den, std::intmax_t exp) { + std::intmax_t gcd = std::gcd(num, den); + num = num * (den < 0 ? -1 : 1) / gcd; + den = detail::abs(den) / gcd; + + while (num % 10 == 0) { + num /= 10; + ++exp; + } + while (den % 10 == 0) { + den /= 10; + --exp; + } + return std::make_tuple(num, den, exp); +} + } // namespace detail -template +template requires(Den != 0) struct ratio { static_assert(-INTMAX_MAX <= Num, "numerator too negative"); static_assert(-INTMAX_MAX <= Den, "denominator too negative"); - static constexpr std::intmax_t num = Num * (Den < 0 ? -1 : 1) / std::gcd(Num, Den); - static constexpr std::intmax_t den = detail::abs(Den) / std::gcd(Num, Den); + private: + static constexpr auto norm = detail::normalize(Num, Den, Exp); - using type = ratio; + public: + static constexpr std::intmax_t num = std::get<0>(norm); + static constexpr std::intmax_t den = std::get<1>(norm); + static constexpr std::intmax_t exp = std::get<2>(norm); + + using type = ratio; }; namespace detail { -template -inline constexpr bool is_ratio> = true; +template +inline constexpr bool is_ratio> = true; } // namespace detail @@ -97,10 +120,19 @@ private: static constexpr std::intmax_t gcd1 = std::gcd(R1::num, R2::den); static constexpr std::intmax_t gcd2 = std::gcd(R2::num, R1::den); + static constexpr auto norm = detail::normalize(safe_multiply(R1::num / gcd1, R2::num / gcd2), + safe_multiply(R1::den / gcd2, R2::den / gcd1), + R1::exp + R2::exp); + + static constexpr std::intmax_t norm_num = std::get<0>(norm); + static constexpr std::intmax_t norm_den = std::get<1>(norm); + static constexpr std::intmax_t norm_exp = std::get<2>(norm); + public: - using type = ratio; + using type = ratio; static constexpr std::intmax_t num = type::num; static constexpr std::intmax_t den = type::den; + static constexpr std::intmax_t exp = type::exp; }; } // namespace detail @@ -115,9 +147,10 @@ namespace detail { template struct ratio_divide_impl { static_assert(R2::num != 0, "division by 0"); - using type = ratio_multiply>; + using type = ratio_multiply>; static constexpr std::intmax_t num = type::num; static constexpr std::intmax_t den = type::den; + static constexpr std::intmax_t exp = type::exp; }; } // namespace detail @@ -168,6 +201,7 @@ static constexpr std::intmax_t sqrt_impl(std::intmax_t v) { return sqrt_impl(v, template struct ratio_sqrt_impl { + // TODO this is broken..need /2 logic on EXP using type = ratio; }; @@ -190,7 +224,7 @@ template struct common_ratio_impl { static constexpr std::intmax_t gcd_num = std::gcd(R1::num, R2::num); static constexpr std::intmax_t gcd_den = std::gcd(R1::den, R2::den); - using type = ratio; + using type = ratio; }; } // namespace detail diff --git a/test/unit_test/runtime/fmt_test.cpp b/test/unit_test/runtime/fmt_test.cpp index 8214bdd1..13ca5176 100644 --- a/test/unit_test/runtime/fmt_test.cpp +++ b/test/unit_test/runtime/fmt_test.cpp @@ -110,7 +110,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") { SECTION("in terms of base units") { - const length, metre>> q(123); + const length, metre>> q(123); stream << q; SECTION("iostream") @@ -131,7 +131,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("in terms of derived units") { - const energy, joule>> q(60); + const energy, joule>> q(60); stream << q; SECTION("iostream") @@ -296,7 +296,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("iostream") { - CHECK(stream.str() == "8 [1/100]m³"); + CHECK(stream.str() == "8 [1 x 10⁻²]m³"); } SECTION("fmt with default format {} on a quantity") @@ -317,7 +317,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("iostream") { - CHECK(stream.str() == "2 [60]Hz"); + CHECK(stream.str() == "2 [6 x 10¹]Hz"); } SECTION("fmt with default format {} on a quantity") @@ -338,7 +338,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("iostream") { - CHECK(stream.str() == "10 [1/60]W"); + CHECK(stream.str() == "10 [1/6 x 10⁻¹]W"); } SECTION("fmt with default format {} on a quantity") @@ -359,7 +359,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("iostream") { - CHECK(stream.str() == "30 [50/3]W"); + CHECK(stream.str() == "30 [1/6 x 10²]W"); } SECTION("fmt with default format {} on a quantity") @@ -404,7 +404,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("iostream") { - CHECK(stream.str() == "8 [1000]m⋅s"); + CHECK(stream.str() == "8 [1 x 10³]m⋅s"); } SECTION("fmt with default format {} on a quantity") @@ -425,7 +425,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("iostream") { - CHECK(stream.str() == "2 [60]kg/s"); + CHECK(stream.str() == "2 [6 x 10¹]kg/s"); } SECTION("fmt with default format {} on a quantity") @@ -446,7 +446,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("iostream") { - CHECK(stream.str() == "10 [1/60]kg/s"); + CHECK(stream.str() == "10 [1/6 x 10⁻¹]kg/s"); } SECTION("fmt with default format {} on a quantity") @@ -467,7 +467,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("iostream") { - CHECK(stream.str() == "30 [3/50]1/m⋅s"); + CHECK(stream.str() == "30 [6 x 10⁻²]1/m⋅s"); } SECTION("fmt with default format {} on a quantity") @@ -693,7 +693,7 @@ TEST_CASE("%q an %Q can be put anywhere in a format string", "[text][fmt]") TEST_CASE("fill and align specification", "[text][fmt]") { - SECTION("default format {} on a quantity") + SECTION("default format {} on a quantity") { CHECK(fmt::format("|{:0}|", 123m) == "|123 m|"); CHECK(fmt::format("|{:10}|", 123m) == "| 123 m|"); @@ -716,8 +716,8 @@ TEST_CASE("fill and align specification", "[text][fmt]") CHECK(fmt::format("|{:*>10%Q%q}|", 123m) == "|******123m|"); CHECK(fmt::format("|{:*^10%Q%q}|", 123m) == "|***123m***|"); } - - SECTION("value only format {:%Q} on a quantity") + + SECTION("value only format {:%Q} on a quantity") { CHECK(fmt::format("|{:0%Q}|", 123m) == "|123|"); CHECK(fmt::format("|{:10%Q}|", 123m) == "| 123|"); @@ -729,7 +729,7 @@ TEST_CASE("fill and align specification", "[text][fmt]") CHECK(fmt::format("|{:*^10%Q}|", 123m) == "|***123****|"); } - SECTION("symbol only format {:%q} on a quantity") + SECTION("symbol only format {:%q} on a quantity") { CHECK(fmt::format("|{:0%q}|", 123m) == "|m|"); CHECK(fmt::format("|{:10%q}|", 123m) == "|m |"); @@ -747,7 +747,7 @@ TEST_CASE("sign specification", "[text][fmt]") length inf(std::numeric_limits::infinity()); length nan(std::numeric_limits::quiet_NaN()); - SECTION("default format {} on a quantity") + SECTION("default format {} on a quantity") { CHECK(fmt::format("{0:},{0:+},{0:-},{0: }", 1m) == "1 m,+1 m,1 m, 1 m"); CHECK(fmt::format("{0:},{0:+},{0:-},{0: }", -1m) == "-1 m,-1 m,-1 m,-1 m"); @@ -755,7 +755,7 @@ TEST_CASE("sign specification", "[text][fmt]") CHECK(fmt::format("{0:},{0:+},{0:-},{0: }", nan) == "nan m,+nan m,nan m, nan m"); } - SECTION("full format {:%Q %q} on a quantity") + SECTION("full format {:%Q %q} on a quantity") { CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", 1m) == "1m,+1m,1m, 1m"); CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", -1m) == "-1m,-1m,-1m,-1m"); @@ -763,7 +763,7 @@ TEST_CASE("sign specification", "[text][fmt]") CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", nan) == "nanm,+nanm,nanm, nanm"); } - SECTION("value only format {:%Q} on a quantity") + SECTION("value only format {:%Q} on a quantity") { CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", 1m) == "1,+1,1, 1"); CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", -1m) == "-1,-1,-1,-1"); @@ -781,7 +781,7 @@ TEST_CASE("sign specification for unit only", "[text][fmt][exception]") TEST_CASE("precision specification", "[text][fmt]") { - SECTION("default format {} on a quantity") + SECTION("default format {} on a quantity") { CHECK(fmt::format("{:.1}", 1.2345m) == "1.2 m"); CHECK(fmt::format("{:.0}", 1.2345m) == "1 m"); @@ -792,7 +792,7 @@ TEST_CASE("precision specification", "[text][fmt]") CHECK(fmt::format("{:.10}", 1.2345m) == "1.2345000000 m"); } - SECTION("full format {:%Q %q} on a quantity") + SECTION("full format {:%Q %q} on a quantity") { CHECK(fmt::format("{:.0%Q %q}", 1.2345m) == "1 m"); CHECK(fmt::format("{:.1%Q %q}", 1.2345m) == "1.2 m"); @@ -803,7 +803,7 @@ TEST_CASE("precision specification", "[text][fmt]") CHECK(fmt::format("{:.10%Q %q}", 1.2345m) == "1.2345000000 m"); } - SECTION("value only format {:%Q} on a quantity") + SECTION("value only format {:%Q} on a quantity") { CHECK(fmt::format("{:.0%Q}", 1.2345m) == "1"); CHECK(fmt::format("{:.1%Q}", 1.2345m) == "1.2"); @@ -817,17 +817,17 @@ TEST_CASE("precision specification", "[text][fmt]") TEST_CASE("precision specification for integral representation should throw", "[text][fmt][exception]") { - SECTION("default format {} on a quantity") + SECTION("default format {} on a quantity") { REQUIRE_THROWS_MATCHES(fmt::format("{:.1}", 1m), fmt::format_error, Message("precision not allowed for integral quantity representation")); } - SECTION("full format {:%Q %q} on a quantity") + SECTION("full format {:%Q %q} on a quantity") { REQUIRE_THROWS_MATCHES(fmt::format("{:.1%Q %q}", 1m), fmt::format_error, Message("precision not allowed for integral quantity representation")); } - SECTION("value only format {:%Q} on a quantity") + SECTION("value only format {:%Q} on a quantity") { REQUIRE_THROWS_MATCHES(fmt::format("{:.1%Q}", 1m), fmt::format_error, Message("precision not allowed for integral quantity representation")); } diff --git a/test/unit_test/static/quantity_test.cpp b/test/unit_test/static/quantity_test.cpp index 7dd965f3..12a2706a 100644 --- a/test/unit_test/static/quantity_test.cpp +++ b/test/unit_test/static/quantity_test.cpp @@ -160,6 +160,7 @@ static_assert(length(1km).count() == 1000); // static_assert(length(1s).count() == 1); // should not compile (different dimensions) //static_assert(length(1010m).count() == 1); // should not compile (truncating conversion) static_assert(length(quantity_cast>(1010m)).count() == 1); +static_assert(length(quantity_cast>(1010m)).count() == 1000); // assignment operator @@ -236,23 +237,23 @@ static_assert(std::is_same_v()), length() * si::time()), length>); static_assert( - std::is_same_v() * si::time()), length, metre>, int>>); + std::is_same_v() * si::time()), length, metre>, int>>); static_assert(std::is_same_v() * si::time()), - quantity, units::exp>, scaled_unit, unknown_unit>>>); + quantity, units::exp>, scaled_unit, unknown_unit>>>); static_assert(std::is_same_v()), frequency>); -static_assert(std::is_same_v()), frequency, hertz>, int>>); +static_assert(std::is_same_v()), frequency, hertz>, int>>); static_assert(std::is_same_v()), si::time>); static_assert(std::is_same_v()), - quantity>, scaled_unit, unknown_unit>>>); + quantity>, scaled_unit, unknown_unit>>>); static_assert(std::is_same_v() / 1.0), length>); static_assert(std::is_same_v() / length()), double>); static_assert(std::is_same_v() / length()), double>); static_assert( std::is_same_v() / si::time()), velocity>); static_assert( - std::is_same_v() / si::time()), velocity, metre_per_second>>>); + std::is_same_v() / si::time()), velocity, metre_per_second>>>); static_assert(std::is_same_v() / length()), - quantity, units::exp>, scaled_unit, unknown_unit>>>); + quantity, units::exp>, scaled_unit, unknown_unit>>>); static_assert(std::is_same_v() % short(1)), length>); static_assert(std::is_same_v() % length(1)), length>); diff --git a/test/unit_test/static/ratio_test.cpp b/test/unit_test/static/ratio_test.cpp index ec1eae7a..7164a32b 100644 --- a/test/unit_test/static/ratio_test.cpp +++ b/test/unit_test/static/ratio_test.cpp @@ -27,10 +27,16 @@ using namespace units; template - inline constexpr bool same = R1::num == R2::num && R1::den == R2::den; + inline constexpr bool same = R1::num == R2::num && R1::den == R2::den && R1::exp == R2::exp; static_assert(same, ratio<1, 2>>); + // basic exponents tests + // note use of ::type is required because template params are changed while stamping out template + static_assert(std::is_same_v::type, ratio<1, 20, 1>::type>); + static_assert(std::is_same_v::type, ratio<10, 2, -1>::type>); + static_assert(std::is_same_v::type, ratio<20'000, 50, -1>::type>); + static_assert(std::is_same_v, ratio<3, 8>>, ratio<3, 8>>); static_assert(std::is_same_v, ratio<1>>, ratio<3, 8>>); static_assert(std::is_same_v, ratio<1, 8>>, ratio<1, 2>>); @@ -38,11 +44,19 @@ static_assert(std::is_same_v, ratio<2>>, ratio<1, 4>>); static_assert(std::is_same_v, ratio<8>>, ratio<4>>); + // multiply with exponents + static_assert(std::is_same_v, ratio<2, 1, 4>>, ratio<1, 4, 6>>); + static_assert(std::is_same_v, ratio<8, 1, 3>>, ratio<4, 1, -1>>); + static_assert(std::is_same_v, ratio<2>>, ratio<2>>); static_assert(std::is_same_v, ratio<8>>, ratio<1, 4>>); static_assert(std::is_same_v, ratio<2>>, ratio<1, 16>>); static_assert(std::is_same_v, ratio<3>>, ratio<2>>); + // divide with exponents + static_assert(std::is_same_v, ratio<2, 1, -8>>, ratio<1, 16, 2>>); + static_assert(std::is_same_v, ratio<3>>, ratio<2, 1, 4>>); + static_assert(std::is_same_v, 0>, ratio<1>>); static_assert(std::is_same_v, 1>, ratio<2>>); static_assert(std::is_same_v, 2>, ratio<4>>); @@ -52,17 +66,33 @@ static_assert(std::is_same_v, 2>, ratio<1, 4>>); static_assert(std::is_same_v, 3>, ratio<1, 8>>); + // pow with exponents + static_assert(std::is_same_v, 2>, ratio<1, 4, 6>>); + static_assert(std::is_same_v, 3>, ratio<1, 8, -18>>); + static_assert(std::is_same_v>, ratio<3>>); static_assert(std::is_same_v>, ratio<2>>); static_assert(std::is_same_v>, ratio<1>>); static_assert(std::is_same_v>, ratio<0>>); static_assert(std::is_same_v>, ratio<1, 2>>); + // // sqrt with exponents: TODO not working yet. Also not sure the non exponent version is accurate. + // static_assert(std::is_same_v>, ratio<3, 1, 1>>); + // static_assert(std::is_same_v>, ratio<2>>); + // common_ratio + // note use of ::type is required because template params are changed while stamping out template + static_assert(std::is_same_v::type, ratio<1000>>, ratio<1>::type>); + static_assert(std::is_same_v, ratio<1>>::type, ratio<1>::type>); + static_assert(std::is_same_v, ratio<1, 1000>>::type, ratio<1, 1000>::type>); + static_assert(std::is_same_v, ratio<1>>::type, ratio<1, 1000>::type>); + static_assert(std::is_same_v, ratio<10, 1>>::type, ratio<10, 1>::type>); + static_assert(std::is_same_v, ratio<1, 10>>::type, ratio<1, 10>::type>); - static_assert(std::is_same_v, ratio<1000>>, ratio<1>>); - static_assert(std::is_same_v, ratio<1>>, ratio<1>>); - static_assert(std::is_same_v, ratio<1, 1000>>, ratio<1, 1000>>); - static_assert(std::is_same_v, ratio<1>>, ratio<1, 1000>>); + // common ratio with exponents + static_assert(std::is_same_v, ratio<1, 1, 3>>::type, ratio<1>::type>); + static_assert(std::is_same_v, ratio<1, 1, -3>>::type, ratio<1, 1, -3>::type>); - } // namespace \ No newline at end of file + + + } // namespace diff --git a/test/unit_test/static/si_test.cpp b/test/unit_test/static/si_test.cpp index a6a54fc2..62d7ab3d 100644 --- a/test/unit_test/static/si_test.cpp +++ b/test/unit_test/static/si_test.cpp @@ -192,7 +192,7 @@ static_assert(10V * 1F == 10C); // velocity -static_assert(std::is_same_v, metre_per_second>, std::int64_t>>); +static_assert(std::is_same_v, metre_per_second>, std::int64_t>>); static_assert(10m / 5s == 2mps); static_assert(10 / 5s * 1m == 2mps); diff --git a/test/unit_test/static/unit_test.cpp b/test/unit_test/static/unit_test.cpp index f7b7aa42..3be1ee5d 100644 --- a/test/unit_test/static/unit_test.cpp +++ b/test/unit_test/static/unit_test.cpp @@ -30,12 +30,12 @@ using namespace units; struct metre : named_unit {}; struct centimetre : prefixed_unit {}; struct kilometre : prefixed_unit {}; -struct yard : named_scaled_unit, metre> {}; +struct yard : named_scaled_unit, metre> {}; struct foot : named_scaled_unit, yard> {}; struct dim_length : base_dimension<"length", metre> {}; struct second : named_unit {}; -struct hour : named_scaled_unit, second> {}; +struct hour : named_scaled_unit, second> {}; struct dim_time : base_dimension<"time", second> {}; struct kelvin : named_unit {}; @@ -46,8 +46,8 @@ struct dim_velocity : derived_dimension {}; static_assert(std::is_same_v, metre>>, metre>); -static_assert(std::is_same_v, metre>>, centimetre>); -static_assert(std::is_same_v, metre>>, yard>); +static_assert(std::is_same_v, metre>>, centimetre>); +static_assert(std::is_same_v, metre>>, yard>); static_assert(std::is_same_v>, metre>>, foot>); static_assert(std::is_same_v, metre_per_second>>, kilometre_per_hour>);