/// @file grotto/fixedpoint.hpp /// @brief /// @details /// @author Ryan Henry /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. #ifndef LIBDPF_INCLUDE_DPF_FIXEDPOINT_HPP__ #define LIBDPF_INCLUDE_DPF_FIXEDPOINT_HPP__ #include "hedley/hedley.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dpf/utils.hpp" #include "dpf/leaf_arithmetic.hpp" #include "dpf/uint256_t.hpp" #define GROTTO_FIXED_DEFAULT_INTEGRAL_REPRESENTATION psnip_uint64_t namespace grotto { namespace detail { /// @brief Integer value of an already-rounded finite double, as a 256-bit word. /// Values that do not fit saturate to all-ones. inline uint256_t uint256_from_rounded_double(double rounded) noexcept { if (!(rounded > 0.0) || !std::isfinite(rounded)) { return uint256_t{0}; } int exp = 0; const double frac = std::frexp(rounded, &exp); if (exp <= 0) { return uint256_t{0}; } if (exp > 256) { return ~uint256_t{0}; } constexpr int mant_bits = 53; const auto mant = static_cast(std::ldexp(frac, mant_bits)); const int place = exp - mant_bits; simde_uint128 chunk = mant; int bit = place; if (bit < 0) { chunk >>= static_cast(-bit); bit = 0; } uint128_t lower{0}; uint128_t upper{0}; if (bit < 128) { const simde_uint128 lowbits = chunk << static_cast(bit); lower = uint128_t{static_cast(lowbits >> 64), static_cast(lowbits)}; if (bit > 128 - mant_bits) { const simde_uint128 hibits = chunk >> static_cast(128 - bit); upper = uint128_t{static_cast(hibits >> 64), static_cast(hibits)}; } } else { const simde_uint128 hibits = chunk << static_cast(bit - 128); upper = uint128_t{static_cast(hibits >> 64), static_cast(hibits)}; } return uint256_t{upper, lower}; } template struct is_static_castable : std::false_type {}; template struct is_static_castable(std::declval()))>> : std::true_type {}; /// Low `bits` of `wide`, saturated to all-ones when `wide` does not fit. template Raw saturate_low_bits(uint256_t wide) noexcept { static_assert(Bits > 0 && Bits <= 256); if constexpr (Bits <= 64) { const std::uint64_t low = wide.lower().lower(); const bool overflow = wide.upper() != uint128_t{0} || wide.lower().upper() != 0 || (Bits < 64 && (low >> Bits) != 0); const std::uint64_t mag = overflow ? (Bits == 64 ? ~std::uint64_t{0} : ((std::uint64_t{1} << Bits) - 1)) : low; return static_cast(mag); } else if constexpr (Bits <= 128) { const std::uint64_t lo = wide.lower().lower(); const std::uint64_t hi = wide.lower().upper(); const bool overflow = wide.upper() != uint128_t{0} || (Bits < 128 && (hi >> (Bits - 64)) != 0); simde_uint128 mag; if (overflow) { if constexpr (Bits == 128) mag = ~simde_uint128{0}; else mag = (simde_uint128{1} << Bits) - 1; } else { mag = (static_cast(hi) << 64) | lo; } return static_cast(mag); } else { uint256_t mag = wide; if constexpr (Bits < 256) { const auto sh = static_cast(Bits - 128); const bool overflow = (wide.upper() >> sh) != uint128_t{0}; if (overflow) { const uint128_t hi = (uint128_t{1} << sh) - 1; mag = uint256_t{hi, ~uint128_t{0}}; } } return static_cast(mag); } } template inline IntegralType rounded_double_to_integral(double rounded) noexcept { if constexpr (std::is_integral_v || std::is_same_v || std::is_same_v) { return static_cast(rounded); } else { const bool neg = std::signbit(rounded); const double mag = neg ? -rounded : rounded; const uint256_t wide = uint256_from_rounded_double(mag); IntegralType result{}; if constexpr (std::is_same_v) { result = wide.upper() != uint128_t{0} ? ~uint128_t{0} : wide.lower(); } else if constexpr (std::is_same_v) { result = wide; } else { constexpr std::size_t bits = dpf::utils::bitlength_of_v; using raw_type = typename dpf::utils::make_from_integral_value::integral_type; if constexpr (bits > 0 && bits <= 256 && is_static_castable::value) { const raw_type raw = saturate_low_bits(wide); result = dpf::utils::make_from_integral_value{}(raw); } else { static_assert(bits > 0 && bits <= 256 && is_static_castable::value, "fixedpoint construction from double is not implemented for this integer type"); return IntegralType{}; } } if (neg) { result = -result; } return result; } } /// Shift an integer into fixed-point raw form: `value * 2^FractionalBits`, /// wrapping in the backend's two's-complement encoding. One shift; no `double`. template HEDLEY_ALWAYS_INLINE HEDLEY_CONST constexpr IntegralType scale_integer_to_fixed_raw(T integer_value) noexcept { using unsigned_type = dpf::utils::make_unsigned_t; const auto bits = static_cast( static_cast(integer_value)); if constexpr (FractionalBits == 0) { return static_cast(bits); } return static_cast(bits << FractionalBits); } template inline constexpr bool is_signed_rep_v = std::is_signed_v || std::is_same_v; /// Two's-complement negate via the unsigned width. Defined for the /// most-negative value (wraps); signed `-x` would be UB there. template HEDLEY_ALWAYS_INLINE HEDLEY_CONST constexpr IntegralType raw_neg(IntegralType x) noexcept { using unsigned_type = dpf::utils::make_unsigned_t; return static_cast( unsigned_type{} - static_cast(x)); } template HEDLEY_ALWAYS_INLINE HEDLEY_CONST constexpr IntegralType raw_abs(IntegralType x) noexcept { if constexpr (is_signed_rep_v) { return (x < IntegralType{}) ? raw_neg(x) : x; } return x; } /// Remainder with the sign of `a` and magnitude `< |b|` (C++ `%` / /// `std::fmod`). Zero divisor → 0; this type has no NaN. template HEDLEY_ALWAYS_INLINE HEDLEY_CONST constexpr IntegralType raw_fmod(IntegralType a, IntegralType b) noexcept { if (b == IntegralType{}) { return IntegralType{}; } return a % b; } } // namespace detail template auto constexpr make_fixed_from_integral_type(IntegralType value) noexcept; /// @tparam FractionalBits Number of fractional bits used in the fixed-point /// representation. /// @tparam IntegralType The underlying integral type used for the fixed-point /// representation. template struct fixedpoint { using integral_type = IntegralType; static constexpr int fractional_bits = FractionalBits; static constexpr int integer_bits = dpf::utils::bitlength_of_v - fractional_bits; static_assert(std::numeric_limits::is_integer || std::is_same_v || std::is_same_v); static_assert(fractional_bits <= dpf::utils::bitlength_of_v); static_assert(integer_bits >= 0); private: struct raw_tag {}; public: /// @name C'tors /// @brief Constructs a new fixed-point number. /// @{ /// @brief Default c'tor /// @details Zero-initializes the encoding (`fixedpoint x;` is 0). HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint() noexcept = default; /// @brief Copy c'tor /// @details Constructs a fixed-point with the value copied from `other`. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint(const fixedpoint & other) noexcept = default; /// @brief Move c'tor /// @details Constructs a fixed-point with the value copied from `other` using move semantics. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint(fixedpoint && other) noexcept = default; /// @brief Value c'tor /// @details Initializes the fixed-point with the value determined by `desired`, using the current rounding mode for the least-significant bit. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint(double desired) noexcept // NOLINT (implicit c'tor) : value{detail::rounded_double_to_integral( std::nearbyint(std::ldexp(desired, fractional_bits)))} { } /// @brief Integer value c'tor /// @details `fixedpoint(3)` is the mathematical value 3 (raw encoding /// `3 << fractional_bits`), not a raw word. One shift; no `double`. /// Use `from_raw` for a bit-exact encoding. template && !std::is_same_v, bool>, int> = 0> HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint(T integer_value) noexcept // NOLINT (implicit c'tor) : value{detail::scale_integer_to_fixed_raw< integral_type, static_cast(fractional_bits)>(integer_value)} { } /// @brief Bit-exact construction from the backend integer encoding. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW static constexpr fixedpoint from_raw(integral_type raw) noexcept { return fixedpoint{raw, raw_tag{}}; } /// @} /// @name Assignment operators /// @brief Assign a new value to a fixed-point number /// {@ /// @brief Copy assignment /// @details Assigns the fixed-point with a copy of `other` HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator=(const fixedpoint & other) noexcept = default; /// @brief Move assignment /// @details Assigns the fixed-point with a copy of `other` using move semantics. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator=(fixedpoint && other) noexcept = default; /// @brief Value assignment /// @details Assigns the fixed-point with a value determined by `desired`, using the current rounding mode for the least-significant bit.. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator=(const double & desired) noexcept { value = detail::rounded_double_to_integral( std::nearbyint(std::ldexp(desired, fractional_bits))); return *this; } /// @} ~fixedpoint() = default; /// @brief Cast to `double` HEDLEY_ALWAYS_INLINE HEDLEY_PURE explicit constexpr operator double() const noexcept { return std::ldexp(static_cast(value), -static_cast(fractional_bits)); } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator&(integral_type mask) const noexcept { return static_cast(this->integral_representation() & mask); } /// Bit test against another encoding (DPF writes `mask & x` with both /// sides the input type when `msb_mask` is a `fixedpoint`). HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator&(fixedpoint mask) const noexcept { return static_cast(value & mask.value); } /// Bitwise complement of the encoding. `std::bit_not` uses this. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr fixedpoint operator~() const noexcept { using unsigned_type = dpf::utils::make_unsigned_t; return from_raw(static_cast( ~static_cast(value))); } /// Next / previous representable encoding (one ULP). HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator++() noexcept { ++value; return *this; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint operator++(int) noexcept { fixedpoint tmp = *this; ++*this; return tmp; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator--() noexcept { --value; return *this; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint operator--(int) noexcept { fixedpoint tmp = *this; --*this; return tmp; } /// Logical shift of the encoding. DPF walks `msb_mask` with `>>`; a /// signed arithmetic shift would sign-extend the MSB and break that. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator>>=(std::size_t n) noexcept { using unsigned_type = dpf::utils::make_unsigned_t; value = static_cast(static_cast(value) >> n); return *this; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator<<=(std::size_t n) noexcept { using unsigned_type = dpf::utils::make_unsigned_t; value = static_cast(static_cast(value) << n); return *this; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW friend constexpr fixedpoint operator>>(fixedpoint x, std::size_t n) noexcept { x >>= n; return x; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW friend constexpr fixedpoint operator<<(fixedpoint x, std::size_t n) noexcept { x <<= n; return x; } /// @brief Access underlying integral representation /// @details If the represented fixed-point number is `x`, then this /// function returns an `integral_type` whose value is `x*2**fractional_bits`. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr integral_type integral_representation() const noexcept { return this->value; } /// @brief Unary negation operator HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr fixedpoint operator-() const noexcept { return from_raw(detail::raw_neg(value)); } /// @brief Binary addition operator /// @details Computes the sum of two fixed-point numbers HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr fixedpoint operator+(fixedpoint rhs) const noexcept { return from_raw(value + rhs.value); } /// @brief Binary addition assignment operator HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator+=(fixedpoint rhs) noexcept { this->value += rhs.integral_representation(); return *this; } /// @brief Binary subtraction operator HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr fixedpoint operator-(fixedpoint rhs) const noexcept { return from_raw(value - rhs.value); } /// @brief Binary addition assignment operator HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator-=(fixedpoint rhs) noexcept { this->value -= rhs.integral_representation(); return *this; } /// @brief Binary multiplication operator template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr auto operator*(fixedpoint rhs) const noexcept { return make_fixed_from_integral_type((this->integral_representation() * rhs.integral_representation())); } /// @name Equality /// @brief Strict equality operator /// @{ HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator==(fixedpoint rhs) const noexcept { return (this->integral_representation() == rhs.integral_representation()); } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator==(double rhs) const noexcept { return is_in_range(rhs) && (*this == fixedpoint(rhs)); } /// @} /// @name Inequality /// @brief Strict inequality operator /// @{ HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator!=(fixedpoint rhs) const noexcept { return (this->integral_representation() != rhs.integral_representation()); } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator!=(double rhs) const noexcept { return !(*this == rhs); } /// @} /// @name Less than /// @brief Binary less-than operator /// @{ HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator<(fixedpoint rhs) const noexcept { return (this->integral_representation() < rhs.integral_representation()); } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator<(double rhs) const noexcept { if (std::isnan(rhs)) return false; if (!is_in_range(rhs)) return rhs > static_cast(std::numeric_limits::max()); return *this < fixedpoint(rhs); } /// @} /// @name Less than or equal /// @brief Binary less-than-or-equal operator /// @{ HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator<=(fixedpoint rhs) const noexcept { return (this->integral_representation() <= rhs.integral_representation()); } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator<=(double rhs) const noexcept { if (std::isnan(rhs)) return false; if (!is_in_range(rhs)) return rhs > static_cast(std::numeric_limits::max()); return *this <= fixedpoint(rhs); } /// @} /// @name Greater than /// @brief Binary greater-than operator /// @{ HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator>(fixedpoint rhs) const noexcept { return (this->integral_representation() > rhs.integral_representation()); } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator>(double rhs) const noexcept { if (std::isnan(rhs)) return false; if (!is_in_range(rhs)) return rhs < static_cast(std::numeric_limits::lowest()); return *this > fixedpoint(rhs); } /// @} /// @name Greater than or equal /// @brief Binary greater-than-or-equal operator /// @{ HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator>=(fixedpoint rhs) const noexcept { return (this->integral_representation() >= rhs.integral_representation()); } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE constexpr bool operator>=(double rhs) const noexcept { if (std::isnan(rhs)) return false; if (!is_in_range(rhs)) return rhs < static_cast(std::numeric_limits::lowest()); return *this >= fixedpoint(rhs); } /// @} private: // struct make_fixed_from_integral_type_tag {}; /// @brief Determine if a floating-point is within range HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW static constexpr bool is_in_range(double d) noexcept { return HEDLEY_LIKELY( static_cast(std::numeric_limits::lowest()) <= d && d <= static_cast(std::numeric_limits::max())); } // HEDLEY_ALWAYS_INLINE // HEDLEY_NO_THROW // constexpr explicit fixedpoint(integral_type val, make_fixed_from_integral_type_tag &&) noexcept // : value{val} // { } // template friend constexpr auto nextafter(fixedpoint) noexcept; // template friend constexpr auto nextbefore(fixedpoint) noexcept; // template friend constexpr auto precision_cast(fixedpoint) noexcept; template friend auto constexpr make_fixed_from_integral_type(T) noexcept; // template friend constexpr auto fabs(fixedpoint) noexcept; // template friend constexpr auto fmod(fixedpoint, double) noexcept; HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint(integral_type raw, raw_tag) noexcept : value{raw} { } integral_type value{}; }; /// @brief Bit test with the mask on the left. DPF key generation and /// evaluation write `mask & x`. template constexpr bool operator&(const Mask & mask, const fixedpoint & x) noexcept { return static_cast(x.integral_representation() & static_cast(mask)); } template std::basic_ostream & operator<<(std::basic_ostream & os, const fixedpoint & f) noexcept { return os << static_cast(f); } template std::basic_istream & operator>>(std::basic_istream & is, fixedpoint & f) { double d; is >> d; f = d; return is; } template auto constexpr make_fixed_from_integral_type(IntegralType value) noexcept { return fixedpoint::from_raw(value); } template HEDLEY_ALWAYS_INLINE HEDLEY_CONST static constexpr auto make_fixed(double d) { return fixedpoint(d); } template HEDLEY_ALWAYS_INLINE HEDLEY_CONST static constexpr auto make_fixed(double d) { return fixedpoint(d); } /// @brief Creates a fixed-point number from a double with bounds checking. /// @throws std::range_error If the input double is outside the representable /// range of the fixed-point number. template HEDLEY_ALWAYS_INLINE HEDLEY_CONST static auto make_fixed_safe(double d) { using fixed_type = fixedpoint; if (HEDLEY_UNLIKELY(d < std::numeric_limits::lowest())) { throw std::range_error("value is too small (underflows integral representation)"); } if (HEDLEY_UNLIKELY(std::numeric_limits::max() < d)) { throw std::range_error("value is too large (overflows integral representation)"); } return make_fixed(d); } template constexpr auto precision_cast(const fixedpoint & f) noexcept { auto value = f.integral_representation(); if constexpr (ToFractionalBits > FromFractionalBits) { return make_fixed_from_integral_type(value << (ToFractionalBits - FromFractionalBits)); } else { return make_fixed_from_integral_type(value >> (FromFractionalBits - ToFractionalBits)); } } template static constexpr auto precision_of(fixedpoint) noexcept { return FractionalBits; } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_CONST constexpr auto nextafter(fixedpoint f) noexcept { return make_fixed_from_integral_type(f.integral_representation()+1); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_CONST constexpr auto nextbefore(fixedpoint f) noexcept { return make_fixed_from_integral_type(f.integral_representation()-1); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_CONST constexpr auto fabs(fixedpoint v) noexcept { if constexpr (!detail::is_signed_rep_v) { return v; } const auto mag = v.integral_representation(); if (mag < IntegralType{}) { return fixedpoint::from_raw( detail::raw_neg(mag)); } return v; } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_CONST constexpr auto fmod(fixedpoint v, fixedpoint modulus) noexcept { return fixedpoint::from_raw( detail::raw_fmod(v.integral_representation(), modulus.integral_representation())); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_CONST constexpr auto fmod(fixedpoint v, double modulus) noexcept { return fmod(v, make_fixed(modulus)); } template && !std::is_same_v, bool>, int> = 0> HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_CONST constexpr auto fmod(fixedpoint v, T modulus) noexcept { return fmod(v, fixedpoint(modulus)); } enum fixed_cast_policy { use_default, use_left_arg, use_right_arg, use_min_arg, use_max_arg, use_arg_sum //< for multiplies only }; template struct binary_operator_precast_wrapper { static_assert(Mode == use_default || Mode == use_left_arg || Mode == use_right_arg || Mode == use_min_arg || Mode == use_max_arg); template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_CONST constexpr auto operator()( fixedpoint lhs, fixedpoint rhs) const noexcept { constexpr bool lt = (FractionalBitsLHS < FractionalBitsRHS); constexpr bool eq = (FractionalBitsLHS == FractionalBitsRHS); constexpr BinaryOperator op{}; if constexpr (eq) return op(lhs, rhs); // no cast necessary else if constexpr ((Mode == use_left_arg) // always casting to lhs || ((Mode == use_min_arg) && lt) // lhs happens to be min || ((Mode == use_max_arg) && !lt) // lhs happens to be max || ((Mode == use_default) && !lt)) // default == use_max { return op(lhs, precision_cast(rhs)); } else if constexpr ((Mode == use_right_arg) // always casting to rhs || ((Mode == use_min_arg) && !lt) // rhs happens to be min || ((Mode == use_max_arg) && lt) // rhs happens to be max || ((Mode == use_default) && lt)) // default == use_max { return op(precision_cast(lhs), rhs); } else { HEDLEY_UNREACHABLE(); } } }; template struct multiplies { static_assert(Mode == use_default || Mode == use_left_arg || Mode == use_right_arg || Mode == use_min_arg || Mode == use_max_arg || Mode == use_arg_sum); template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_CONST constexpr auto operator()( fixedpoint lhs, fixedpoint rhs) const noexcept { // First element is the right-shift that brings the raw product down to // `FractionalBitsOut`. It is negative when the product must be shifted left. constexpr auto r = []() -> std::pair { constexpr bool lt = (FractionalBitsLHS < FractionalBitsRHS); constexpr bool eq = (FractionalBitsLHS == FractionalBitsRHS); if constexpr (FractionalBitsOut != unsigned(-1)) { return {static_cast(FractionalBitsLHS + FractionalBitsRHS) - static_cast(FractionalBitsOut), FractionalBitsOut}; } if constexpr (eq) { return {static_cast((FractionalBitsLHS+FractionalBitsLHS)/2), 1u+(FractionalBitsLHS+FractionalBitsLHS-1)/2}; } if constexpr ((Mode == use_left_arg) || ((Mode == use_min_arg) && lt) || ((Mode == use_max_arg) && !lt)) { return {static_cast(FractionalBitsRHS), FractionalBitsLHS}; } if constexpr ((Mode == use_right_arg) || ((Mode == use_min_arg) && !lt) || ((Mode == use_max_arg) && lt)) { return {static_cast(FractionalBitsLHS), FractionalBitsRHS}; } else if constexpr ((Mode == use_arg_sum) || (Mode == use_default)) { return {0, FractionalBitsRHS+FractionalBitsLHS}; } }(); if constexpr (std::numeric_limits::digits > 32) { simde_uint128 product = simde_uint128(lhs.integral_representation()) * rhs.integral_representation(); if constexpr (r.first < 0) product = product << -r.first; else product = product >> r.first; return make_fixed_from_integral_type( static_cast(product)); } else { std::uint64_t product = std::uint64_t(lhs.integral_representation()) * rhs.integral_representation(); if constexpr (r.first < 0) product = product << -r.first; else product = product >> static_cast(r.first); return make_fixed_from_integral_type( static_cast(product)); } } }; template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_CONST constexpr bool operator==(double lhs, fixedpoint rhs) noexcept { return (rhs == lhs); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_CONST constexpr bool operator<(double lhs, fixedpoint rhs) noexcept { return (rhs > lhs); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_CONST constexpr bool operator<=(double lhs, fixedpoint rhs) noexcept { return (rhs >= lhs); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_CONST constexpr bool operator>(double lhs, fixedpoint rhs) noexcept { return (rhs < lhs); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_CONST constexpr bool operator>=(double lhs, fixedpoint rhs) noexcept { return (rhs <= lhs); } template struct fixedpoint_polynomial : public std::array { using coefficient_type = FixedPointType; static constexpr std::size_t degree = Degree; constexpr auto operator()(coefficient_type x) const { constexpr auto product_of = multiplies{}; constexpr auto sum_of = binary_operator_precast_wrapper, use_max_arg>{}; auto coeff = this->rbegin(); coefficient_type y{*coeff}; while (++coeff != this->rend()) { y = sum_of(product_of(y, x), *coeff); } return y; } }; template static constexpr auto evaluate(const fixedpoint_polynomial & poly, FixedPointType x) { return poly(x); } namespace fixedpoint_literals { constexpr auto operator "" _fixed0(long double val) { return grotto::make_fixed<0>(val); } constexpr auto operator "" _fixed1(long double val) { return grotto::make_fixed<1>(val); } constexpr auto operator "" _fixed2(long double val) { return grotto::make_fixed<2>(val); } constexpr auto operator "" _fixed3(long double val) { return grotto::make_fixed<3>(val); } constexpr auto operator "" _fixed4(long double val) { return grotto::make_fixed<4>(val); } constexpr auto operator "" _fixed5(long double val) { return grotto::make_fixed<5>(val); } constexpr auto operator "" _fixed6(long double val) { return grotto::make_fixed<6>(val); } constexpr auto operator "" _fixed7(long double val) { return grotto::make_fixed<7>(val); } constexpr auto operator "" _fixed8(long double val) { return grotto::make_fixed<8>(val); } constexpr auto operator "" _fixed9(long double val) { return grotto::make_fixed<9>(val); } constexpr auto operator "" _fixed10(long double val) { return grotto::make_fixed<10>(val); } constexpr auto operator "" _fixed11(long double val) { return grotto::make_fixed<11>(val); } constexpr auto operator "" _fixed12(long double val) { return grotto::make_fixed<12>(val); } constexpr auto operator "" _fixed13(long double val) { return grotto::make_fixed<13>(val); } constexpr auto operator "" _fixed14(long double val) { return grotto::make_fixed<14>(val); } constexpr auto operator "" _fixed15(long double val) { return grotto::make_fixed<15>(val); } constexpr auto operator "" _fixed16(long double val) { return grotto::make_fixed<16>(val); } constexpr auto operator "" _fixed17(long double val) { return grotto::make_fixed<17>(val); } constexpr auto operator "" _fixed18(long double val) { return grotto::make_fixed<18>(val); } constexpr auto operator "" _fixed19(long double val) { return grotto::make_fixed<19>(val); } constexpr auto operator "" _fixed20(long double val) { return grotto::make_fixed<20>(val); } constexpr auto operator "" _fixed21(long double val) { return grotto::make_fixed<21>(val); } constexpr auto operator "" _fixed22(long double val) { return grotto::make_fixed<22>(val); } constexpr auto operator "" _fixed23(long double val) { return grotto::make_fixed<23>(val); } constexpr auto operator "" _fixed24(long double val) { return grotto::make_fixed<24>(val); } constexpr auto operator "" _fixed25(long double val) { return grotto::make_fixed<25>(val); } constexpr auto operator "" _fixed26(long double val) { return grotto::make_fixed<26>(val); } constexpr auto operator "" _fixed27(long double val) { return grotto::make_fixed<27>(val); } constexpr auto operator "" _fixed28(long double val) { return grotto::make_fixed<28>(val); } constexpr auto operator "" _fixed29(long double val) { return grotto::make_fixed<29>(val); } constexpr auto operator "" _fixed30(long double val) { return grotto::make_fixed<30>(val); } constexpr auto operator "" _fixed31(long double val) { return grotto::make_fixed<31>(val); } constexpr auto operator "" _fixed32(long double val) { return grotto::make_fixed<32>(val); } constexpr auto operator "" _fixed33(long double val) { return grotto::make_fixed<33>(val); } constexpr auto operator "" _fixed34(long double val) { return grotto::make_fixed<34>(val); } constexpr auto operator "" _fixed35(long double val) { return grotto::make_fixed<35>(val); } constexpr auto operator "" _fixed36(long double val) { return grotto::make_fixed<36>(val); } constexpr auto operator "" _fixed37(long double val) { return grotto::make_fixed<37>(val); } constexpr auto operator "" _fixed38(long double val) { return grotto::make_fixed<38>(val); } constexpr auto operator "" _fixed39(long double val) { return grotto::make_fixed<39>(val); } constexpr auto operator "" _fixed40(long double val) { return grotto::make_fixed<40>(val); } constexpr auto operator "" _fixed41(long double val) { return grotto::make_fixed<41>(val); } constexpr auto operator "" _fixed42(long double val) { return grotto::make_fixed<42>(val); } constexpr auto operator "" _fixed43(long double val) { return grotto::make_fixed<43>(val); } constexpr auto operator "" _fixed44(long double val) { return grotto::make_fixed<44>(val); } constexpr auto operator "" _fixed45(long double val) { return grotto::make_fixed<45>(val); } constexpr auto operator "" _fixed46(long double val) { return grotto::make_fixed<46>(val); } constexpr auto operator "" _fixed47(long double val) { return grotto::make_fixed<47>(val); } constexpr auto operator "" _fixed48(long double val) { return grotto::make_fixed<48>(val); } constexpr auto operator "" _fixed49(long double val) { return grotto::make_fixed<49>(val); } constexpr auto operator "" _fixed50(long double val) { return grotto::make_fixed<50>(val); } constexpr auto operator "" _fixed51(long double val) { return grotto::make_fixed<51>(val); } constexpr auto operator "" _fixed52(long double val) { return grotto::make_fixed<52>(val); } constexpr auto operator "" _fixed53(long double val) { return grotto::make_fixed<53>(val); } constexpr auto operator "" _fixed54(long double val) { return grotto::make_fixed<54>(val); } constexpr auto operator "" _fixed55(long double val) { return grotto::make_fixed<55>(val); } constexpr auto operator "" _fixed56(long double val) { return grotto::make_fixed<56>(val); } constexpr auto operator "" _fixed57(long double val) { return grotto::make_fixed<57>(val); } constexpr auto operator "" _fixed58(long double val) { return grotto::make_fixed<58>(val); } constexpr auto operator "" _fixed59(long double val) { return grotto::make_fixed<59>(val); } constexpr auto operator "" _fixed60(long double val) { return grotto::make_fixed<60>(val); } constexpr auto operator "" _fixed61(long double val) { return grotto::make_fixed<61>(val); } constexpr auto operator "" _fixed62(long double val) { return grotto::make_fixed<62>(val); } constexpr auto operator "" _fixed63(long double val) { return grotto::make_fixed<63>(val); } constexpr auto operator "" _fixed64(long double val) { return grotto::make_fixed<64>(val); } } // namespace grotto::fixedpoint_literals } // namespace grotto using grotto::precision_cast; namespace dpf { namespace utils { template struct bitlength_of> : public bitlength_of { }; template struct msb_of> { static constexpr grotto::fixedpoint value = grotto::fixedpoint::from_raw( static_cast(msb_of_v)); }; template struct uses_signed_msb> : std::bool_constant> { }; template struct make_unsigned> { using type = grotto::fixedpoint>; }; template struct countl_zero_symmetric_difference> { using T = grotto::fixedpoint; static constexpr auto clz = dpf::utils::countl_zero_symmetric_difference{}; HEDLEY_PURE HEDLEY_ALWAYS_INLINE constexpr std::size_t operator()(const T & lhs, const T & rhs) const noexcept { return clz(lhs.integral_representation(), rhs.integral_representation()); } }; template struct to_integral_type> : to_integral_type_base> { using parent = to_integral_type_base>; using typename parent::integral_type; HEDLEY_PURE HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr integral_type operator()( const grotto::fixedpoint & input) const noexcept { return to_integral_type{}(input.integral_representation()); } }; template struct mod_pow_2> { using fixed_type = grotto::fixedpoint; static constexpr auto mod = mod_pow_2{}; std::size_t operator()(fixed_type val, std::size_t n) const noexcept { return mod(val.integral_representation(), n); } }; template struct make_from_integral_value> { using fixed_type = grotto::fixedpoint; using integral_type = typename to_integral_type::integral_type; constexpr fixed_type operator()(integral_type val) const noexcept { return grotto::make_fixed_from_integral_type( static_cast(val)); } }; template struct flip_msb_for_input> { using fixed_type = grotto::fixedpoint; constexpr void operator()(fixed_type & x) const { if constexpr (uses_signed_msb_v) { using unsigned_type = make_unsigned_t; auto raw = static_cast(x.integral_representation()); raw ^= static_cast(msb_of_v); x = grotto::make_fixed_from_integral_type( static_cast(raw)); } } }; } // namespace dpf::utils } // namespace dpf namespace dpf::leaf_arithmetic { template struct add_t, simde__m128i> { auto operator()(const simde__m128i & a, const simde__m128i & b) const { return add_t{}(a, b); } }; template struct add_t, simde__m256i> { auto operator()(const simde__m256i & a, const simde__m256i & b) const { return add_t{}(a, b); } }; template struct subtract_t, simde__m128i> { auto operator()(const simde__m128i & a, const simde__m128i & b) const { return subtract_t{}(a, b); } }; template struct subtract_t, simde__m256i> { auto operator()(const simde__m256i & a, const simde__m256i & b) const { return subtract_t{}(a, b); } }; template struct multiply_t, simde__m128i> { auto operator()(const simde__m128i & a, grotto::fixedpoint b) const { return multiply_t{}(a, b.integral_representation()); } }; template struct multiply_t, simde__m256i> { auto operator()(const simde__m256i & a, grotto::fixedpoint b) const { return multiply_t{}(a, b.integral_representation()); } }; } // namespace dpf::leaf_arithmetic namespace std { template class numeric_limits> { using T = grotto::fixedpoint; using I = IntegralType; static constexpr bool signed_rep = std::is_signed_v || std::is_same_v; static constexpr int bitwidth = static_cast(dpf::utils::bitlength_of_v); static constexpr I raw_lowest() noexcept { if constexpr (signed_rep) { using U = dpf::utils::make_unsigned_t; return static_cast(U{1} << static_cast(bitwidth - 1)); } return I{}; } static constexpr I raw_max() noexcept { if constexpr (signed_rep) { using U = dpf::utils::make_unsigned_t; return static_cast(static_cast(raw_lowest()) - U{1}); } return static_cast(~I{}); } public: static constexpr bool is_specialized = true; static constexpr bool is_signed = signed_rep; static constexpr bool is_integer = (FractionalBits == 0); static constexpr bool is_exact = true; static constexpr bool has_infinity = false; static constexpr bool has_quiet_NaN = false; static constexpr bool has_signaling_NaN = false; static constexpr float_denorm_style has_denorm = denorm_absent; static constexpr bool has_denorm_loss = false; static constexpr float_round_style round_style = FractionalBits ? round_to_nearest : round_toward_zero; static constexpr bool is_iec559 = false; static constexpr bool is_bounded = true; static constexpr bool is_modulo = !signed_rep; static constexpr int digits = bitwidth - (signed_rep ? 1 : 0); static constexpr int digits10 = digits * 301 / 1000; static constexpr int max_digits10 = 0; static constexpr int radix = 2; static constexpr int min_exponent = 1 - static_cast(FractionalBits); static constexpr int max_exponent = digits - static_cast(FractionalBits) + 1; static constexpr int min_exponent10 = min_exponent * 301 / 1000; static constexpr int max_exponent10 = max_exponent * 301 / 1000; static constexpr bool traps = false; static constexpr bool tinyness_before = false; static constexpr T lowest() noexcept { return T::from_raw(raw_lowest()); } static constexpr T max() noexcept { return T::from_raw(raw_max()); } static constexpr T min() noexcept { if constexpr (FractionalBits == 0) { return lowest(); } return T::from_raw(I{1}); } static constexpr T epsilon() noexcept { return FractionalBits ? T::from_raw(I{1}) : T::from_raw(I{}); } static constexpr T round_error() noexcept { return FractionalBits ? T(0.5) : T(0); } static constexpr T infinity() noexcept { return max(); } static constexpr T quiet_NaN() noexcept { return T::from_raw(I{}); } static constexpr T signalling_NaN() noexcept { return T::from_raw(I{}); } static constexpr T denorm_min() noexcept { return min(); } }; template struct numeric_limits const> : public numeric_limits> {}; template struct numeric_limits volatile> : public numeric_limits> {}; template struct numeric_limits const volatile> : public numeric_limits> {}; } // namespace std #include "grotto/fixedpoint_mul.hpp" #endif // LIBDPF_INCLUDE_GROTTO_FIXEDPOINT_HPP__