Open GMellar opened 1 year ago
Hi @GMellar, at the moment fpm
supports any native integer type as base and intermediate type. However, unless your compiler defines a 128-bit integer type (for fpm
's intermediate type), it's not possible to define a 64-bit fixed-point type.
There is an open request (#8) to support e.g. boost::multiprecision::cpp_int
which would solve this.
Does this answer your question?
Does this mean at the moment a Q48.16 is not possible?
For most common platforms, yes, it's not possible at the moment.
If you're using gcc, you can use __int128_t
For me, I had to define
// in namespace std
template <>
struct is_signed<__int128_t>: std::true_type{};
for it to work. But I was able to use a 64bit fixed point type with that.
For GCC you should be able to use already mentioned __int128_t
For MSVC, there are many comments about "no 128-bit support" but... there is std::_Signed128
from __MSVC_Int128.hpp
(found thanks to https://stackoverflow.com/a/76440171 ).
You may be able to use code like
#if defined(FPM_INT128)
// Already defined
#elif defined(__SIZEOF_INT128__)
using int128_t = __int128_t;
#define FPM_INT128 ::fpm::int128_t
#elif defined(_WIN32) && defined(_MSC_VER)
using int128_t = std::_Signed128;
#define FPM_INT128 ::fpm::int128_t
#else
#warning 128-bit numbers not supported.
#endif
#ifdef FPM_INT128
static_assert(sizeof(FPM_INT128) > sizeof(std::int64_t));
static_assert(std::is_signed<FPM_INT128>::value);
using fixed_56_8 = fixed<std::int64_t, FPM_INT128, 8>;
using fixed_48_16 = fixed<std::int64_t, FPM_INT128, 16>;
using fixed_32_32 = fixed<std::int64_t, FPM_INT128, 32>;
using fixed_16_48 = fixed<std::int64_t, FPM_INT128, 48>;
using fixed_8_56 = fixed<std::int64_t, FPM_INT128, 56>;
#endif
I've put it after "Convenience typedefs" block of fixed.hpp
. But I cannot test it right now as MinGW supports the GCC one and I don't have MSVC at hand.
@AbitTheGray I'm afraid it's not working, there are two issues:
// Explicit conversion to a floating-point type
template <typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
constexpr inline explicit operator T() const noexcept
{
return static_cast<T>(m_value) / FRACTION_MULT;
}
@MikeLankamp from this error, I found the comments in the code:
// Although this value fits in the BaseType in terms of bits, if there's only one integral bit, this value
// is incorrect (flips from positive to negative), so we must extend the size to IntermediateType.
static constexpr IntermediateType FRACTION_MULT = IntermediateType(1) << FractionBits;
But I don't know the meaning of "there's only one integral bit", can I change the type 'IntermediateType' to 'BaseType' if I only use 24:8 or 48:16 fpm numbers? It'll work if change that but I don't know if it will lead to some calculation errors...
Updated: I tested to change the IntermediateType to BaseType and it seems work well.
But I don't know the meaning of "there's only one integral bit"
That comment is relevant e.g. when you use a 1.31 fixed point type with a base type of int32_t
. Then a BaseType FRACTION_MULT
would be 1 << 31
(0x80000000), which is MIN_INT. This messes up the calculations using FRACTION_MULT
.
can I change the type 'IntermediateType' to 'BaseType' if I only use 24:8 or 48:16 fpm numbers?
Yes.
@MikeLankamp got it, thanks for your explanation! For others who want to use 64bit fpm numbers (like 48:16) with the MSVC compiler, you need c++20 to use std::_Signed128 or another workaround:
#include <ranges>
using int128_t = std::ranges::range_difference_t<std::ranges::iota_view<long long, long long>>;
plus you need to comment on the static checks that lead to errors.
I was wondering how to use larger base types for example Q32.16 and use all the multiplication and division routines. Is there any plan to support this?