Created
October 30, 2024 00:56
-
-
Save kerrytazi/d806705f530f54744d910cbf22c12267 to your computer and use it in GitHub Desktop.
Fixed point number for fast math.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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