Fixed-point improvements:

* Mark everything constexpr
* Use unsigned type for multiplication to prevent UB on overflow
* Add fp -> fp conversions
* Add floor & ceil functions
This commit is contained in:
Nikita Lisitsa 2025-09-14 00:03:07 +03:00
parent 7beba986ae
commit 8a739bf637

View file

@ -106,7 +106,7 @@ namespace psemek::util
};
template <typename T, typename H>
H clamp(T x, H min, H max)
constexpr H clamp(T x, H min, H max)
{
if (x < static_cast<T>(min))
return min;
@ -147,32 +147,37 @@ namespace psemek::util
using unsigned_rep_type = typename traits::unsigned_rep_type;
using unsigned_wide_type = typename traits::unsigned_wide_type;
fixed_point() = default;
fixed_point(fixed_point const &) = default;
constexpr fixed_point() = default;
constexpr fixed_point(fixed_point const &) = default;
constexpr fixed_point & operator = (fixed_point const &) = default;
template <std::integral T>
explicit fixed_point(T value);
constexpr explicit fixed_point(T value);
template <std::floating_point T>
explicit fixed_point(T value);
constexpr explicit fixed_point(T value);
template <unsigned int I2, unsigned int F2, bool S2>
constexpr explicit fixed_point(fixed_point<I2, F2, S2> value);
template <std::integral T>
explicit operator T () const;
constexpr explicit operator T () const;
template <std::floating_point T>
explicit operator T () const;
constexpr explicit operator T () const;
fixed_point & operator += (fixed_point);
fixed_point & operator -= (fixed_point);
fixed_point & operator *= (fixed_point);
fixed_point & operator /= (fixed_point);
constexpr fixed_point & operator += (fixed_point);
constexpr fixed_point & operator -= (fixed_point);
constexpr fixed_point & operator *= (fixed_point);
constexpr fixed_point & operator /= (fixed_point);
rep_type rep() const { return rep_; }
constexpr rep_type rep() const { return rep_; }
static fixed_point from_rep(rep_type rep);
static constexpr fixed_point from_rep(rep_type rep);
static fixed_point min();
static fixed_point max();
static constexpr fixed_point min();
static constexpr fixed_point max();
private:
rep_type rep_;
@ -180,32 +185,47 @@ namespace psemek::util
template <unsigned int I, unsigned int F, bool S>
template <std::integral T>
fixed_point<I, F, S>::fixed_point(T value)
constexpr fixed_point<I, F, S>::fixed_point(T value)
: rep_(static_cast<rep_type>(value) << F)
{}
template <unsigned int I, unsigned int F, bool S>
template <std::floating_point T>
fixed_point<I, F, S>::fixed_point(T value)
constexpr fixed_point<I, F, S>::fixed_point(T value)
: rep_(static_cast<rep_type>(detail::clamp(std::round(value * (static_cast<rep_type>(1) << F)), min().rep(), max().rep())))
{}
template <unsigned int I, unsigned int F, bool S>
template <unsigned int I2, unsigned int F2, bool S2>
constexpr fixed_point<I, F, S>::fixed_point(fixed_point<I2, F2, S2> value)
{
// TODO: what to do with signs?
if constexpr (F >= F2)
{
rep_ = static_cast<rep_type>(value.rep_) << (F - F2);
}
else
{
rep_ = static_cast<rep_type>(value.rep_) >> (F - F2);
}
}
template <unsigned int I, unsigned int F, bool S>
template <std::integral T>
fixed_point<I, F, S>::operator T () const
constexpr fixed_point<I, F, S>::operator T () const
{
return static_cast<T>(rep_) >> F;
}
template <unsigned int I, unsigned int F, bool S>
template <std::floating_point T>
fixed_point<I, F, S>::operator T () const
constexpr fixed_point<I, F, S>::operator T () const
{
return rep_ / static_cast<T>(static_cast<rep_type>(1) << F);
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> fixed_point<I, F, S>::from_rep(fixed_point<I, F, S>::rep_type rep)
constexpr fixed_point<I, F, S> fixed_point<I, F, S>::from_rep(fixed_point<I, F, S>::rep_type rep)
{
fixed_point<I, F, S> result;
result.rep_ = rep;
@ -213,7 +233,7 @@ namespace psemek::util
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> fixed_point<I, F, S>::min()
constexpr fixed_point<I, F, S> fixed_point<I, F, S>::min()
{
using FP = fixed_point<I, F, S>;
using rep_type = typename FP::rep_type;
@ -221,7 +241,7 @@ namespace psemek::util
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> fixed_point<I, F, S>::max()
constexpr fixed_point<I, F, S> fixed_point<I, F, S>::max()
{
using FP = fixed_point<I, F, S>;
using rep_type = typename FP::rep_type;
@ -229,7 +249,7 @@ namespace psemek::util
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> operator - (fixed_point<I, F, S> x)
constexpr fixed_point<I, F, S> operator - (fixed_point<I, F, S> x)
{
using FP = fixed_point<I, F, S>;
using rep_type = typename FP::rep_type;
@ -238,7 +258,7 @@ namespace psemek::util
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> operator + (fixed_point<I, F, S> x1, fixed_point<I, F, S> x2)
constexpr fixed_point<I, F, S> operator + (fixed_point<I, F, S> x1, fixed_point<I, F, S> x2)
{
using FP = fixed_point<I, F, S>;
using rep_type = typename FP::rep_type;
@ -247,7 +267,7 @@ namespace psemek::util
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> operator - (fixed_point<I, F, S> x1, fixed_point<I, F, S> x2)
constexpr fixed_point<I, F, S> operator - (fixed_point<I, F, S> x1, fixed_point<I, F, S> x2)
{
using FP = fixed_point<I, F, S>;
using rep_type = typename FP::rep_type;
@ -256,16 +276,16 @@ namespace psemek::util
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> operator * (fixed_point<I, F, S> x1, fixed_point<I, F, S> x2)
constexpr fixed_point<I, F, S> operator * (fixed_point<I, F, S> x1, fixed_point<I, F, S> x2)
{
using FP = fixed_point<I, F, S>;
using rep_type = typename FP::rep_type;
using wide_type = typename FP::wide_type;
return FP::from_rep(static_cast<rep_type>((static_cast<wide_type>(x1.rep()) * static_cast<wide_type>(x2.rep())) >> F));
using unsigned_wide_type = typename FP::unsigned_wide_type;
return FP::from_rep(static_cast<rep_type>((static_cast<unsigned_wide_type>(x1.rep()) * static_cast<unsigned_wide_type>(x2.rep())) >> F));
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> operator / (fixed_point<I, F, S> x1, fixed_point<I, F, S> x2)
constexpr fixed_point<I, F, S> operator / (fixed_point<I, F, S> x1, fixed_point<I, F, S> x2)
{
using FP = fixed_point<I, F, S>;
using rep_type = typename FP::rep_type;
@ -274,47 +294,47 @@ namespace psemek::util
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> & fixed_point<I, F, S>::operator += (fixed_point<I, F, S> x)
constexpr fixed_point<I, F, S> & fixed_point<I, F, S>::operator += (fixed_point<I, F, S> x)
{
(*this) = (*this) + x;
return (*this);
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> & fixed_point<I, F, S>::operator -= (fixed_point<I, F, S> x)
constexpr fixed_point<I, F, S> & fixed_point<I, F, S>::operator -= (fixed_point<I, F, S> x)
{
(*this) = (*this) - x;
return (*this);
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> & fixed_point<I, F, S>::operator *= (fixed_point<I, F, S> x)
constexpr fixed_point<I, F, S> & fixed_point<I, F, S>::operator *= (fixed_point<I, F, S> x)
{
(*this) = (*this) * x;
return (*this);
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> & fixed_point<I, F, S>::operator /= (fixed_point<I, F, S> x)
constexpr fixed_point<I, F, S> & fixed_point<I, F, S>::operator /= (fixed_point<I, F, S> x)
{
(*this) = (*this) / x;
return (*this);
}
template <unsigned int I, unsigned int F, bool S>
bool operator == (fixed_point<I, F, S> x1, fixed_point<I, F, S> x2)
constexpr bool operator == (fixed_point<I, F, S> x1, fixed_point<I, F, S> x2)
{
return x1.rep() == x2.rep();
}
template <unsigned int I, unsigned int F, bool S>
auto operator <=> (fixed_point<I, F, S> x1, fixed_point<I, F, S> x2)
constexpr auto operator <=> (fixed_point<I, F, S> x1, fixed_point<I, F, S> x2)
{
return x1.rep() <=> x2.rep();
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> abs(fixed_point<I, F, S> x)
constexpr fixed_point<I, F, S> abs(fixed_point<I, F, S> x)
{
using FP = fixed_point<I, F, S>;
using rep_type = typename FP::rep_type;
@ -325,11 +345,42 @@ namespace psemek::util
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> round(fixed_point<I, F, S> x)
constexpr fixed_point<I, F, S> floor(fixed_point<I, F, S> x)
{
using FP = fixed_point<I, F, S>;
using rep_type = typename FP::rep_type;
return FP::from_rep(x.rep() & ~((static_cast<rep_type>(1) << F) - 1));
constexpr auto fixed_point_one = static_cast<rep_type>(1) << F;
constexpr auto fractional_mask = fixed_point_one - static_cast<rep_type>(1);
return FP::from_rep(x.rep() & ~fractional_mask);
}
template <unsigned int I, unsigned int F, bool S>
constexpr fixed_point<I, F, S> ceil(fixed_point<I, F, S> x)
{
using FP = fixed_point<I, F, S>;
using rep_type = typename FP::rep_type;
constexpr auto fixed_point_one = static_cast<rep_type>(1) << F;
constexpr auto fractional_mask = fixed_point_one - static_cast<rep_type>(1);
return FP::from_rep(((x.rep() - static_cast<rep_type>(1)) & ~fractional_mask) + fixed_point_one);
}
template <unsigned int I, unsigned int F, bool S>
constexpr fixed_point<I, F, S> round(fixed_point<I, F, S> x)
{
if constexpr (F == 0)
{
return x;
}
else
{
using FP = fixed_point<I, F, S>;
using rep_type = typename FP::rep_type;
constexpr auto fixed_point_half = static_cast<rep_type>(1) << (F - 1);
if ((x.rep() & fixed_point_half) == 0)
return floor(x);
else
return ceil(x);
}
}
template <unsigned int I, unsigned int F, bool S>
@ -342,7 +393,7 @@ namespace psemek::util
}
template <unsigned int I, unsigned int F, bool S>
fixed_point<I, F, S> pow(fixed_point<I, F, S> x, int p)
constexpr fixed_point<I, F, S> pow(fixed_point<I, F, S> x, int p)
{
if (p < 0)
return fixed_point<I, F, S>(1) / pow(x, -p);