196 lines
4.6 KiB
C++
196 lines
4.6 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <type_traits>
|
|
|
|
#include "mtl/target.hpp"
|
|
|
|
namespace mtl {
|
|
/**
|
|
* \brief 32-bit Fixed point number
|
|
*
|
|
* Uses a base of 64. ie. the lower 6 bits are after the decimal place,
|
|
* the other 26 bits are before the decimal place.
|
|
*
|
|
* Valid values are in the range ~[-33'554'431.01, 33'554'432.98]
|
|
*
|
|
* Has a maximum error of +/- 1/128 (~0.0078), integers are always
|
|
* exactly represented.
|
|
*
|
|
* \par ARM
|
|
*
|
|
* All functions are compiled in ARM-mode because some operators (notably
|
|
* multiplication and division) are much faster in ARM-mode. For optimal
|
|
* performance, fixed point numbers should only be used in ARM-mode
|
|
* code to enable as much inlining as possible.
|
|
*/
|
|
class fixed {
|
|
private:
|
|
int32_t x;
|
|
|
|
/**
|
|
* \brief Raw constructor
|
|
*
|
|
* Creates a new fixed point number with the raw data of x.
|
|
*
|
|
* \note
|
|
*
|
|
* DO NOT USE DIRECTLY. Use `from_raw` instead.
|
|
*
|
|
* \note
|
|
*
|
|
* DO NOT use to set the fixed number to an integer value, use
|
|
* the public constructor instead.
|
|
*/
|
|
ARM_MODE
|
|
constexpr fixed(int32_t _x, bool) noexcept : x(_x) {}
|
|
|
|
public:
|
|
ARM_MODE
|
|
constexpr fixed() noexcept : x(0) {}
|
|
/**
|
|
* \brief Integer constructor
|
|
*
|
|
* Creates a new fixed point number with the value of the integer.
|
|
* Must be within the range represented by fixed point numbers, see
|
|
* the class description for more detail.
|
|
*/
|
|
template <typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
|
|
ARM_MODE
|
|
constexpr fixed(T _i) noexcept : x(_i * 64) {}
|
|
/**
|
|
* \brief Floating point constructor
|
|
*
|
|
* Creates a new fixed point number with the closest number to
|
|
* the floating point number. Must be within the range represented by
|
|
* fixed point numbers, see the class description for more detail.
|
|
*
|
|
* Must be implemented as a template with enable_if, otherwise passing
|
|
* an int (not int32_t) is ambiguous between the promotion to int32_t and
|
|
* float.
|
|
*/
|
|
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
|
|
ARM_MODE
|
|
constexpr fixed(T _f) noexcept
|
|
// 0.5 offset accounts for truncating to integer, round instead
|
|
: x((_f * 64) + 0.5f) {}
|
|
|
|
/**
|
|
* \brief Raw value factory
|
|
*
|
|
* Creates a new fixed point number with the raw data of x.
|
|
*
|
|
* \note
|
|
*
|
|
* Should not be used unless absolutely needed.
|
|
*/
|
|
ARM_MODE
|
|
static constexpr fixed from_raw(int32_t x) noexcept {
|
|
return fixed(x, true);
|
|
}
|
|
|
|
/**
|
|
* \brief Raw value accessor
|
|
*
|
|
* Gets the raw value of the fixed point number. i.e. The fixed point
|
|
* number multiplied by 64.
|
|
*/
|
|
ARM_MODE
|
|
constexpr int32_t raw() const noexcept {
|
|
return x;
|
|
}
|
|
|
|
/**
|
|
* \brief Fixed point addition
|
|
*
|
|
* Addition with fixed point numbers is the same as with a 32-bit
|
|
* integer, so should be extremely quick.
|
|
*/
|
|
ARM_MODE
|
|
constexpr fixed operator+(fixed rhs) const noexcept {
|
|
return from_raw(x + rhs.x);
|
|
}
|
|
ARM_MODE
|
|
constexpr fixed& operator+=(fixed rhs) noexcept {
|
|
x += rhs.x;
|
|
return *this;
|
|
}
|
|
/**
|
|
* \brief Fixed point subtraction
|
|
*/
|
|
ARM_MODE
|
|
constexpr fixed operator-(fixed rhs) const noexcept {
|
|
return from_raw(x - rhs.x);
|
|
}
|
|
ARM_MODE
|
|
constexpr fixed& operator-=(fixed rhs) noexcept {
|
|
x -= rhs.x;
|
|
return *this;
|
|
}
|
|
ARM_MODE
|
|
constexpr fixed operator-() const noexcept {
|
|
return from_raw(-x);
|
|
}
|
|
|
|
/**
|
|
* \brief Fixed point multiplication
|
|
*/
|
|
ARM_MODE
|
|
constexpr fixed operator*(fixed rhs) const noexcept {
|
|
return from_raw(((int64_t)x * rhs.x) >> 6);
|
|
}
|
|
ARM_MODE
|
|
constexpr fixed& operator*=(fixed rhs) noexcept {
|
|
*this = *this * rhs;
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* \brief Fixed point division
|
|
*
|
|
* Faster for numerators in domain [-0x7FFFF, 0x7FFFF].
|
|
*
|
|
* On attempted division by zero, the result is set to the largest
|
|
* absolute value possible with the same sign as the numerator. This means
|
|
* that if a denominator slowly approaches zero, once it reaches zero
|
|
* the quotient's sign will flip. The largest value is used because fixed
|
|
* point numbers don't have a representation of infinity.
|
|
*
|
|
* \par GBA
|
|
*
|
|
* Placed in IWRAM
|
|
*/
|
|
ARM_MODE GBA_IWRAM
|
|
fixed operator/(fixed rhs) const noexcept;
|
|
ARM_MODE
|
|
fixed& operator/=(fixed rhs) noexcept {
|
|
*this = *this / rhs;
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* \brief Comparison operators
|
|
*/
|
|
constexpr bool operator==(fixed rhs) const noexcept {
|
|
return x == rhs.x;
|
|
}
|
|
constexpr bool operator!=(fixed rhs) const noexcept {
|
|
return x != rhs.x;
|
|
}
|
|
constexpr bool operator>(fixed rhs) const noexcept {
|
|
return x > rhs.x;
|
|
}
|
|
constexpr bool operator>=(fixed rhs) const noexcept {
|
|
return x >= rhs.x;
|
|
}
|
|
constexpr bool operator<(fixed rhs) const noexcept {
|
|
return x < rhs.x;
|
|
}
|
|
constexpr bool operator<=(fixed rhs) const noexcept {
|
|
return x <= rhs.x;
|
|
}
|
|
};
|
|
|
|
} // namespace mtl
|
|
|