MikeLankamp / fpm

C++ header-only fixed-point math library
https://mikelankamp.github.io/fpm
MIT License
672 stars 85 forks source link

Use types larger than 32bit #57

Open GMellar opened 1 year ago

GMellar commented 1 year ago

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?

MikeLankamp commented 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?

peterson79 commented 1 year ago

Does this mean at the moment a Q48.16 is not possible?

MikeLankamp commented 1 year ago

For most common platforms, yes, it's not possible at the moment.

id01 commented 1 year ago

If you're using gcc, you can use __int128_t

SirNate0 commented 11 months ago

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.

AbitTheGray commented 1 month ago

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.

rockingdice commented 3 weeks ago

@AbitTheGray I'm afraid it's not working, there are two issues:

  1. the std::is_signed check for the std::_Signed128 is always false since it's not an arithmetic type.
  2. if you just comment on those checks, it still fails: [error C2679: binary "/" : no operator found which takes a right-hand operand of type 'const IntermediateType' (or there is no acceptable conversion)] on :
    // 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.

MikeLankamp commented 3 weeks ago

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.

rockingdice commented 3 weeks ago

@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.