Skip to content

Commit

Permalink
Prevent integer division with raw numerics (#142)
Browse files Browse the repository at this point in the history
This closes a loophole and makes the library more consistent.

Fixes #39.
  • Loading branch information
chiphogg authored Jun 27, 2023
1 parent 2e72f81 commit efeb15f
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 6 deletions.
31 changes: 26 additions & 5 deletions au/quantity.hh
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ class Quantity {
}
template <typename T, typename = std::enable_if_t<std::is_arithmetic<T>::value>>
friend constexpr auto operator/(T s, Quantity a) {
warn_if_integer_division<T>();
return make_quantity<decltype(pow<-1>(unit))>(s / a.value_);
}

Expand All @@ -313,11 +314,7 @@ class Quantity {
// Division for dimensioned quantities.
template <typename OtherUnit, typename OtherRep>
constexpr auto operator/(Quantity<OtherUnit, OtherRep> q) const {
constexpr bool uses_integer_division =
(std::is_integral<Rep>::value && std::is_integral<OtherRep>::value);
static_assert(!uses_integer_division,
"Integer division forbidden: use integer_quotient() if you really want it");

warn_if_integer_division<OtherRep>();
return make_quantity_unless_unitless<UnitQuotientT<Unit, OtherUnit>>(value_ /
q.in(OtherUnit{}));
}
Expand Down Expand Up @@ -380,6 +377,14 @@ class Quantity {
}

private:
template <typename OtherRep>
static constexpr void warn_if_integer_division() {
constexpr bool uses_integer_division =
(std::is_integral<Rep>::value && std::is_integral<OtherRep>::value);
static_assert(!uses_integer_division,
"Integer division forbidden: use integer_quotient() if you really want it");
}

constexpr Quantity(Rep value) : value_{value} {}

Rep value_{0};
Expand All @@ -393,6 +398,22 @@ constexpr auto integer_quotient(Quantity<U1, R1> q1, Quantity<U2, R2> q2) {
return make_quantity<UnitQuotientT<U1, U2>>(q1.in(U1{}) / q2.in(U2{}));
}

// Force integer division beteween an integer Quantity and a raw number.
template <typename U, typename R, typename T>
constexpr auto integer_quotient(Quantity<U, R> q, T x) {
static_assert(std::is_integral<R>::value && std::is_integral<T>::value,
"integer_quotient() can only be called with integral Rep");
return make_quantity<U>(q.in(U{}) / x);
}

// Force integer division beteween a raw number and an integer Quantity.
template <typename T, typename U, typename R>
constexpr auto integer_quotient(T x, Quantity<U, R> q) {
static_assert(std::is_integral<T>::value && std::is_integral<R>::value,
"integer_quotient() can only be called with integral Rep");
return make_quantity<UnitInverseT<U>>(x / q.in(U{}));
}

// The modulo operator (i.e., the remainder of an integer division).
//
// Only defined whenever (R1{} % R2{}) is defined (i.e., for integral Reps), _and_
Expand Down
13 changes: 12 additions & 1 deletion au/quantity_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,12 @@ TEST(Quantity, ProductOfInvertingUnitsIsScalar) {
TEST(Quantity, ScalarDivisionWorks) {
constexpr auto x = feet(10);
EXPECT_EQ(x / 2, feet(5));
EXPECT_EQ(pow<-1>(feet)(2), 20 / x);
EXPECT_EQ(20. / x, inverse(feet)(2.));
}

TEST(Quantity, ScalarDivisionIsConstexprCompatible) {
constexpr auto quotient = feet(10.) / 2;
EXPECT_EQ(quotient, feet(5.));
}

TEST(Quantity, ShortHandAdditionAssignmentWorks) {
Expand Down Expand Up @@ -684,6 +689,12 @@ TEST(AreQuantityTypesEquivalent, RequiresSameRepAndEquivalentUnits) {
TEST(integer_quotient, EnablesIntegerDivision) {
constexpr auto dt = integer_quotient(meters(60), (miles / hour)(65));
EXPECT_THAT(dt, QuantityEquivalent((hour * meters / mile)(0)));

constexpr auto x = integer_quotient(meters(60), 31);
EXPECT_THAT(x, SameTypeAndValue(meters(1)));

constexpr auto freq = integer_quotient(1000, minutes(300));
EXPECT_THAT(freq, SameTypeAndValue(inverse(minutes)(3)));
}

TEST(mod, ComputesRemainderForSameUnits) {
Expand Down

0 comments on commit efeb15f

Please sign in to comment.