diff --git a/au/code/au/magnitude.hh b/au/code/au/magnitude.hh index 230fb54b..3a4daf35 100644 --- a/au/code/au/magnitude.hh +++ b/au/code/au/magnitude.hh @@ -22,6 +22,7 @@ #include "au/power_aliases.hh" #include "au/stdx/utility.hh" #include "au/utility/factoring.hh" +#include "au/utility/string_constant.hh" #include "au/zero.hh" // "Magnitude" is a collection of templated types, representing positive real numbers. @@ -60,6 +61,14 @@ using MagQuotientT = PackQuotientT; template using MagInverseT = PackInverseT; +// A printable label to indicate the Magnitude for human readers. +template +struct MagnitudeLabel; + +// A sizeof()-compatible API to get the label for a Magnitude. +template +constexpr const auto &mag_label(MagT = MagT{}); + // A helper function to create a Magnitude from an integer constant. template constexpr auto mag(); @@ -559,6 +568,83 @@ constexpr T get_value(Magnitude m) { return result.value; } +//////////////////////////////////////////////////////////////////////////////////////////////////// +// `MagnitudeLabel` implementation. + +namespace detail { +enum class MagLabelCategory { + INTEGER, + RATIONAL, + UNSUPPORTED, +}; + +template +constexpr MagLabelCategory categorize_mag_label(Magnitude m) { + if (IsInteger>::value) { + return get_value_result(m).outcome == MagRepresentationOutcome::OK + ? MagLabelCategory::INTEGER + : MagLabelCategory::UNSUPPORTED; + } + if (IsRational>::value) { + return MagLabelCategory::RATIONAL; + } + return MagLabelCategory::UNSUPPORTED; +} + +template +struct MagnitudeLabelImplementation { + static constexpr const char value[] = "(UNLABELED SCALE FACTOR)"; + + static constexpr const bool has_exposed_slash = false; +}; +template +constexpr const char MagnitudeLabelImplementation::value[]; +template +constexpr const bool MagnitudeLabelImplementation::has_exposed_slash; + +template +struct MagnitudeLabelImplementation + : detail::IToA(MagT{})> { + static constexpr const bool has_exposed_slash = false; +}; +template +constexpr const bool + MagnitudeLabelImplementation::has_exposed_slash; + +// Analogous to `detail::ExtendedLabel`, but for magnitudes. +// +// This makes it easier to name the exact type for compound labels. +template +using ExtendedMagLabel = + StringConstant::value...).size() + ExtensionStrlen>; + +template +struct MagnitudeLabelImplementation { + using LabelT = ExtendedMagLabel<3u, NumeratorT, DenominatorT>; + static constexpr LabelT value = join_by( + " / ", MagnitudeLabel>::value, MagnitudeLabel>::value); + + static constexpr const bool has_exposed_slash = true; +}; +template +constexpr typename MagnitudeLabelImplementation::LabelT + MagnitudeLabelImplementation::value; +template +constexpr const bool + MagnitudeLabelImplementation::has_exposed_slash; + +} // namespace detail + +template +struct MagnitudeLabel> + : detail::MagnitudeLabelImplementation, + detail::categorize_mag_label(Magnitude{})> {}; + +template +constexpr const auto &mag_label(MagT) { + return detail::as_char_array(MagnitudeLabel::value); +} + //////////////////////////////////////////////////////////////////////////////////////////////////// // `CommonMagnitude` implementation. diff --git a/au/code/au/magnitude_test.cc b/au/code/au/magnitude_test.cc index ccf818ff..07ecdb21 100644 --- a/au/code/au/magnitude_test.cc +++ b/au/code/au/magnitude_test.cc @@ -23,6 +23,7 @@ using ::testing::DoubleEq; using ::testing::Eq; using ::testing::FloatEq; using ::testing::StaticAssertTypeEq; +using ::testing::StrEq; namespace au { namespace { @@ -60,6 +61,26 @@ TEST(Magnitude, PowersBehaveCorrectly) { TEST(Magnitude, RootsBehaveCorrectly) { EXPECT_EQ(root<3>(mag<8>()), mag<2>()); } +TEST(MagnitudeLabel, HandlesIntegers) { + EXPECT_THAT(mag_label(mag<1>()), StrEq("1")); + EXPECT_THAT(mag_label(mag<287'987>()), StrEq("287987")); +} + +TEST(MagnitudeLabel, HandlesRationals) { + EXPECT_THAT(mag_label(mag<1>() / mag<2>()), StrEq("1 / 2")); + EXPECT_THAT(mag_label(mag<541>() / mag<123456789>()), StrEq("541 / 123456789")); +} + +TEST(MagnitudeLabel, DefaultsToUnlabeledForFactorTooBig) { + // Someday, we'll find a better way to handle this; this just unblocks the first implementation. + EXPECT_THAT(mag_label(pow<24>(mag<10>())), StrEq("(UNLABELED SCALE FACTOR)")); +} + +TEST(MagnitudeLabel, IndicatesPresenceOfExposedSlash) { + EXPECT_FALSE(MagnitudeLabel())>::has_exposed_slash); + EXPECT_TRUE(MagnitudeLabel() / mag<2>())>::has_exposed_slash); +} + TEST(Pi, HasCorrectValue) { // This pattern makes sure the test will fail if we _run_ on an architecture without `M_PIl`. // It does, however, permit us to _build_ on such an architecture with no problem. diff --git a/au/code/au/unit_of_measure.hh b/au/code/au/unit_of_measure.hh index d88f335d..b224810c 100644 --- a/au/code/au/unit_of_measure.hh +++ b/au/code/au/unit_of_measure.hh @@ -707,15 +707,6 @@ struct ComputeCommonPointUnit // `UnitLabel` implementation. namespace detail { -template -constexpr auto as_char_array(const char (&x)[N]) -> const char (&)[N] { - return x; -} - -template -constexpr auto as_char_array(const StringConstant &x) -> const char (&)[N + 1] { - return x.char_array(); -} template using HasLabel = decltype(Unit::label); diff --git a/au/code/au/utility/string_constant.hh b/au/code/au/utility/string_constant.hh index 8964f9b9..451444ac 100644 --- a/au/code/au/utility/string_constant.hh +++ b/au/code/au/utility/string_constant.hh @@ -254,5 +254,15 @@ constexpr auto parens_if(const StringT &s) { return concatenate(ParensIf::open(), s, ParensIf::close()); } +template +constexpr auto as_char_array(const char (&x)[N]) -> const char (&)[N] { + return x; +} + +template +constexpr auto as_char_array(const StringConstant &x) -> const char (&)[N + 1] { + return x.char_array(); +} + } // namespace detail } // namespace au