Skip to content

Commit

Permalink
Add labels for integer and rational magnitudes
Browse files Browse the repository at this point in the history
Think of this as "just enough of #85 to unblock #105".  The fact that
our common unit labels don't tell you the _size_ of the unit (#105) has
really, really been bugging me for a long time.  Recently, I realized we
don't need to do all of #85 to get it!  Instead, all we need to do is:

1. Build a _mechanism_ that we can easily _extend_.

2. Cover the most important use cases.

This PR creates the `MagnitudeLabel` trait mechanism (also accessible
via a function/value interface as `mag_label`).  We enumerate the
various categories of magnitudes that we can label, defaulting to
"unsupported".  The first two supported categories are _integers_ (that
fit in `std::uintmax_t`), and _rationals_.

We also add a trait, `has_exposed_slash`, looking forward to the obvious
use case of auto-generating labels for scaled units.  Those labels will
have the form `"[M U]"` for a unit of label `"U"` scaled by a magnitude
of label `"M"`.  If `has_exposed_slash` is `true` for a given magnitude
label, then we'll know to make this `"[(M) U]"` instead.

Finally, we move a couple of `StringConstant`-ish utilities into
`"string_constant.hh"`, so that we can use them in our implementation.

Helps #85.
  • Loading branch information
chiphogg committed Nov 27, 2024
1 parent 7791a9a commit 5634200
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 9 deletions.
86 changes: 86 additions & 0 deletions au/code/au/magnitude.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -60,6 +61,14 @@ using MagQuotientT = PackQuotientT<Magnitude, T, U>;
template <typename T>
using MagInverseT = PackInverseT<Magnitude, T>;

// A printable label to indicate the Magnitude for human readers.
template <typename MagT>
struct MagnitudeLabel;

// A sizeof()-compatible API to get the label for a Magnitude.
template <typename MagT>
constexpr const auto &mag_label(MagT = MagT{});

// A helper function to create a Magnitude from an integer constant.
template <std::size_t N>
constexpr auto mag();
Expand Down Expand Up @@ -559,6 +568,83 @@ constexpr T get_value(Magnitude<BPs...> m) {
return result.value;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// `MagnitudeLabel` implementation.

namespace detail {
enum class MagLabelCategory {
INTEGER,
RATIONAL,
UNSUPPORTED,
};

template <typename... BPs>
constexpr MagLabelCategory categorize_mag_label(Magnitude<BPs...> m) {
if (IsInteger<Magnitude<BPs...>>::value) {
return get_value_result<std::uintmax_t>(m).outcome == MagRepresentationOutcome::OK
? MagLabelCategory::INTEGER
: MagLabelCategory::UNSUPPORTED;
}
if (IsRational<Magnitude<BPs...>>::value) {
return MagLabelCategory::RATIONAL;
}
return MagLabelCategory::UNSUPPORTED;
}

template <typename MagT, MagLabelCategory Category>
struct MagnitudeLabelImplementation {
static constexpr const char value[] = "(UNLABELED SCALE FACTOR)";

static constexpr const bool has_exposed_slash = false;
};
template <typename MagT, MagLabelCategory Category>
constexpr const char MagnitudeLabelImplementation<MagT, Category>::value[];
template <typename MagT, MagLabelCategory Category>
constexpr const bool MagnitudeLabelImplementation<MagT, Category>::has_exposed_slash;

template <typename MagT>
struct MagnitudeLabelImplementation<MagT, MagLabelCategory::INTEGER>
: detail::IToA<get_value<std::uintmax_t>(MagT{})> {
static constexpr const bool has_exposed_slash = false;
};
template <typename MagT>
constexpr const bool
MagnitudeLabelImplementation<MagT, MagLabelCategory::INTEGER>::has_exposed_slash;

// Analogous to `detail::ExtendedLabel`, but for magnitudes.
//
// This makes it easier to name the exact type for compound labels.
template <std::size_t ExtensionStrlen, typename... Mags>
using ExtendedMagLabel =
StringConstant<concatenate(MagnitudeLabel<Mags>::value...).size() + ExtensionStrlen>;

template <typename MagT>
struct MagnitudeLabelImplementation<MagT, MagLabelCategory::RATIONAL> {
using LabelT = ExtendedMagLabel<3u, NumeratorT<MagT>, DenominatorT<MagT>>;
static constexpr LabelT value = join_by(
" / ", MagnitudeLabel<NumeratorT<MagT>>::value, MagnitudeLabel<DenominatorT<MagT>>::value);

static constexpr const bool has_exposed_slash = true;
};
template <typename MagT>
constexpr typename MagnitudeLabelImplementation<MagT, MagLabelCategory::RATIONAL>::LabelT
MagnitudeLabelImplementation<MagT, MagLabelCategory::RATIONAL>::value;
template <typename MagT>
constexpr const bool
MagnitudeLabelImplementation<MagT, MagLabelCategory::RATIONAL>::has_exposed_slash;

} // namespace detail

template <typename... BPs>
struct MagnitudeLabel<Magnitude<BPs...>>
: detail::MagnitudeLabelImplementation<Magnitude<BPs...>,
detail::categorize_mag_label(Magnitude<BPs...>{})> {};

template <typename MagT>
constexpr const auto &mag_label(MagT) {
return detail::as_char_array(MagnitudeLabel<MagT>::value);
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// `CommonMagnitude` implementation.

Expand Down
21 changes: 21 additions & 0 deletions au/code/au/magnitude_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ using ::testing::DoubleEq;
using ::testing::Eq;
using ::testing::FloatEq;
using ::testing::StaticAssertTypeEq;
using ::testing::StrEq;

namespace au {
namespace {
Expand Down Expand Up @@ -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<decltype(mag<287'987>())>::has_exposed_slash);
EXPECT_TRUE(MagnitudeLabel<decltype(mag<1>() / 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.
Expand Down
9 changes: 0 additions & 9 deletions au/code/au/unit_of_measure.hh
Original file line number Diff line number Diff line change
Expand Up @@ -707,15 +707,6 @@ struct ComputeCommonPointUnit
// `UnitLabel` implementation.

namespace detail {
template <std::size_t N>
constexpr auto as_char_array(const char (&x)[N]) -> const char (&)[N] {
return x;
}

template <std::size_t N>
constexpr auto as_char_array(const StringConstant<N> &x) -> const char (&)[N + 1] {
return x.char_array();
}

template <typename Unit>
using HasLabel = decltype(Unit::label);
Expand Down
10 changes: 10 additions & 0 deletions au/code/au/utility/string_constant.hh
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,15 @@ constexpr auto parens_if(const StringT &s) {
return concatenate(ParensIf<Enable>::open(), s, ParensIf<Enable>::close());
}

template <std::size_t N>
constexpr auto as_char_array(const char (&x)[N]) -> const char (&)[N] {
return x;
}

template <std::size_t N>
constexpr auto as_char_array(const StringConstant<N> &x) -> const char (&)[N + 1] {
return x.char_array();
}

} // namespace detail
} // namespace au

0 comments on commit 5634200

Please sign in to comment.