| | |
| | |
| |
|
| | |
| | |
| |
|
| | #pragma once |
| |
|
| | #include <cstddef> |
| | #include <cstdint> |
| | #include <exception> |
| | #include <ostream> |
| | #include <type_traits> |
| |
|
| | #include <common/concepts.h> |
| |
|
| | namespace Common { |
| |
|
| | template <size_t I, size_t F> |
| | class FixedPoint; |
| |
|
| | namespace detail { |
| |
|
| | |
| | |
| | |
| | |
| | template <size_t T> |
| | struct type_from_size { |
| | using value_type = void; |
| | using unsigned_type = void; |
| | using signed_type = void; |
| | static constexpr bool is_specialized = false; |
| | }; |
| |
|
| | #if defined(__GNUC__) && defined(__x86_64__) && !defined(__STRICT_ANSI__) |
| | template <> |
| | struct type_from_size<128> { |
| | static constexpr bool is_specialized = true; |
| | static constexpr size_t size = 128; |
| |
|
| | using value_type = __int128; |
| | using unsigned_type = unsigned __int128; |
| | using signed_type = __int128; |
| | using next_size = type_from_size<256>; |
| | }; |
| | #endif |
| |
|
| | template <> |
| | struct type_from_size<64> { |
| | static constexpr bool is_specialized = true; |
| | static constexpr size_t size = 64; |
| |
|
| | using value_type = int64_t; |
| | using unsigned_type = std::make_unsigned_t<value_type>; |
| | using signed_type = std::make_signed_t<value_type>; |
| | using next_size = type_from_size<128>; |
| | }; |
| |
|
| | template <> |
| | struct type_from_size<32> { |
| | static constexpr bool is_specialized = true; |
| | static constexpr size_t size = 32; |
| |
|
| | using value_type = int32_t; |
| | using unsigned_type = std::make_unsigned_t<value_type>; |
| | using signed_type = std::make_signed_t<value_type>; |
| | using next_size = type_from_size<64>; |
| | }; |
| |
|
| | template <> |
| | struct type_from_size<16> { |
| | static constexpr bool is_specialized = true; |
| | static constexpr size_t size = 16; |
| |
|
| | using value_type = int16_t; |
| | using unsigned_type = std::make_unsigned_t<value_type>; |
| | using signed_type = std::make_signed_t<value_type>; |
| | using next_size = type_from_size<32>; |
| | }; |
| |
|
| | template <> |
| | struct type_from_size<8> { |
| | static constexpr bool is_specialized = true; |
| | static constexpr size_t size = 8; |
| |
|
| | using value_type = int8_t; |
| | using unsigned_type = std::make_unsigned_t<value_type>; |
| | using signed_type = std::make_signed_t<value_type>; |
| | using next_size = type_from_size<16>; |
| | }; |
| |
|
| | |
| | |
| | |
| | template <class B, class N> |
| | constexpr B next_to_base(N rhs) { |
| | return static_cast<B>(rhs); |
| | } |
| |
|
| | struct divide_by_zero : std::exception {}; |
| |
|
| | template <size_t I, size_t F> |
| | constexpr FixedPoint<I, F> divide( |
| | FixedPoint<I, F> numerator, FixedPoint<I, F> denominator, FixedPoint<I, F>& remainder, |
| | std::enable_if_t<type_from_size<I + F>::next_size::is_specialized>* = nullptr) { |
| |
|
| | using next_type = typename FixedPoint<I, F>::next_type; |
| | using base_type = typename FixedPoint<I, F>::base_type; |
| | constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits; |
| |
|
| | next_type t(numerator.to_raw()); |
| | t <<= fractional_bits; |
| |
|
| | FixedPoint<I, F> quotient; |
| |
|
| | quotient = FixedPoint<I, F>::from_base(next_to_base<base_type>(t / denominator.to_raw())); |
| | remainder = FixedPoint<I, F>::from_base(next_to_base<base_type>(t % denominator.to_raw())); |
| |
|
| | return quotient; |
| | } |
| |
|
| | template <size_t I, size_t F> |
| | constexpr FixedPoint<I, F> divide( |
| | FixedPoint<I, F> numerator, FixedPoint<I, F> denominator, FixedPoint<I, F>& remainder, |
| | std::enable_if_t<!type_from_size<I + F>::next_size::is_specialized>* = nullptr) { |
| |
|
| | using unsigned_type = typename FixedPoint<I, F>::unsigned_type; |
| |
|
| | constexpr int bits = FixedPoint<I, F>::total_bits; |
| |
|
| | if (denominator == 0) { |
| | throw divide_by_zero(); |
| | } else { |
| |
|
| | int sign = 0; |
| |
|
| | FixedPoint<I, F> quotient; |
| |
|
| | if (numerator < 0) { |
| | sign ^= 1; |
| | numerator = -numerator; |
| | } |
| |
|
| | if (denominator < 0) { |
| | sign ^= 1; |
| | denominator = -denominator; |
| | } |
| |
|
| | unsigned_type n = numerator.to_raw(); |
| | unsigned_type d = denominator.to_raw(); |
| | unsigned_type x = 1; |
| | unsigned_type answer = 0; |
| |
|
| | |
| | while ((n >= d) && (((d >> (bits - 1)) & 1) == 0)) { |
| | x <<= 1; |
| | d <<= 1; |
| | } |
| |
|
| | while (x != 0) { |
| | if (n >= d) { |
| | n -= d; |
| | answer += x; |
| | } |
| |
|
| | x >>= 1; |
| | d >>= 1; |
| | } |
| |
|
| | unsigned_type l1 = n; |
| | unsigned_type l2 = denominator.to_raw(); |
| |
|
| | |
| | while (l1 >> (bits - F) > 0) { |
| | l1 >>= 1; |
| | l2 >>= 1; |
| | } |
| | const unsigned_type lo = (l1 << F) / l2; |
| |
|
| | quotient = FixedPoint<I, F>::from_base((answer << F) | lo); |
| | remainder = n; |
| |
|
| | if (sign) { |
| | quotient = -quotient; |
| | } |
| |
|
| | return quotient; |
| | } |
| | } |
| |
|
| | |
| | template <size_t I, size_t F> |
| | constexpr FixedPoint<I, F> multiply( |
| | FixedPoint<I, F> lhs, FixedPoint<I, F> rhs, |
| | std::enable_if_t<type_from_size<I + F>::next_size::is_specialized>* = nullptr) { |
| |
|
| | using next_type = typename FixedPoint<I, F>::next_type; |
| | using base_type = typename FixedPoint<I, F>::base_type; |
| |
|
| | constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits; |
| |
|
| | next_type t(static_cast<next_type>(lhs.to_raw()) * static_cast<next_type>(rhs.to_raw())); |
| | t >>= fractional_bits; |
| |
|
| | return FixedPoint<I, F>::from_base(next_to_base<base_type>(t)); |
| | } |
| |
|
| | |
| | |
| | |
| | template <size_t I, size_t F> |
| | constexpr FixedPoint<I, F> multiply( |
| | FixedPoint<I, F> lhs, FixedPoint<I, F> rhs, |
| | std::enable_if_t<!type_from_size<I + F>::next_size::is_specialized>* = nullptr) { |
| |
|
| | using base_type = typename FixedPoint<I, F>::base_type; |
| |
|
| | constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits; |
| | constexpr base_type integer_mask = FixedPoint<I, F>::integer_mask; |
| | constexpr base_type fractional_mask = FixedPoint<I, F>::fractional_mask; |
| |
|
| | |
| | const base_type a_hi = (lhs.to_raw() & integer_mask) >> fractional_bits; |
| | const base_type b_hi = (rhs.to_raw() & integer_mask) >> fractional_bits; |
| | const base_type a_lo = (lhs.to_raw() & fractional_mask); |
| | const base_type b_lo = (rhs.to_raw() & fractional_mask); |
| |
|
| | const base_type x1 = a_hi * b_hi; |
| | const base_type x2 = a_hi * b_lo; |
| | const base_type x3 = a_lo * b_hi; |
| | const base_type x4 = a_lo * b_lo; |
| |
|
| | return FixedPoint<I, F>::from_base((x1 << fractional_bits) + (x3 + x2) + |
| | (x4 >> fractional_bits)); |
| | } |
| | } |
| |
|
| | template <size_t I, size_t F> |
| | class FixedPoint { |
| | static_assert(detail::type_from_size<I + F>::is_specialized, "invalid combination of sizes"); |
| |
|
| | public: |
| | static constexpr size_t fractional_bits = F; |
| | static constexpr size_t integer_bits = I; |
| | static constexpr size_t total_bits = I + F; |
| |
|
| | using base_type_info = detail::type_from_size<total_bits>; |
| |
|
| | using base_type = typename base_type_info::value_type; |
| | using next_type = typename base_type_info::next_size::value_type; |
| | using unsigned_type = typename base_type_info::unsigned_type; |
| |
|
| | public: |
| | #ifdef __GNUC__ |
| | #pragma GCC diagnostic push |
| | #pragma GCC diagnostic ignored "-Woverflow" |
| | #endif |
| | static constexpr base_type fractional_mask = |
| | ~(static_cast<unsigned_type>(~base_type(0)) << fractional_bits); |
| | static constexpr base_type integer_mask = ~fractional_mask; |
| | #ifdef __GNUC__ |
| | #pragma GCC diagnostic pop |
| | #endif |
| |
|
| | public: |
| | static constexpr base_type one = base_type(1) << fractional_bits; |
| |
|
| | public: |
| | constexpr FixedPoint() = default; |
| |
|
| | constexpr FixedPoint(const FixedPoint&) = default; |
| | constexpr FixedPoint& operator=(const FixedPoint&) = default; |
| |
|
| | constexpr FixedPoint(FixedPoint&&) noexcept = default; |
| | constexpr FixedPoint& operator=(FixedPoint&&) noexcept = default; |
| |
|
| | template <IsArithmetic Number> |
| | constexpr FixedPoint(Number n) : data_(static_cast<base_type>(n * one)) {} |
| |
|
| | public: |
| | template <size_t I2, size_t F2> |
| | constexpr explicit FixedPoint(FixedPoint<I2, F2> other) { |
| | static_assert(I2 <= I && F2 <= F, "Scaling conversion can only upgrade types"); |
| | using T = FixedPoint<I2, F2>; |
| |
|
| | const base_type fractional = (other.data_ & T::fractional_mask); |
| | const base_type integer = (other.data_ & T::integer_mask) >> T::fractional_bits; |
| | data_ = |
| | (integer << fractional_bits) | (fractional << (fractional_bits - T::fractional_bits)); |
| | } |
| |
|
| | private: |
| | |
| | |
| | |
| | struct NoScale {}; |
| |
|
| | constexpr FixedPoint(base_type n, const NoScale&) : data_(n) {} |
| |
|
| | public: |
| | static constexpr FixedPoint from_base(base_type n) { |
| | return FixedPoint(n, NoScale()); |
| | } |
| |
|
| | public: |
| | friend constexpr auto operator<=>(FixedPoint lhs, FixedPoint rhs) = default; |
| |
|
| | public: |
| | [[nodiscard]] constexpr bool operator!() const { |
| | return !data_; |
| | } |
| |
|
| | [[nodiscard]] constexpr FixedPoint operator~() const { |
| | |
| | |
| | |
| | |
| | return FixedPoint::from_base(~data_); |
| | } |
| |
|
| | [[nodiscard]] constexpr FixedPoint operator-() const { |
| | return FixedPoint::from_base(-data_); |
| | } |
| |
|
| | [[nodiscard]] constexpr FixedPoint operator+() const { |
| | return FixedPoint::from_base(+data_); |
| | } |
| |
|
| | constexpr FixedPoint& operator++() { |
| | data_ += one; |
| | return *this; |
| | } |
| |
|
| | constexpr FixedPoint& operator--() { |
| | data_ -= one; |
| | return *this; |
| | } |
| |
|
| | constexpr FixedPoint operator++(int) { |
| | FixedPoint tmp(*this); |
| | data_ += one; |
| | return tmp; |
| | } |
| |
|
| | constexpr FixedPoint operator--(int) { |
| | FixedPoint tmp(*this); |
| | data_ -= one; |
| | return tmp; |
| | } |
| |
|
| | public: |
| | constexpr FixedPoint& operator+=(FixedPoint n) { |
| | data_ += n.data_; |
| | return *this; |
| | } |
| |
|
| | constexpr FixedPoint& operator-=(FixedPoint n) { |
| | data_ -= n.data_; |
| | return *this; |
| | } |
| |
|
| | constexpr FixedPoint& operator*=(FixedPoint n) { |
| | return assign(detail::multiply(*this, n)); |
| | } |
| |
|
| | constexpr FixedPoint& operator/=(FixedPoint n) { |
| | FixedPoint temp; |
| | return assign(detail::divide(*this, n, temp)); |
| | } |
| |
|
| | private: |
| | constexpr FixedPoint& assign(FixedPoint rhs) { |
| | data_ = rhs.data_; |
| | return *this; |
| | } |
| |
|
| | public: |
| | |
| | constexpr FixedPoint& operator&=(FixedPoint n) { |
| | data_ &= n.data_; |
| | return *this; |
| | } |
| |
|
| | constexpr FixedPoint& operator|=(FixedPoint n) { |
| | data_ |= n.data_; |
| | return *this; |
| | } |
| |
|
| | constexpr FixedPoint& operator^=(FixedPoint n) { |
| | data_ ^= n.data_; |
| | return *this; |
| | } |
| |
|
| | template <IsIntegral Integer> |
| | constexpr FixedPoint& operator>>=(Integer n) { |
| | data_ >>= n; |
| | return *this; |
| | } |
| |
|
| | template <IsIntegral Integer> |
| | constexpr FixedPoint& operator<<=(Integer n) { |
| | data_ <<= n; |
| | return *this; |
| | } |
| |
|
| | public: |
| | constexpr void round_up() { |
| | data_ += (data_ & fractional_mask) >> 1; |
| | } |
| |
|
| | [[nodiscard]] constexpr int to_int() { |
| | round_up(); |
| | return static_cast<int>((data_ & integer_mask) >> fractional_bits); |
| | } |
| |
|
| | [[nodiscard]] constexpr unsigned int to_uint() { |
| | round_up(); |
| | return static_cast<unsigned int>((data_ & integer_mask) >> fractional_bits); |
| | } |
| |
|
| | [[nodiscard]] constexpr int64_t to_long() { |
| | round_up(); |
| | return static_cast<int64_t>((data_ & integer_mask) >> fractional_bits); |
| | } |
| |
|
| | [[nodiscard]] constexpr int to_int_floor() const { |
| | return static_cast<int>((data_ & integer_mask) >> fractional_bits); |
| | } |
| |
|
| | [[nodiscard]] constexpr int64_t to_long_floor() const { |
| | return static_cast<int64_t>((data_ & integer_mask) >> fractional_bits); |
| | } |
| |
|
| | [[nodiscard]] constexpr unsigned int to_uint_floor() const { |
| | return static_cast<unsigned int>((data_ & integer_mask) >> fractional_bits); |
| | } |
| |
|
| | [[nodiscard]] constexpr float to_float() const { |
| | return static_cast<float>(data_) / FixedPoint::one; |
| | } |
| |
|
| | [[nodiscard]] constexpr double to_double() const { |
| | return static_cast<double>(data_) / FixedPoint::one; |
| | } |
| |
|
| | [[nodiscard]] constexpr base_type to_raw() const { |
| | return data_; |
| | } |
| |
|
| | constexpr void clear_int() { |
| | data_ &= fractional_mask; |
| | } |
| |
|
| | [[nodiscard]] constexpr base_type get_frac() const { |
| | return data_ & fractional_mask; |
| | } |
| |
|
| | public: |
| | constexpr void swap(FixedPoint& rhs) noexcept { |
| | using std::swap; |
| | swap(data_, rhs.data_); |
| | } |
| |
|
| | public: |
| | base_type data_{}; |
| | }; |
| |
|
| | |
| | |
| | template <size_t I1, size_t I2, size_t F> |
| | constexpr std::conditional_t<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>> operator+( |
| | FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) { |
| |
|
| | using T = std::conditional_t<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>; |
| |
|
| | const T l = T::from_base(lhs.to_raw()); |
| | const T r = T::from_base(rhs.to_raw()); |
| | return l + r; |
| | } |
| |
|
| | template <size_t I1, size_t I2, size_t F> |
| | constexpr std::conditional_t<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>> operator-( |
| | FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) { |
| |
|
| | using T = std::conditional_t<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>; |
| |
|
| | const T l = T::from_base(lhs.to_raw()); |
| | const T r = T::from_base(rhs.to_raw()); |
| | return l - r; |
| | } |
| |
|
| | template <size_t I1, size_t I2, size_t F> |
| | constexpr std::conditional_t<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>> operator*( |
| | FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) { |
| |
|
| | using T = std::conditional_t<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>; |
| |
|
| | const T l = T::from_base(lhs.to_raw()); |
| | const T r = T::from_base(rhs.to_raw()); |
| | return l * r; |
| | } |
| |
|
| | template <size_t I1, size_t I2, size_t F> |
| | constexpr std::conditional_t<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>> operator/( |
| | FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) { |
| |
|
| | using T = std::conditional_t<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>; |
| |
|
| | const T l = T::from_base(lhs.to_raw()); |
| | const T r = T::from_base(rhs.to_raw()); |
| | return l / r; |
| | } |
| |
|
| | template <size_t I, size_t F> |
| | std::ostream& operator<<(std::ostream& os, FixedPoint<I, F> f) { |
| | os << f.to_double(); |
| | return os; |
| | } |
| |
|
| | |
| | template <size_t I, size_t F> |
| | constexpr FixedPoint<I, F> operator+(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) { |
| | lhs += rhs; |
| | return lhs; |
| | } |
| | template <size_t I, size_t F> |
| | constexpr FixedPoint<I, F> operator-(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) { |
| | lhs -= rhs; |
| | return lhs; |
| | } |
| | template <size_t I, size_t F> |
| | constexpr FixedPoint<I, F> operator*(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) { |
| | lhs *= rhs; |
| | return lhs; |
| | } |
| | template <size_t I, size_t F> |
| | constexpr FixedPoint<I, F> operator/(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) { |
| | lhs /= rhs; |
| | return lhs; |
| | } |
| |
|
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr FixedPoint<I, F> operator+(FixedPoint<I, F> lhs, Number rhs) { |
| | lhs += FixedPoint<I, F>(rhs); |
| | return lhs; |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr FixedPoint<I, F> operator-(FixedPoint<I, F> lhs, Number rhs) { |
| | lhs -= FixedPoint<I, F>(rhs); |
| | return lhs; |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr FixedPoint<I, F> operator*(FixedPoint<I, F> lhs, Number rhs) { |
| | lhs *= FixedPoint<I, F>(rhs); |
| | return lhs; |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr FixedPoint<I, F> operator/(FixedPoint<I, F> lhs, Number rhs) { |
| | lhs /= FixedPoint<I, F>(rhs); |
| | return lhs; |
| | } |
| |
|
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr FixedPoint<I, F> operator+(Number lhs, FixedPoint<I, F> rhs) { |
| | FixedPoint<I, F> tmp(lhs); |
| | tmp += rhs; |
| | return tmp; |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr FixedPoint<I, F> operator-(Number lhs, FixedPoint<I, F> rhs) { |
| | FixedPoint<I, F> tmp(lhs); |
| | tmp -= rhs; |
| | return tmp; |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr FixedPoint<I, F> operator*(Number lhs, FixedPoint<I, F> rhs) { |
| | FixedPoint<I, F> tmp(lhs); |
| | tmp *= rhs; |
| | return tmp; |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr FixedPoint<I, F> operator/(Number lhs, FixedPoint<I, F> rhs) { |
| | FixedPoint<I, F> tmp(lhs); |
| | tmp /= rhs; |
| | return tmp; |
| | } |
| |
|
| | |
| | template <size_t I, size_t F, IsIntegral Integer> |
| | constexpr FixedPoint<I, F> operator<<(FixedPoint<I, F> lhs, Integer rhs) { |
| | lhs <<= rhs; |
| | return lhs; |
| | } |
| | template <size_t I, size_t F, IsIntegral Integer> |
| | constexpr FixedPoint<I, F> operator>>(FixedPoint<I, F> lhs, Integer rhs) { |
| | lhs >>= rhs; |
| | return lhs; |
| | } |
| |
|
| | |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr bool operator>(FixedPoint<I, F> lhs, Number rhs) { |
| | return lhs > FixedPoint<I, F>(rhs); |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr bool operator<(FixedPoint<I, F> lhs, Number rhs) { |
| | return lhs < FixedPoint<I, F>(rhs); |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr bool operator>=(FixedPoint<I, F> lhs, Number rhs) { |
| | return lhs >= FixedPoint<I, F>(rhs); |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr bool operator<=(FixedPoint<I, F> lhs, Number rhs) { |
| | return lhs <= FixedPoint<I, F>(rhs); |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr bool operator==(FixedPoint<I, F> lhs, Number rhs) { |
| | return lhs == FixedPoint<I, F>(rhs); |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr bool operator!=(FixedPoint<I, F> lhs, Number rhs) { |
| | return lhs != FixedPoint<I, F>(rhs); |
| | } |
| |
|
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr bool operator>(Number lhs, FixedPoint<I, F> rhs) { |
| | return FixedPoint<I, F>(lhs) > rhs; |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr bool operator<(Number lhs, FixedPoint<I, F> rhs) { |
| | return FixedPoint<I, F>(lhs) < rhs; |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr bool operator>=(Number lhs, FixedPoint<I, F> rhs) { |
| | return FixedPoint<I, F>(lhs) >= rhs; |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr bool operator<=(Number lhs, FixedPoint<I, F> rhs) { |
| | return FixedPoint<I, F>(lhs) <= rhs; |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr bool operator==(Number lhs, FixedPoint<I, F> rhs) { |
| | return FixedPoint<I, F>(lhs) == rhs; |
| | } |
| | template <size_t I, size_t F, IsArithmetic Number> |
| | constexpr bool operator!=(Number lhs, FixedPoint<I, F> rhs) { |
| | return FixedPoint<I, F>(lhs) != rhs; |
| | } |
| |
|
| | } |
| |
|