//===-- Utility class to manipulate fixed point numbers. --*- C++ -*-=========// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef LLVM_LIBC_SRC___SUPPORT_FIXED_POINT_FX_BITS_H #define LLVM_LIBC_SRC___SUPPORT_FIXED_POINT_FX_BITS_H #include "include/llvm-libc-macros/stdfix-macros.h" #include "src/__support/CPP/algorithm.h" #include "src/__support/CPP/bit.h" #include "src/__support/CPP/limits.h" // numeric_limits #include "src/__support/CPP/type_traits.h" #include "src/__support/libc_assert.h" #include "src/__support/macros/attributes.h" // LIBC_INLINE #include "src/__support/macros/config.h" // LIBC_NAMESPACE_DECL #include "src/__support/macros/null_check.h" // LIBC_CRASH_ON_VALUE #include "src/__support/macros/optimization.h" // LIBC_UNLIKELY #include "src/__support/math_extras.h" #include "fx_rep.h" #include #ifdef LIBC_COMPILER_HAS_FIXED_POINT namespace LIBC_NAMESPACE_DECL { namespace fixed_point { template struct FXBits { private: using fx_rep = FXRep; using StorageType = typename fx_rep::StorageType; StorageType value; static_assert(fx_rep::FRACTION_LEN > 0); static constexpr size_t FRACTION_OFFSET = 0; // Just for completeness static constexpr size_t INTEGRAL_OFFSET = fx_rep::INTEGRAL_LEN == 0 ? 0 : fx_rep::FRACTION_LEN; static constexpr size_t SIGN_OFFSET = fx_rep::SIGN_LEN == 0 ? 0 : ((sizeof(StorageType) * CHAR_BIT) - fx_rep::SIGN_LEN); static constexpr StorageType FRACTION_MASK = mask_trailing_ones() << FRACTION_OFFSET; static constexpr StorageType INTEGRAL_MASK = mask_trailing_ones() << INTEGRAL_OFFSET; static constexpr StorageType SIGN_MASK = (fx_rep::SIGN_LEN == 0 ? 0 : StorageType(1) << SIGN_OFFSET); // mask for static constexpr StorageType VALUE_MASK = INTEGRAL_MASK | FRACTION_MASK; // mask for static constexpr StorageType TOTAL_MASK = SIGN_MASK | VALUE_MASK; public: LIBC_INLINE constexpr FXBits() = default; template LIBC_INLINE constexpr explicit FXBits(XType x) { using Unqual = typename cpp::remove_cv_t; if constexpr (cpp::is_same_v) { value = cpp::bit_cast(x); } else if constexpr (cpp::is_same_v) { value = x; } else { // We don't want accidental type promotions/conversions, so we require // exact type match. static_assert(cpp::always_false); } } LIBC_INLINE constexpr StorageType get_fraction() { return (value & FRACTION_MASK) >> FRACTION_OFFSET; } LIBC_INLINE constexpr StorageType get_integral() { return (value & INTEGRAL_MASK) >> INTEGRAL_OFFSET; } // returns complete bitstring representation the fixed point number // the bitstring is of the form: padding | sign | integral | fraction LIBC_INLINE constexpr StorageType get_bits() { return (value & TOTAL_MASK) >> FRACTION_OFFSET; } // TODO: replace bool with Sign LIBC_INLINE constexpr bool get_sign() { return static_cast((value & SIGN_MASK) >> SIGN_OFFSET); } // This represents the effective negative exponent applied to this number LIBC_INLINE constexpr int get_exponent() { return fx_rep::FRACTION_LEN; } LIBC_INLINE constexpr void set_fraction(StorageType fraction) { value = (value & (~FRACTION_MASK)) | ((fraction << FRACTION_OFFSET) & FRACTION_MASK); } LIBC_INLINE constexpr void set_integral(StorageType integral) { value = (value & (~INTEGRAL_MASK)) | ((integral << INTEGRAL_OFFSET) & INTEGRAL_MASK); } // TODO: replace bool with Sign LIBC_INLINE constexpr void set_sign(bool sign) { value = (value & (~SIGN_MASK)) | ((static_cast(sign) << SIGN_OFFSET) & SIGN_MASK); } LIBC_INLINE constexpr T get_val() const { return cpp::bit_cast(value); } }; // Bit-wise operations are not available for fixed point types yet. template LIBC_INLINE constexpr cpp::enable_if_t, T> bit_and(T x, T y) { using BitType = typename FXRep::StorageType; BitType x_bit = cpp::bit_cast(x); BitType y_bit = cpp::bit_cast(y); // For some reason, bit_cast cannot deduce BitType from the input. return cpp::bit_cast(x_bit & y_bit); } template LIBC_INLINE constexpr cpp::enable_if_t, T> bit_or(T x, T y) { using BitType = typename FXRep::StorageType; BitType x_bit = cpp::bit_cast(x); BitType y_bit = cpp::bit_cast(y); // For some reason, bit_cast cannot deduce BitType from the input. return cpp::bit_cast(x_bit | y_bit); } template LIBC_INLINE constexpr cpp::enable_if_t, T> bit_not(T x) { using BitType = typename FXRep::StorageType; BitType x_bit = cpp::bit_cast(x); // For some reason, bit_cast cannot deduce BitType from the input. return cpp::bit_cast(static_cast(~x_bit)); } template LIBC_INLINE constexpr T abs(T x) { using FXRep = FXRep; if constexpr (FXRep::SIGN_LEN == 0) return x; else { if (LIBC_UNLIKELY(x == FXRep::MIN())) return FXRep::MAX(); return (x < FXRep::ZERO() ? -x : x); } } // Round-to-nearest, tie-to-(+Inf) template LIBC_INLINE constexpr T round(T x, int n) { using FXRep = FXRep; if (LIBC_UNLIKELY(n < 0)) n = 0; if (LIBC_UNLIKELY(n >= FXRep::FRACTION_LEN)) return x; T round_bit = FXRep::EPS() << (FXRep::FRACTION_LEN - n - 1); // Check for overflow. if (LIBC_UNLIKELY(FXRep::MAX() - round_bit < x)) return FXRep::MAX(); T all_ones = bit_not(FXRep::ZERO()); int shift = FXRep::FRACTION_LEN - n; T rounding_mask = (shift == FXRep::TOTAL_LEN) ? FXRep::ZERO() : (all_ones << shift); return bit_and((x + round_bit), rounding_mask); } // count leading sign bits // TODO: support fixed_point_padding template LIBC_INLINE constexpr cpp::enable_if_t, int> countls(T f) { using FXRep = FXRep; using BitType = typename FXRep::StorageType; using FXBits = FXBits; if constexpr (FXRep::SIGN_LEN > 0) { if (f < 0) f = bit_not(f); } BitType value_bits = FXBits(f).get_bits(); return cpp::countl_zero(value_bits) - FXRep::SIGN_LEN; } // fixed-point to integer conversion template LIBC_INLINE constexpr cpp::enable_if_t, XType> bitsfx(T f) { return cpp::bit_cast(f); } // divide the two fixed-point types and return an integer result template LIBC_INLINE constexpr cpp::enable_if_t, XType> idiv(T x, T y) { using FXBits = FXBits; using FXRep = FXRep; using CompType = typename FXRep::CompType; // If the value of the second operand of the / operator is zero, the // behavior is undefined. Ref: ISO/IEC TR 18037:2008(E) p.g. 16 LIBC_CRASH_ON_VALUE(y, FXRep::ZERO()); CompType x_comp = static_cast(FXBits(x).get_bits()); CompType y_comp = static_cast(FXBits(y).get_bits()); // If an integer result of one of these functions overflows, the behavior is // undefined. Ref: ISO/IEC TR 18037:2008(E) p.g. 16 CompType result = x_comp / y_comp; return static_cast(result); } LIBC_INLINE long accum nrstep(long accum d, long accum x0) { auto v = x0 * (2.lk - (d * x0)); return v; } // Divide the two integers and return a fixed_point value // // For reference, see: // https://en.wikipedia.org/wiki/Division_algorithm#Newton%E2%80%93Raphson_division // https://stackoverflow.com/a/9231996 template LIBC_INLINE constexpr XType divi(int n, int d) { // If the value of the second operand of the / operator is zero, the // behavior is undefined. Ref: ISO/IEC TR 18037:2008(E) p.g. 16 LIBC_CRASH_ON_VALUE(d, 0); if (LIBC_UNLIKELY(n == 0)) { return FXRep::ZERO(); } auto is_power_of_two = [](int n) { return (n > 0) && ((n & (n - 1)) == 0); }; long accum max_val = static_cast(FXRep::MAX()); long accum min_val = static_cast(FXRep::MIN()); if (is_power_of_two(cpp::abs(d))) { int k = cpp::countr_zero(static_cast(cpp::abs(d))); constexpr int F = FXRep::FRACTION_LEN; int64_t scaled_n = static_cast(n) << F; int64_t res64 = scaled_n >> k; constexpr int TOTAL_BITS = sizeof(XType) * 8; const int64_t max_limit = (1LL << (TOTAL_BITS - 1)) - 1; const int64_t min_limit = -(1LL << (TOTAL_BITS - 1)); if (res64 > max_limit) { return FXRep::MAX(); } else if (res64 < min_limit) { return FXRep::MIN(); } long accum res_accum = static_cast(res64) / static_cast(1 << F); res_accum = (d < 0) ? static_cast(-1) * res_accum : res_accum; if (res_accum > max_val) { return FXRep::MAX(); } else if (res_accum < min_val) { return FXRep::MIN(); } return static_cast(res_accum); } bool result_is_negative = ((n < 0) != (d < 0)); int64_t n64 = static_cast(n); int64_t d64 = static_cast(d); uint64_t nv = static_cast(n64 < 0 ? -n64 : n64); uint64_t dv = static_cast(d64 < 0 ? -d64 : d64); if (d == INT_MIN) { nv <<= 1; dv >>= 1; } uint32_t clz = cpp::countl_zero(static_cast(dv)) - 1; uint64_t scaled_val = dv << clz; // Scale denominator to be in the range of [0.5,1] FXBits d_scaled{scaled_val}; uint64_t scaled_val_n = nv << clz; // Scale the numerator as much as the denominator to maintain correctness of // the original equation FXBits n_scaled{scaled_val_n}; long accum n_scaled_val = n_scaled.get_val(); long accum d_scaled_val = d_scaled.get_val(); // x0 = (48/17) - (32/17) * d_n long accum a = 0x2.d89d89d8p0lk; // 48/17 = 2.8235294... long accum b = 0x1.e1e1e1e1p0lk; // 32/17 = 1.8823529... // Error of the initial approximation, as derived // from the wikipedia article is // E0 = 1/17 = 0.059 (5.9%) long accum initial_approx = a - (b * d_scaled_val); // Since, 0.5 <= d_scaled_val <= 1.0, 0.9412 <= initial_approx <= 1.88235 LIBC_ASSERT((initial_approx >= 0x0.78793dd9p0lk) && (initial_approx <= 0x1.f0f0d845p0lk)); // Each newton-raphson iteration will square the error, due // to quadratic convergence. So, // E1 = (0.059)^2 = 0.0034 long accum val = nrstep(d_scaled_val, initial_approx); if constexpr (FXRep::FRACTION_LEN > 8) { // E2 = 0.0000121 val = nrstep(d_scaled_val, val); if constexpr (FXRep::FRACTION_LEN > 16) { // E3 = 1.468e−10 val = nrstep(d_scaled_val, val); } } long accum res = n_scaled_val * val; if (result_is_negative) { res *= static_cast(-1); } // Per clause 7.18a.6.1, saturate values on overflow if (res > max_val) { return FXRep::MAX(); } else if (res < min_val) { return FXRep::MIN(); } else { return static_cast(res); } } } // namespace fixed_point } // namespace LIBC_NAMESPACE_DECL #endif // LIBC_COMPILER_HAS_FIXED_POINT #endif // LLVM_LIBC_SRC___SUPPORT_FIXED_POINT_FX_BITS_H