Skip to content

Instantly share code, notes, and snippets.

@kerrytazi
Created October 30, 2024 00:56
Show Gist options
  • Save kerrytazi/d806705f530f54744d910cbf22c12267 to your computer and use it in GitHub Desktop.
Save kerrytazi/d806705f530f54744d910cbf22c12267 to your computer and use it in GitHub Desktop.
Fixed point number for fast math.
#pragma once
template <unsigned int TPrecision>
class fnumber32
{
static_assert(TPrecision > 0, "fnumber32 precision must be greater than zero");
static_assert(TPrecision < 32, "fnumber32 precision must be less than 32");
private:
int num;
template <unsigned long long lhs, unsigned long long rhs>
static constexpr unsigned long long static_pow = static_pow<lhs, rhs - 1> * lhs;
template <unsigned long long lhs>
static constexpr unsigned long long static_pow<lhs, 0> = 1;
static constexpr unsigned int FRAC_MASK = ((1 << TPrecision) - 1);
static constexpr unsigned int SIGN_MASK = 0x8000'0000;
static constexpr unsigned long long TO_STRING_EXPONENT = static_pow<5, TPrecision>;
struct _init_tag {};
template <typename TFloat>
constexpr fnumber32(_init_tag, TFloat initial)
{
bool sign = initial < 0.0f;
if (sign)
initial = -initial;
num = (int)initial;
TFloat frac = initial - (TFloat)num;
frac = frac * (TFloat)(1 << TPrecision);
num = (num << TPrecision) | (int)frac;
if (sign)
num = -num;
}
public:
fnumber32() = default;
constexpr fnumber32(int initial)
: num{ initial << TPrecision }
{
}
constexpr fnumber32(float initial)
: fnumber32(_init_tag{}, initial)
{
}
constexpr fnumber32(double initial)
: fnumber32(_init_tag{}, initial)
{
}
constexpr fnumber32(long double initial)
: fnumber32(_init_tag{}, initial)
{
}
constexpr fnumber32 &operator+=(fnumber32 other)
{
num += other.num;
return *this;
}
constexpr fnumber32 &operator-=(fnumber32 other)
{
num -= other.num;
return *this;
}
constexpr fnumber32 &operator*=(fnumber32 other)
{
num = (int)(((long long)num * (long long)other.num) >> TPrecision);
return *this;
}
constexpr fnumber32 &operator/=(fnumber32 other)
{
num = (int)(((long long)num << TPrecision) / (long long)other.num);
return *this;
}
constexpr fnumber32 &operator%=(fnumber32 other)
{
num = (int)(((long long)num << TPrecision) % (long long)other.num);
return *this;
}
constexpr fnumber32 &operator*=(int initial)
{
num *= initial;
return *this;
}
constexpr fnumber32 &operator/=(int initial)
{
num /= initial;
return *this;
}
constexpr fnumber32 &operator%=(int initial)
{
num %= initial;
return *this;
}
[[nodiscard]] constexpr fnumber32 operator+(fnumber32 other) const
{
return fnumber32(*this) += other;
}
[[nodiscard]] constexpr fnumber32 &operator-(fnumber32 other) const
{
return fnumber32(*this) -= other;
}
[[nodiscard]] constexpr fnumber32 operator*(fnumber32 other) const
{
return fnumber32(*this) *= other;
}
[[nodiscard]] constexpr fnumber32 operator/(fnumber32 other) const
{
return fnumber32(*this) /= other;
}
[[nodiscard]] constexpr fnumber32 operator%(fnumber32 other) const
{
return fnumber32(*this) %= other;
}
[[nodiscard]] constexpr fnumber32 operator*(int initial) const
{
return fnumber32(*this) *= initial;
}
[[nodiscard]] constexpr fnumber32 operator/(int initial) const
{
return fnumber32(*this) /= initial;
}
[[nodiscard]] constexpr fnumber32 operator%(int initial) const
{
return fnumber32(*this) %= initial;
}
[[nodiscard]] constexpr int operator<=>(fnumber32 other) const
{
return num - other.num;
}
[[nodiscard]] constexpr int floor() const
{
return num >> TPrecision;
}
[[nodiscard]] constexpr int ceil() const
{
int result = num >> TPrecision;
if (num & FRAC_MASK)
result += 1;
return result;
}
[[nodiscard]] constexpr int round() const
{
int result = num >> TPrecision;
if (num & (1 << (TPrecision - 1)))
result += 1;
return result;
}
template <typename TChar, unsigned int TMaxSize = 32>
struct ToStringResult
{
TChar buffer[TMaxSize];
unsigned int size;
};
template <typename TChar = char>
[[nodiscard]] constexpr ToStringResult<TChar> to_string() const
{
ToStringResult<TChar> result = {};
const auto append = [&](char ch) {
result.buffer[result.size++] = (TChar)ch;
};
unsigned int unsigned_num = num;
if (num & SIGN_MASK)
{
result.buffer[result.size++] = (TChar)'-';
unsigned_num = -num;
}
unsigned int whole = unsigned_num >> TPrecision;
unsigned int frac = unsigned_num & FRAC_MASK;
if (whole)
{
ToStringResult<TChar> tmp = {};
while (whole)
{
unsigned int d1 = whole / 10;
unsigned int d2 = whole % 10;
tmp.buffer[tmp.size++] = (TChar)'0' + (TChar)d2;
whole = d1;
}
while (tmp.size)
result.buffer[result.size++] = tmp.buffer[--tmp.size];
}
else
{
result.buffer[result.size++] = (TChar)'0';
}
if (frac)
{
result.buffer[result.size++] = (TChar)'.';
ToStringResult<TChar> tmp = {};
// Ryu conversion
// Add 'one' at beginning to count leading zeroes
unsigned long long frac_whole = (unsigned long long)(frac + (1 << TPrecision)) * TO_STRING_EXPONENT;
while (frac_whole)
{
unsigned long long d1 = frac_whole / 10;
unsigned long long d2 = frac_whole % 10;
tmp.buffer[tmp.size++] = (TChar)'0' + (TChar)d2;
frac_whole = d1;
}
// remove 'one' at beginning
--tmp.size;
unsigned int least_zeroes = 0;
for (unsigned int i = 0; i != tmp.size; ++i)
{
if (tmp.buffer[i] != (TChar)'0')
break;
++least_zeroes;
}
while (tmp.size - least_zeroes)
result.buffer[result.size++] = tmp.buffer[--tmp.size];
}
result.buffer[result.size] = (TChar)'\0';
return result;
}
[[nodiscard]] fnumber32 copy_volatile() const volatile
{
fnumber32 result;
result.num = num;
return result;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment