Skip to content

Commit

Permalink
code(numbers): Created the 'Number' concept to determine whenever a t…
Browse files Browse the repository at this point in the history
…ype satisfies the criteria to be a Number

code(numbers): implemented the basic operations for Rationals
code(numbers): Implemented the implementation of the equality operators for Naturals, Integers and Rationals
code(numbers): implemented some operator overloads for mix arithmetic operations between types
code(tsuite-assertions): cleaned the assertions module, and refactored the way of how are assertions handled internally
code(zero::concepts): Created a set of concepts to constrain types that are printable
  • Loading branch information
TheRustifyer committed Nov 19, 2023
1 parent 569e987 commit 683b461
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 68 deletions.
16 changes: 16 additions & 0 deletions zero/ifc/commons/concepts.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ import type_traits;

export namespace zero::concepts {

/// \brief Constrain for types that has an operator<< overload
template <typename T>
concept Ostreamable = requires(const T& t, std::ostream& os) {
{ os << t } -> std::same_as<std::ostream&>;
};

/// \brief Constrain for types that has an std::to_string implementation
template <typename T>
concept StringConvertible = requires(const T& t) {
{ std::to_string(t) } -> std::same_as<std::string>;
};

/// \brief Constrain for types with either operator<< or std::to_string
template <typename T>
concept Printable = Ostreamable<T> || StringConvertible<T>;

/**
* @brief checks if the type is a type with a push_back
* member method implemented
Expand Down
8 changes: 4 additions & 4 deletions zero/ifc/iterators/internal/iterator_detail.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ namespace iterator::__detail {
struct arrow_proxy {
Reference r;

arrow_proxy(Reference&& value) : r(std::move(value)) {}
explicit arrow_proxy(Reference&& value) : r(std::move(value)) {}
Reference* operator->() const { return &r; }
};

/**
* @brief The iterator declares itself a single pass iterator.
* input or output iterators must meet this requirement
* input or output iterators must meet this requirement
*/
template <typename T>
concept is_single_pass = bool(T::single_pass_iterator);
Expand Down Expand Up @@ -62,7 +62,7 @@ namespace iterator::__detail {
template <typename T>
using infer_difference_type_t = typename infer_difference_type<T>::type;

/// Partial template speciallizations to allow us to deduce the
/// Partial template specializations to allow us to deduce the
/// iterator mandatory `value:type`
template <typename T>
requires requires { typename T::value_type; }
Expand Down Expand Up @@ -102,7 +102,7 @@ namespace iterator::__detail {

/// Helper concept to declare a later-deduced parameter type,
/// but that type is still constrained to be a type that we
///don’t yet know
/// don’t yet know
template <typename Arg, typename Iter>
concept difference_type_arg =
std::convertible_to<Arg, infer_difference_type_t<Iter>>;
Expand Down
2 changes: 1 addition & 1 deletion zero/ifc/iterators/iterator.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

export module iterator;

export import :concepts;
export import :concepts; ///< Iterator concepts

export import :input_iterator;
export import :iterator_facade;
Expand Down
9 changes: 4 additions & 5 deletions zero/ifc/iterators/legacy/legacy_output_iterator.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export namespace zero::iterator::legacy
struct output_iter_proxy {
output_iter<Z> &_iter;

constexpr output_iter_proxy(output_iter<Z> &iter) noexcept : _iter(iter) {}
constexpr explicit output_iter_proxy(output_iter<Z> &iter) noexcept : _iter(iter) {}

template <typename U>
constexpr auto operator=(const U &val) -> output_iter_proxy& {
Expand All @@ -49,8 +49,8 @@ export namespace zero::iterator::legacy
};

public:
constexpr output_iter<T>() noexcept = default;
constexpr output_iter(T &elem) noexcept : _elem(&elem) {}
constexpr output_iter<T>() noexcept = default; // Left defaulted and not deleted because the legacy implementation
constexpr explicit output_iter(T& elem) noexcept : _elem(&elem) {}

constexpr output_iter<T>(const output_iter<T> &other) = default;
constexpr output_iter<T>(output_iter<T> &&other) noexcept = default;
Expand Down Expand Up @@ -88,8 +88,7 @@ export namespace zero::iterator::legacy

[[nodiscard]]
constexpr friend auto operator!=(const output_iter& lhs, const output_iter& rhs) noexcept -> bool {
return not (lhs == rhs);
return lhs != rhs;
}
};

}
75 changes: 71 additions & 4 deletions zero/ifc/math/numbers.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ export namespace zero::math {
[[nodiscard]] inline constexpr Natural operator-(Natural rhs) const noexcept;
[[nodiscard]] inline constexpr Natural operator*(Natural rhs) const noexcept;
[[nodiscard]] inline constexpr Rational operator/(Natural rhs) const noexcept;
// Comparison operator overloads
[[nodiscard]] inline constexpr bool operator==(Natural rhs) const noexcept;
[[nodiscard]] inline constexpr bool operator==(unsigned int rhs) const noexcept;
// Printable
inline constexpr friend std::ostream& operator<<(std::ostream& os, const Natural& rhs) {
os << rhs._number;
return os;
}
};

/// A whole real number
Expand All @@ -70,10 +78,17 @@ export namespace zero::math {
[[nodiscard]] inline constexpr Integer operator*(Integer rhs) const noexcept;
[[nodiscard]] inline constexpr Rational operator*(Rational rhs) const noexcept;
[[nodiscard]] inline constexpr Rational operator/(Integer rhs) const noexcept;
// Comparison operator overloads
[[nodiscard]] inline constexpr bool operator==(Integer rhs) const noexcept;
[[nodiscard]] inline constexpr bool operator==(int rhs) const noexcept;

// Explicit conversion operators
[[nodiscard]] inline explicit operator int() const { return _number; }
// Printable
inline constexpr friend std::ostream& operator<<(std::ostream& os, const Integer& rhs) {
os << rhs._number;
return os;
}
};

/// @brief A type that represents rational numbers of the form: ℚ = {a, b ∈ ℤ, b ≠ 0}
Expand All @@ -99,6 +114,8 @@ export namespace zero::math {
Integer _numerator; ///< The numerator of the rational number, belonging to ℤ.
Integer _denominator; ///< The denominator of the rational number, belonging to ℤ, NOT excluding the zero.
public:
static constexpr MathSymbol symbol = MathSymbol::Rationals;

[[nodiscard]] constexpr Rational(int numerator, int denominator) noexcept
: _numerator(numerator), _denominator(denominator) {}
[[nodiscard]] constexpr Rational(Natural numerator, Natural denominator) noexcept
Expand All @@ -113,6 +130,16 @@ export namespace zero::math {

// Arithmetic operator overloads
[[nodiscard]] inline constexpr Rational operator+(Rational rhs) const;
// TODO complete arithmetic overloads
// Comparison operator overloads
[[nodiscard]] inline constexpr bool operator==(Rational rhs) const noexcept;
// Printable
inline constexpr friend std::ostream& operator<<(std::ostream& os, const Rational& rhs) {
os << rhs._numerator;
os << 0x2044;
os << rhs._denominator;
return os;
}
};


Expand All @@ -132,6 +159,7 @@ using namespace zero::math;

/*++++++++ Operator overloads implementations ++++++++++*/
/*+++++++++++++++++ Naturals +++++++++++++++++*/
// Arithmetic
[[nodiscard]] inline constexpr Natural Natural::operator+(const Natural rhs) const noexcept {
return Natural(_number + rhs.number());
}
Expand All @@ -146,8 +174,16 @@ using namespace zero::math;
[[nodiscard]] inline constexpr Rational Natural::operator/(const Natural rhs) const noexcept {
return {static_cast<signed int>(_number), static_cast<signed int>(rhs.number())};
}
// Equality
[[nodiscard]] inline constexpr bool Natural::operator==(const Natural rhs) const noexcept {
return _number == rhs.number();
}
[[nodiscard]] inline constexpr bool Natural::operator==(const unsigned int rhs) const noexcept {
return _number == rhs;
}

/*+++++++++++++++++ Integers +++++++++++++++++*/
/*+++++++++++++++++ Integers +++++++++++++++++*/
// Arithmetic
[[nodiscard]] inline constexpr Integer Integer::operator+(const Integer rhs) const noexcept {
return Integer(_number + rhs.number());
}
Expand All @@ -163,11 +199,17 @@ using namespace zero::math;
[[nodiscard]] inline constexpr Rational Integer::operator/(const Integer rhs) const noexcept {
return {static_cast<signed int>(_number), static_cast<signed int>(rhs.number())};
}
// Equality
[[nodiscard]] inline constexpr bool Integer::operator==(const Integer rhs) const noexcept {
return _number == rhs.number();
}
[[nodiscard]] inline constexpr bool Integer::operator==(const int rhs) const noexcept {
return _number == rhs;
}

/*+++++++++++++++++ Rationals +++++++++++++++++*/
// Arithmetic

/// Adds the current rational number to another rational number.
/// @param rhs The rational number to be added.
/// @return The sum of the two rational numbers.
Expand All @@ -177,17 +219,18 @@ using namespace zero::math;
/// finds the least common multiple (LCM) of the denominators and scales the
/// numerators to have the LCM as the common denominator before adding.
[[nodiscard]] inline constexpr Rational Rational::operator+(const Rational rhs) const {
if (_denominator == rhs.denominator())
if (_denominator == rhs.denominator()) // Like fractions
return {
static_cast<int>(_numerator) + static_cast<int>(rhs.numerator()),
static_cast<int>(_denominator)
}; // Like fractions
else {
};
else { // Unlike fractions
const int lhs_numerator = static_cast<int>(_numerator);
const int rhs_numerator = static_cast<int>(rhs._numerator);
const int lhs_denominator = static_cast<int>(_denominator);
const int rhs_denominator = static_cast<int>(rhs._denominator);

// Get their lcd by finding their lcm
const auto lcd = zero::math::lcm(_denominator.number(), rhs.denominator().number());

// Scale numerators to have the common denominator (lcm)
Expand All @@ -196,3 +239,27 @@ using namespace zero::math;
return {numerator, lcd};
}
}

// Equality

// TODO should we check that 4/2 is the same as 2/1 right? Or we should maintain the difference and explictly
// say that 4/2 aren't the same Rational number as 2/1?
[[nodiscard]] inline constexpr bool Rational::operator==(const Rational rhs) const noexcept {
return _numerator == rhs.numerator() && _denominator == rhs.denominator();
}

/*+++++++++++++++++ oss operator overloads (oss) +++++++++++++++++*/
//inline constexpr std::ostream& Natural::operator<<(std::ostream& os) {
// os << _number;
// return os;
//}
//inline constexpr std::ostream& Integer::operator<<(std::ostream& os) {
// os << _number;
// return os;
//}
//inline constexpr std::ostream& Rational::operator<<(std::ostream& os) {
// os << _numerator;
// os << 0x2044;
// os << _denominator;
// return os;
//}
130 changes: 91 additions & 39 deletions zero/ifc/test-suite/assertions.cppm
Original file line number Diff line number Diff line change
@@ -1,57 +1,109 @@
export module tsuite:assertions;

import std;
import concepts;

using namespace zero::concepts;

template <Printable T, Printable U>
constexpr inline void throw_on_failed_test(const T& expected, const U& actual);

export {
/**
* Compares two values. Generates a test failed if values are non-equal.
*/
template<typename T>
requires (!std::is_pointer_v<T>)
void assertEquals(const T& expected, const T& actual) {
if (expected != actual)
throw std::runtime_error("Assertion failed: expected = " + std::to_string(expected) +
", actual = " + std::to_string(actual));
/// @brief Compares two values. Generates a test failed if values are non-equal.
/// @param actual the given or computed value
/// @param expected the given expectation for the computed value
template<typename T, typename U>
requires (!std::is_pointer_v<T> && !std::is_pointer_v<U>)
constexpr void assertEquals(const T& expected, const U& actual) {
::throw_on_failed_test(expected, actual);
}

/**
* Compares two values being T pointer types.
* Generates a test failed if the values after dereference
* the pointers are non-equal.
*/
/// @brief Compares two values being T pointer types.
/// Generates a test failed if the values after dereference the pointers are non-equal.
template<typename T>
void assertEquals(const T* expected_ptr, const T* actual_ptr) {
auto expected = *expected_ptr;
auto actual = *actual_ptr;
constexpr void assertEquals(const T* expected_ptr, const T* actual_ptr) {
const auto expected = *expected_ptr;
const auto actual = *actual_ptr;

if (expected != actual)
throw std::runtime_error("Assertion failed: expected = " + std::to_string(expected) +
", actual = " + std::to_string(actual));
::throw_on_failed_test(expected, actual);
}

/**
* Compares two values. Generates a test failed if the values are equals.
*/

/// @brief Compares two values. Generates a test failed if the values are equals.
template<typename T>
requires (!std::is_pointer_v<T>)
void assertNotEquals(const T& expected, const T& actual) {
if (expected == actual)
throw std::runtime_error("Assertion failed: expected = " + std::to_string(expected) +
", actual = " + std::to_string(actual));
constexpr void assertNotEquals(const T& expected, const T& actual) {
::throw_on_failed_test(expected, actual);
}

/**
* Compares two values being T pointer types.
* Generates a test failed if the values after dereference
* the pointers are non-equal.
*/

/// @brief Compares two values being T pointer types.
/// Generates a test failed if the values after dereference
/// the pointers are non-equal.
template<typename T>
void assertNotEquals(const T* expected_ptr, const T* actual_ptr) {
auto expected = *expected_ptr;
auto actual = *actual_ptr;
constexpr void assertNotEquals(const T* expected_ptr, const T* actual_ptr) {
const auto expected = *expected_ptr;
const auto actual = *actual_ptr;

::throw_on_failed_test(expected, actual);
}
}

// Detail -- TODO move to an impl module partition

/// \brief custom internal exception for the assertions failure
class assertion_failed : public std::exception {
private:
std::string message_;

if (expected == actual)
throw std::runtime_error("Assertion failed: expected = " + std::to_string(expected) +
", actual = " + std::to_string(actual));
public:
explicit assertion_failed(const std::string& message)
: message_(message) {}

[[nodiscard]] const char* what() const noexcept override {
return message_.c_str();
}
};

/// \brief helper to reduce cognitive complexity,
/// \enabled when the concept {\link @StringConvertible} is satisfied
template<typename T, typename U>
constexpr inline void throw_on_failed_test_str_impl(const T& expected, const U& actual) {
if (expected != actual) {
const auto expected_str = std::to_string(expected);
const auto actual_str = std::to_string(actual);

std::string msg {};
msg.reserve(29 + expected_str.size() + actual_str.size() + 11);

msg += "Assertion failed: expected = "; // 29
msg += expected_str;
msg += ", actual = "; // 11
msg += actual_str;

throw assertion_failed(msg);
}
}
}

/// \brief helper to reduce cognitive complexity,
/// \enabled when the concept {\link @Ostreamable} is satisfied
template<typename T, typename U>
constexpr inline void throw_on_failed_test_oss_impl(const T& expected, const U& actual) {
if (expected != actual) {
std::ostringstream oss;
oss << "Assertion failed: expected = ";
oss << expected;
oss << ", actual = ";
oss << actual;

throw assertion_failed(oss.str()); // TODO use a better one
}
}

template <Printable T, Printable U>
constexpr inline void throw_on_failed_test(const T& expected, const U& actual) {
if constexpr (StringConvertible<T> && StringConvertible<U>)
throw_on_failed_test_str_impl(expected, actual);
else
throw_on_failed_test_oss_impl(expected, actual);
}
Loading

0 comments on commit 683b461

Please sign in to comment.