Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add labels for integer and rational magnitudes #331

Merged
merged 1 commit into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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