570 lines
15 KiB
C++
570 lines
15 KiB
C++
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bli
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <cmath>
|
|
|
|
#include "BLI_string_ref.hh"
|
|
#include "BLI_unroll.hh"
|
|
|
|
namespace blender::fixed_width_int {
|
|
|
|
/**
|
|
* An unsigned fixed width integer.
|
|
*
|
|
* For some algorithms, the largest cross platform integer type (`uint64_t`) is not large enough.
|
|
* Then one has the choice to use some big-integer implementation like the one from GMP or one can
|
|
* use fixed-width-integers as implemented here.
|
|
*
|
|
* Internally, this type combines multiple smaller integers into a bigger integer.
|
|
*/
|
|
template<typename T, int S> struct UIntF {
|
|
static_assert(std::is_unsigned_v<T>);
|
|
static_assert(S >= 1);
|
|
|
|
/**
|
|
* Array of smaller integers that make up the bigger integer. The first element is the least
|
|
* significant digit.
|
|
*/
|
|
std::array<T, S> v;
|
|
|
|
/** Allow default construction. Note that the value is not initialized in this case. */
|
|
UIntF() = default;
|
|
|
|
/** Construct from a specific integer. */
|
|
explicit UIntF(uint64_t value);
|
|
|
|
/** Construct from a string. */
|
|
explicit UIntF(StringRefNull str, int base = 10);
|
|
|
|
/** Convert to a normal integer. Note that this may lose digits. */
|
|
explicit operator uint64_t() const;
|
|
|
|
/** Convert to floating point. This may lose precision. */
|
|
explicit operator double() const;
|
|
explicit operator float() const;
|
|
|
|
/* See `BLI_fixed_width_int_str.hh`. */
|
|
#ifdef WITH_GMP
|
|
/** Update value based on the integer encoded in the string. */
|
|
void set_from_str(StringRefNull str, int base = 10);
|
|
|
|
/** Convert to a string. */
|
|
std::string to_string(int base = 10) const;
|
|
#endif
|
|
};
|
|
|
|
/**
|
|
* A signed fixed width integer. It's mostly the same as #UIntF, but signed.
|
|
*/
|
|
template<typename T, int S> struct IntF {
|
|
static_assert(std::is_unsigned_v<T>);
|
|
static_assert(S >= 1);
|
|
|
|
/**
|
|
* Array of smaller integers that make up the bigger integer. The first element is the least
|
|
* significant digit.
|
|
*/
|
|
std::array<T, S> v;
|
|
|
|
/** Allow default construction. Note that the value is not initialized in this case. */
|
|
IntF() = default;
|
|
|
|
/** Construct from a specific integer. */
|
|
explicit IntF(int64_t value);
|
|
|
|
/** Support casting unsigned to signed fixed-width-int. */
|
|
explicit IntF(const UIntF<T, S> &value);
|
|
|
|
/** Construct from a string. */
|
|
explicit IntF(StringRefNull str, int base = 10);
|
|
|
|
/** Convert to a normal integer. Note that this may lose digits. */
|
|
explicit operator int64_t() const;
|
|
|
|
/** Convert to floating point. This may lose precision. */
|
|
explicit operator double() const;
|
|
explicit operator float() const;
|
|
|
|
/** Support casting from signed to unsigned fixed-width-int. */
|
|
explicit operator UIntF<T, S>() const;
|
|
|
|
/* See `BLI_fixed_width_int_str.hh`. */
|
|
#ifdef WITH_GMP
|
|
/** Update value based on the integer encoded in the string. */
|
|
void set_from_str(const StringRefNull str, const int base = 10);
|
|
|
|
/** Convert to a string. */
|
|
std::string to_string(int base = 10) const;
|
|
#endif
|
|
};
|
|
|
|
template<typename T> struct DoubleUIntType {
|
|
using type = void;
|
|
};
|
|
template<> struct DoubleUIntType<uint8_t> {
|
|
using type = uint16_t;
|
|
};
|
|
template<> struct DoubleUIntType<uint16_t> {
|
|
using type = uint32_t;
|
|
};
|
|
template<> struct DoubleUIntType<uint32_t> {
|
|
using type = uint64_t;
|
|
};
|
|
#ifndef _MSC_VER
|
|
template<> struct DoubleUIntType<uint64_t> {
|
|
using type = __uint128_t;
|
|
};
|
|
#endif
|
|
|
|
/** Maps unsigned integer types to a type that's twice the size. E.g. uint16_t to uint32_t. */
|
|
template<typename T> using double_uint_type = typename DoubleUIntType<T>::type;
|
|
|
|
using UInt64_8 = UIntF<uint8_t, 8>;
|
|
using UInt64_16 = UIntF<uint16_t, 4>;
|
|
using UInt64_32 = UIntF<uint32_t, 2>;
|
|
|
|
using Int64_8 = IntF<uint8_t, 8>;
|
|
using Int64_16 = IntF<uint16_t, 4>;
|
|
using Int64_32 = IntF<uint32_t, 2>;
|
|
|
|
using UInt128_8 = UIntF<uint8_t, 16>;
|
|
using UInt128_16 = UIntF<uint16_t, 8>;
|
|
using UInt128_32 = UIntF<uint32_t, 4>;
|
|
using UInt128_64 = UIntF<uint64_t, 2>;
|
|
|
|
using UInt256_8 = UIntF<uint8_t, 32>;
|
|
using UInt256_16 = UIntF<uint16_t, 16>;
|
|
using UInt256_32 = UIntF<uint32_t, 8>;
|
|
using UInt256_64 = UIntF<uint64_t, 4>;
|
|
|
|
using Int128_8 = IntF<uint8_t, 16>;
|
|
using Int128_16 = IntF<uint16_t, 8>;
|
|
using Int128_32 = IntF<uint32_t, 4>;
|
|
using Int128_64 = IntF<uint64_t, 2>;
|
|
|
|
using Int256_8 = IntF<uint8_t, 32>;
|
|
using Int256_16 = IntF<uint16_t, 16>;
|
|
using Int256_32 = IntF<uint32_t, 8>;
|
|
using Int256_64 = IntF<uint64_t, 4>;
|
|
|
|
#ifdef _MSC_VER
|
|
using UInt128 = UInt128_32;
|
|
using UInt256 = UInt256_32;
|
|
using Int128 = Int128_32;
|
|
using Int256 = Int256_32;
|
|
#else
|
|
using UInt128 = UInt128_64;
|
|
using UInt256 = UInt256_64;
|
|
using Int128 = Int128_64;
|
|
using Int256 = Int256_64;
|
|
#endif
|
|
|
|
template<typename T, int S> inline UIntF<T, S>::UIntF(const uint64_t value)
|
|
{
|
|
constexpr int Count = std::min(S, int(sizeof(decltype(value)) / sizeof(T)));
|
|
constexpr int BitsPerT = 8 * sizeof(T);
|
|
|
|
for (int i = 0; i < Count; i++) {
|
|
this->v[i] = T(value >> (BitsPerT * i));
|
|
}
|
|
for (int i = Count; i < S; i++) {
|
|
this->v[i] = 0;
|
|
}
|
|
}
|
|
|
|
template<typename T, int S> inline IntF<T, S>::IntF(const int64_t value)
|
|
{
|
|
constexpr int Count = std::min(S, int(sizeof(decltype(value)) / sizeof(T)));
|
|
constexpr int BitsPerT = 8 * sizeof(T);
|
|
|
|
for (int i = 0; i < Count; i++) {
|
|
this->v[i] = T(value >> (BitsPerT * i));
|
|
}
|
|
const T sign_extend_fill = value < 0 ? T(-1) : T(0);
|
|
for (int i = Count; i < S; i++) {
|
|
this->v[i] = sign_extend_fill;
|
|
}
|
|
}
|
|
|
|
template<typename T, int S> inline IntF<T, S>::IntF(const UIntF<T, S> &value) : v(value.v) {}
|
|
|
|
#ifdef WITH_GMP
|
|
template<typename T, int S> UIntF<T, S>::UIntF(const StringRefNull str, const int base)
|
|
{
|
|
this->set_from_str(str, base);
|
|
}
|
|
|
|
template<typename T, int S> IntF<T, S>::IntF(const StringRefNull str, const int base)
|
|
{
|
|
this->set_from_str(str, base);
|
|
}
|
|
#endif /* WITH_GMP */
|
|
|
|
template<typename T, int S> inline UIntF<T, S>::operator uint64_t() const
|
|
{
|
|
constexpr int Count = std::min(S, int(sizeof(uint64_t) / sizeof(T)));
|
|
constexpr int BitsPerT = 8 * sizeof(T);
|
|
|
|
uint64_t result = 0;
|
|
for (int i = 0; i < Count; i++) {
|
|
result |= uint64_t(this->v[i]) << (BitsPerT * i);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template<typename T, int S> inline UIntF<T, S>::operator double() const
|
|
{
|
|
double result = double(this->v[0]);
|
|
for (int i = 1; i < S; i++) {
|
|
const T a = this->v[i];
|
|
if (a == 0) {
|
|
continue;
|
|
}
|
|
result += ldexp(a, 8 * sizeof(T) * i);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template<typename T, int S> inline UIntF<T, S>::operator float() const
|
|
{
|
|
return float(double(*this));
|
|
}
|
|
|
|
template<typename T, int S> inline IntF<T, S>::operator int64_t() const
|
|
{
|
|
return int64_t(uint64_t(UIntF<T, S>(*this)));
|
|
}
|
|
|
|
template<typename T, int S> inline IntF<T, S>::operator double() const
|
|
{
|
|
if (is_negative(*this)) {
|
|
return -double(-*this);
|
|
}
|
|
double result = double(this->v[0]);
|
|
for (int i = 1; i < S; i++) {
|
|
const T a = this->v[i];
|
|
if (a == 0) {
|
|
continue;
|
|
}
|
|
result += ldexp(a, 8 * sizeof(T) * i);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template<typename T, int S> inline IntF<T, S>::operator float() const
|
|
{
|
|
return float(double(*this));
|
|
}
|
|
|
|
template<typename T, int S> inline IntF<T, S>::operator UIntF<T, S>() const
|
|
{
|
|
UIntF<T, S> result;
|
|
result.v = this->v;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Adds two fixed-width-integer together using the standard addition with carry algorithm taught
|
|
* in schools. The main difference is that the digits here are not 0 to 9, but 0 to max(T).
|
|
*
|
|
* Due to the design of two's-complement numbers, this works for signed and unsigned
|
|
* fixed-width-integer. The overflow behavior is wrap-around.
|
|
*
|
|
* \param T: Type for individual digits.
|
|
* \param T2: Integer type that is twice as large as T.
|
|
* \param S: Number of digits of type T in each fixed-width-integer.
|
|
*/
|
|
template<typename T, typename T2, int S>
|
|
inline void generic_add(T *__restrict dst, const T *a, const T *b)
|
|
{
|
|
constexpr int shift = 8 * sizeof(T);
|
|
T2 carry = 0;
|
|
unroll<S>([&](auto i) {
|
|
const T2 ai = T2(a[i]);
|
|
const T2 bi = T2(b[i]);
|
|
const T2 ri = ai + bi + carry;
|
|
dst[i] = T(ri);
|
|
carry = ri >> shift;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Similar to #generic_add, but for subtraction.
|
|
*/
|
|
template<typename T, typename T2, int S>
|
|
inline void generic_sub(T *__restrict dst, const T *a, const T *b)
|
|
{
|
|
T2 carry = 0;
|
|
unroll<S>([&](auto i) {
|
|
const T2 ai = T2(a[i]);
|
|
const T2 bi = T2(b[i]);
|
|
const T2 ri = ai - bi - carry;
|
|
dst[i] = T(ri);
|
|
carry = ri > ai;
|
|
});
|
|
}
|
|
|
|
/** Similar to #generic_add, but for unsigned multiplication. */
|
|
template<typename T, typename T2, int S>
|
|
inline void generic_unsigned_mul(T *__restrict dst, const T *a, const T *b)
|
|
{
|
|
constexpr int shift = 8 * sizeof(T);
|
|
|
|
T2 r[S] = {};
|
|
|
|
for (int i = 0; i < S; i++) {
|
|
const T2 bi = T2(b[i]);
|
|
T2 carry = 0;
|
|
for (int j = 0; j < S - i; j++) {
|
|
const T2 rji = T2(a[j]) * bi + carry;
|
|
carry = rji >> shift;
|
|
r[i + j] += T2(T(rji));
|
|
}
|
|
}
|
|
|
|
T2 carry = 0;
|
|
for (int i = 0; i < S; i++) {
|
|
const T2 ri = r[i] + carry;
|
|
carry = ri >> shift;
|
|
dst[i] = T(ri);
|
|
}
|
|
}
|
|
|
|
template<typename T, int Size, BLI_ENABLE_IF((!std::is_void_v<double_uint_type<T>>))>
|
|
inline UIntF<T, Size> operator+(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
|
|
{
|
|
UIntF<T, Size> result;
|
|
generic_add<T, double_uint_type<T>, Size>(result.v.data(), a.v.data(), b.v.data());
|
|
return result;
|
|
}
|
|
|
|
template<typename T, int Size, BLI_ENABLE_IF((!std::is_void_v<double_uint_type<T>>))>
|
|
inline IntF<T, Size> operator+(const IntF<T, Size> &a, const IntF<T, Size> &b)
|
|
{
|
|
IntF<T, Size> result;
|
|
generic_add<T, double_uint_type<T>, Size>(result.v.data(), a.v.data(), b.v.data());
|
|
return result;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline UIntF<T, Size> operator-(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
|
|
{
|
|
UIntF<T, Size> result;
|
|
generic_sub<T, double_uint_type<T>, Size>(result.v.data(), a.v.data(), b.v.data());
|
|
return result;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline IntF<T, Size> operator-(const IntF<T, Size> &a, const IntF<T, Size> &b)
|
|
{
|
|
IntF<T, Size> result;
|
|
generic_sub<T, double_uint_type<T>, Size>(result.v.data(), a.v.data(), b.v.data());
|
|
return result;
|
|
}
|
|
|
|
template<typename T, int Size, BLI_ENABLE_IF((!std::is_void_v<double_uint_type<T>>))>
|
|
inline UIntF<T, Size> operator*(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
|
|
{
|
|
UIntF<T, Size> result;
|
|
generic_unsigned_mul<T, double_uint_type<T>, Size>(result.v.data(), a.v.data(), b.v.data());
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Using this function is faster than using the comparison operator. Only a single bit has to be
|
|
* checked to determine if the value is negative.
|
|
*/
|
|
template<typename T, int Size> bool is_negative(const IntF<T, Size> &a)
|
|
{
|
|
return (a.v[Size - 1] & (T(1) << (sizeof(T) * 8 - 1))) != 0;
|
|
}
|
|
|
|
template<typename T, int Size> inline bool is_zero(const UIntF<T, Size> &a)
|
|
{
|
|
bool result = true;
|
|
unroll<Size>([&](auto i) { result &= (a.v[i] == 0); });
|
|
return result;
|
|
}
|
|
|
|
template<typename T, int Size> inline bool is_zero(const IntF<T, Size> &a)
|
|
{
|
|
bool result = true;
|
|
unroll<Size>([&](auto i) { result &= (a.v[i] == 0); });
|
|
return result;
|
|
}
|
|
|
|
template<typename T, int Size, BLI_ENABLE_IF((!std::is_void_v<double_uint_type<T>>))>
|
|
inline IntF<T, Size> operator*(const IntF<T, Size> &a, const IntF<T, Size> &b)
|
|
{
|
|
using UIntF = UIntF<T, Size>;
|
|
using IntF = IntF<T, Size>;
|
|
|
|
/* Signed multiplication is implemented in terms of unsigned multiplication. */
|
|
const bool is_negative_a = is_negative(a);
|
|
const bool is_negative_b = is_negative(b);
|
|
if (is_negative_a && is_negative_b) {
|
|
return IntF(UIntF(-a) * UIntF(-b));
|
|
}
|
|
if (is_negative_a) {
|
|
return -IntF(UIntF(-a) * UIntF(b));
|
|
}
|
|
if (is_negative_b) {
|
|
return -IntF(UIntF(a) * UIntF(-b));
|
|
}
|
|
return IntF(UIntF(a) * UIntF(b));
|
|
}
|
|
|
|
template<typename T, int Size> inline IntF<T, Size> operator-(const IntF<T, Size> &a)
|
|
{
|
|
IntF<T, Size> result;
|
|
for (int i = 0; i < Size; i++) {
|
|
result.v[i] = ~a.v[i];
|
|
}
|
|
return result + IntF<T, Size>(1);
|
|
}
|
|
|
|
template<typename T, int Size> inline void operator+=(UIntF<T, Size> &a, const UIntF<T, Size> &b)
|
|
{
|
|
a = a + b;
|
|
}
|
|
|
|
template<typename T, int Size> inline void operator+=(IntF<T, Size> &a, const IntF<T, Size> &b)
|
|
{
|
|
a = a + b;
|
|
}
|
|
|
|
template<typename T, int Size> inline void operator-=(UIntF<T, Size> &a, const UIntF<T, Size> &b)
|
|
{
|
|
a = a - b;
|
|
}
|
|
|
|
template<typename T, int Size> inline void operator-=(IntF<T, Size> &a, const IntF<T, Size> &b)
|
|
{
|
|
a = a - b;
|
|
}
|
|
|
|
template<typename T, int Size> inline void operator*=(UIntF<T, Size> &a, const UIntF<T, Size> &b)
|
|
{
|
|
a = a * b;
|
|
}
|
|
|
|
template<typename T, int Size> inline void operator*=(IntF<T, Size> &a, const IntF<T, Size> &b)
|
|
{
|
|
a = a * b;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline bool operator==(const IntF<T, Size> &a, const IntF<T, Size> &b)
|
|
{
|
|
return a.v == b.v;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline bool operator==(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
|
|
{
|
|
return a.v == b.v;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline bool operator!=(const IntF<T, Size> &a, const IntF<T, Size> &b)
|
|
{
|
|
return a.v != b.v;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline bool operator!=(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
|
|
{
|
|
return a.v != b.v;
|
|
}
|
|
|
|
template<typename T, size_t Size>
|
|
inline int compare_reversed_order(const std::array<T, Size> &a, const std::array<T, Size> &b)
|
|
{
|
|
for (int i = Size - 1; i >= 0; i--) {
|
|
if (a[i] < b[i]) {
|
|
return -1;
|
|
}
|
|
if (a[i] > b[i]) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline bool operator<(const IntF<T, Size> &a, const IntF<T, Size> &b)
|
|
{
|
|
const bool is_negative_a = is_negative(a);
|
|
const bool is_negative_b = is_negative(b);
|
|
if (is_negative_a == is_negative_b) {
|
|
return compare_reversed_order(a.v, b.v) < 0;
|
|
}
|
|
return is_negative_a;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline bool operator<=(const IntF<T, Size> &a, const IntF<T, Size> &b)
|
|
{
|
|
const bool is_negative_a = is_negative(a);
|
|
const bool is_negative_b = is_negative(b);
|
|
if (is_negative_a == is_negative_b) {
|
|
return compare_reversed_order(a.v, b.v) <= 0;
|
|
}
|
|
return is_negative_a;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline bool operator>(const IntF<T, Size> &a, const IntF<T, Size> &b)
|
|
{
|
|
const bool is_negative_a = is_negative(a);
|
|
const bool is_negative_b = is_negative(b);
|
|
if (is_negative_a == is_negative_b) {
|
|
return compare_reversed_order(a.v, b.v) > 0;
|
|
}
|
|
return is_negative_b;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline bool operator>=(const IntF<T, Size> &a, const IntF<T, Size> &b)
|
|
{
|
|
const bool is_negative_a = is_negative(a);
|
|
const bool is_negative_b = is_negative(b);
|
|
if (is_negative_a == is_negative_b) {
|
|
return compare_reversed_order(a.v, b.v) >= 0;
|
|
}
|
|
return is_negative_b;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline bool operator<(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
|
|
{
|
|
return compare_reversed_order(a.v, b.v) < 0;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline bool operator<=(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
|
|
{
|
|
return compare_reversed_order(a.v, b.v) <= 0;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline bool operator>(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
|
|
{
|
|
return compare_reversed_order(a.v, b.v) > 0;
|
|
}
|
|
|
|
template<typename T, int Size>
|
|
inline bool operator>=(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
|
|
{
|
|
return compare_reversed_order(a.v, b.v) >= 0;
|
|
}
|
|
|
|
} // namespace blender::fixed_width_int
|