blob: 5c3b39d5a64aad081e40975dc9fcd5364aa378d0 [file]
//===-- 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 "src/__support/uint128.h"
#include "fx_rep.h"
#ifdef LIBC_COMPILER_HAS_FIXED_POINT
namespace LIBC_NAMESPACE_DECL {
namespace fixed_point {
template <typename T> struct FXBits {
private:
using fx_rep = FXRep<T>;
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<StorageType, fx_rep::FRACTION_LEN>()
<< FRACTION_OFFSET;
static constexpr StorageType INTEGRAL_MASK =
mask_trailing_ones<StorageType, fx_rep::INTEGRAL_LEN>()
<< INTEGRAL_OFFSET;
static constexpr StorageType SIGN_MASK =
(fx_rep::SIGN_LEN == 0 ? 0 : StorageType(1) << SIGN_OFFSET);
// mask for <integral | fraction>
static constexpr StorageType VALUE_MASK = INTEGRAL_MASK | FRACTION_MASK;
// mask for <sign | integral | fraction>
static constexpr StorageType TOTAL_MASK = SIGN_MASK | VALUE_MASK;
public:
LIBC_INLINE constexpr FXBits() = default;
template <typename XType> LIBC_INLINE constexpr explicit FXBits(XType x) {
using Unqual = typename cpp::remove_cv_t<XType>;
if constexpr (cpp::is_same_v<Unqual, T>) {
value = cpp::bit_cast<StorageType>(x);
} else if constexpr (cpp::is_same_v<Unqual, StorageType>) {
value = x;
} else {
// We don't want accidental type promotions/conversions, so we require
// exact type match.
static_assert(cpp::always_false<XType>);
}
}
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<bool>((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<StorageType>(sign) << SIGN_OFFSET) & SIGN_MASK);
}
LIBC_INLINE constexpr T get_val() const { return cpp::bit_cast<T>(value); }
};
// Bit-wise operations are not available for fixed point types yet.
template <typename T>
LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_fixed_point_v<T>, T>
bit_and(T x, T y) {
using BitType = typename FXRep<T>::StorageType;
BitType x_bit = cpp::bit_cast<BitType>(x);
BitType y_bit = cpp::bit_cast<BitType>(y);
// For some reason, bit_cast cannot deduce BitType from the input.
return cpp::bit_cast<T, BitType>(x_bit & y_bit);
}
template <typename T>
LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_fixed_point_v<T>, T>
bit_or(T x, T y) {
using BitType = typename FXRep<T>::StorageType;
BitType x_bit = cpp::bit_cast<BitType>(x);
BitType y_bit = cpp::bit_cast<BitType>(y);
// For some reason, bit_cast cannot deduce BitType from the input.
return cpp::bit_cast<T, BitType>(x_bit | y_bit);
}
template <typename T>
LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_fixed_point_v<T>, T>
bit_not(T x) {
using BitType = typename FXRep<T>::StorageType;
BitType x_bit = cpp::bit_cast<BitType>(x);
// For some reason, bit_cast cannot deduce BitType from the input.
return cpp::bit_cast<T, BitType>(static_cast<BitType>(~x_bit));
}
template <typename T> LIBC_INLINE constexpr T abs(T x) {
using FXRep = FXRep<T>;
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 <typename T> LIBC_INLINE constexpr T round(T x, int n) {
using FXRep = FXRep<T>;
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 <typename T>
LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_fixed_point_v<T>, int>
countls(T f) {
using FXRep = FXRep<T>;
using BitType = typename FXRep::StorageType;
using FXBits = FXBits<T>;
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 <typename T, typename XType>
LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_fixed_point_v<T>, XType>
bitsfx(T f) {
return cpp::bit_cast<XType, T>(f);
}
// divide the two fixed-point types and return an integer result
template <typename T, typename XType>
LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_fixed_point_v<T>, XType>
idiv(T x, T y) {
using FXBits = FXBits<T>;
using FXRep = FXRep<T>;
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<CompType>(FXBits(x).get_bits());
CompType y_comp = static_cast<CompType>(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<XType>(result);
}
// Divide 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 <typename FXType, typename IntType>
LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_fixed_point_v<FXType>, FXType>
fxdivi(IntType n, IntType d) {
using OutRep = FXRep<FXType>;
static_assert(cpp::is_signed_v<IntType> == (OutRep::SIGN_LEN > 0),
"IntType and FXType must have matching signedness");
constexpr bool IS_SIGNED = OutRep::SIGN_LEN > 0;
using UIntType = cpp::make_unsigned_t<IntType>;
// 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 OutRep::ZERO();
// n == d and d != 0 means the quotient is 1. The general NR path can't
// guarantee landing on 1 exactly so special case for this.
if (LIBC_UNLIKELY(n == d)) {
if constexpr (OutRep::INTEGRAL_LEN > 0)
return static_cast<FXType>(1);
else
return OutRep::MAX();
}
// Intermediate arithmetic is done in a wide fixed-point type.
using WideFXType =
cpp::conditional_t<IS_SIGNED, long accum, unsigned long accum>;
using WideRep = FXRep<WideFXType>;
using WideStorage = typename WideRep::StorageType;
constexpr int F = OutRep::FRACTION_LEN;
constexpr int WF = WideRep::FRACTION_LEN;
// Split each operand into a sign and a magnitude.
bool result_is_negative = false;
UIntType n_mag, d_mag;
if constexpr (IS_SIGNED) {
result_is_negative = (n < 0) != (d < 0);
n_mag = (n < 0) ? -static_cast<UIntType>(n) : static_cast<UIntType>(n);
d_mag = (d < 0) ? -static_cast<UIntType>(d) : static_cast<UIntType>(d);
} else {
n_mag = n;
d_mag = d;
}
if constexpr (OutRep::INTEGRAL_LEN > 0) {
constexpr UInt128 MAX_UNITS = static_cast<UInt128>(1)
<< OutRep::INTEGRAL_LEN;
if (LIBC_UNLIKELY(static_cast<UInt128>(n_mag) >=
MAX_UNITS * static_cast<UInt128>(d_mag)))
return result_is_negative ? OutRep::MIN() : OutRep::MAX();
}
WideFXType res;
constexpr int INTERMEDIATE_BITS = cpp::numeric_limits<UIntType>::digits + WF;
using WideIntType =
cpp::conditional_t<(INTERMEDIATE_BITS <= 64), uint64_t, UInt128>;
if ((d_mag & (d_mag - 1)) == 0) {
// d is a power of 2. n/d is an exact right shift.
int log2_d = cpp::countr_zero(d_mag);
WideIntType scaled_n = (static_cast<WideIntType>(n_mag) << WF) >> log2_d;
constexpr int WIDE_STORAGE_BITS = cpp::numeric_limits<WideStorage>::digits;
if (LIBC_UNLIKELY((static_cast<UInt128>(scaled_n) >> WIDE_STORAGE_BITS) !=
0))
return result_is_negative ? OutRep::MIN() : OutRep::MAX();
res = FXBits<WideFXType>(static_cast<WideStorage>(scaled_n)).get_val();
} else if constexpr (WF <= F) {
WideIntType wide_n = static_cast<WideIntType>(n_mag) << WF;
WideIntType quotient = wide_n / static_cast<WideIntType>(d_mag);
res = FXBits<WideFXType>(static_cast<WideStorage>(quotient)).get_val();
} else {
// General case: Approximate 1/d, then multiply by n.
// Normalize d_mag into a WF fraction value in [0.5, 1) and apply the same
// shift to n_mag so that n_scaled/d_scaled = n/d.
constexpr int W = cpp::numeric_limits<UIntType>::digits;
int d_msb = (W - 1) - cpp::countl_zero(d_mag);
int norm_shift = (WF - 1) - d_msb;
auto scale = [norm_shift](UIntType v) -> WideFXType {
WideStorage wide_v = static_cast<WideStorage>(v);
WideStorage shifted =
norm_shift >= 0 ? (wide_v << norm_shift) : (wide_v >> -norm_shift);
return FXBits<WideFXType>(shifted).get_val();
};
WideFXType d_scaled = scale(d_mag);
WideFXType n_scaled = scale(n_mag);
// Initial approximation of 1/d_scaled: x0 = 48/17 - (32/17) * d_scaled.
// d_scaled is in [0.5, 1) so x0 is in [0.941, 1.882] with a worst-case
// relative error bounded by 1/17 (~5.88%).
WideFXType a = static_cast<WideFXType>(0x2.d89d89d8p0lk); // 48/17
WideFXType b = static_cast<WideFXType>(0x1.e1e1e1e1p0lk); // 32/17
WideFXType initial_approx = a - b * d_scaled;
auto nrstep = [](WideFXType d_, WideFXType x0) {
return x0 * (static_cast<WideFXType>(2) - d_ * x0);
};
// Each iteration squares the relative error (quadratic convergence).
WideFXType recip = nrstep(d_scaled, initial_approx); // E1 <= 0.346%
if constexpr (F >= 7)
recip = nrstep(d_scaled, recip); // E2 <= 1.197e-5
if constexpr (F >= 15)
recip = nrstep(d_scaled, recip); // E3 <= 1.434e-10
res = n_scaled * recip;
}
if constexpr (IS_SIGNED) {
if (result_is_negative)
res = -res;
}
// According to clause 7.18a.6.1, saturate the result on overflow.
WideFXType max_val = static_cast<WideFXType>(OutRep::MAX());
if (res > max_val)
return OutRep::MAX();
if constexpr (IS_SIGNED) {
WideFXType min_val = static_cast<WideFXType>(OutRep::MIN());
if (res < min_val)
return OutRep::MIN();
}
return static_cast<FXType>(res);
}
// Divide an integer operand by a fixed-point operand and return the
// mathematically exact result as an IntType rounded towards 0. Assumes
// signedness of IntType matches the signedness of FXType.
template <typename IntType, typename FXType>
LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_fixed_point_v<FXType>, IntType>
divifx(IntType n, FXType d) {
using FXBits = FXBits<FXType>;
using FXRep = FXRep<FXType>;
using CompType = typename FXRep::CompType;
static_assert(cpp::is_signed_v<IntType> == (FXRep::SIGN_LEN > 0),
"IntType and FXType must have matching signedness");
// UB if denominator is 0.
LIBC_CRASH_ON_VALUE(d, FXRep::ZERO());
if (LIBC_UNLIKELY(n == 0)) {
return static_cast<IntType>(0);
}
CompType d_comp = static_cast<CompType>(FXBits(d).get_bits());
constexpr int F = FXRep::FRACTION_LEN;
constexpr int INTERMEDIATE_BITS =
sizeof(IntType) * 8 - cpp::is_signed_v<IntType> + F;
using WideType = cpp::conditional_t<
cpp::is_signed_v<IntType>,
cpp::conditional_t<INTERMEDIATE_BITS <= 64, int64_t, Int128>,
cpp::conditional_t<INTERMEDIATE_BITS <= 64, uint64_t, UInt128>>;
WideType scaled_n = static_cast<WideType>(n) << F;
WideType result = scaled_n / static_cast<WideType>(d_comp);
return static_cast<IntType>(result);
}
} // namespace fixed_point
} // namespace LIBC_NAMESPACE_DECL
#endif // LIBC_COMPILER_HAS_FIXED_POINT
#endif // LLVM_LIBC_SRC___SUPPORT_FIXED_POINT_FX_BITS_H