AK: Make BigIntBase more agnostic to non native word sizes

This will allow us to use it in Crypto::UnsignedBigInteger, which always
uses 32 bit words
This commit is contained in:
Hendiadyoin1 2024-03-17 19:33:41 +01:00 committed by Andrew Kaster
parent 1a312f4265
commit f95abe8c0e
Notes: sideshowbarker 2024-07-17 07:11:12 +09:00
5 changed files with 220 additions and 166 deletions

View file

@ -14,33 +14,51 @@
namespace AK {
namespace Detail {
template<typename T>
struct DoubleWordHelper;
template<>
struct DoubleWordHelper<u32> {
using Type = u64;
using SignedType = i64;
};
template<typename T>
using DoubleWord = typename DoubleWordHelper<T>::Type;
template<typename T>
using SignedDoubleWord = typename DoubleWordHelper<T>::SignedType;
// Ideally, we want to store data in the native processor's words. However, for some algorithms,
// particularly multiplication, we require double of the amount of the native word size.
#if defined(__SIZEOF_INT128__) && defined(AK_ARCH_64_BIT)
template<>
struct DoubleWordHelper<u64> {
using Type = unsigned __int128;
using SignedType = __int128;
};
using NativeWord = u64;
using DoubleWord = unsigned __int128;
using SignedDoubleWord = __int128;
#else
using NativeWord = u32;
using DoubleWord = u64;
using SignedDoubleWord = i64;
#endif
template<bool sign>
using ConditionallySignedDoubleWord = Conditional<sign, SignedDoubleWord, DoubleWord>;
using NativeDoubleWord = DoubleWord<NativeWord>;
using SignedNativeDoubleWord = SignedDoubleWord<NativeWord>;
template<typename WordType, bool sign>
using ConditionallySignedDoubleWord = Conditional<sign, SignedDoubleWord<WordType>, DoubleWord<WordType>>;
template<typename T>
concept BuiltInUFixedInt = OneOf<T, bool, u8, u16, u32, u64, unsigned long, unsigned long long, DoubleWord>;
concept BuiltInUFixedInt = OneOf<T, bool, u8, u16, u32, u64, unsigned long, unsigned long long, NativeDoubleWord>;
template<typename T>
constexpr inline size_t bit_width = sizeof(T) * 8;
constexpr size_t word_size = bit_width<NativeWord>;
constexpr NativeWord max_word = ~static_cast<NativeWord>(0);
static_assert(word_size == 32 || word_size == 64);
constexpr size_t native_word_size = bit_width<NativeWord>;
constexpr NativeWord max_native_word = NumericLimits<NativeWord>::max();
static_assert(native_word_size == 32 || native_word_size == 64);
// Max big integer length is 256 MiB (2.1e9 bits) for 32-bit, 4 GiB (3.4e10 bits) for 64-bit.
constexpr size_t max_big_int_length = 1 << (word_size == 32 ? 26 : 29);
constexpr size_t max_big_int_length = 1 << (native_word_size == 32 ? 26 : 29);
// ===== Static storage for big integers =====
template<typename T, typename WordType = NativeWord>
@ -59,8 +77,8 @@ concept IntegerStorage = requires(T storage, size_t index) {
} -> ConvertibleTo<WordType*>;
};
template<typename T>
concept IntegerReadonlyStorage = IntegerStorage<T, NativeWord const>;
template<typename T, typename WordType = NativeWord>
concept IntegerReadonlyStorage = IntegerStorage<T, WordType const>;
struct NullAllocator {
NativeWord* allocate(size_t) { VERIFY_NOT_REACHED(); }
@ -79,7 +97,7 @@ struct StorageSpan : AK::Span<Word> {
constexpr bool is_negative() const
{
return is_signed && this->last() >> (word_size - 1);
return is_signed && this->last() >> (bit_width<Word> - 1);
}
};
@ -91,8 +109,8 @@ using UnsignedStorageReadonlySpan = StorageSpan<NativeWord const, false>;
// `bit_size`-sized and `ceil(bit_size / word_size) * word_size`-sized `StaticStorage`s will act the
// same.
template<bool is_signed_, size_t bit_size>
requires(bit_size <= max_big_int_length * word_size) struct StaticStorage {
constexpr static size_t static_size = (bit_size + word_size - 1) / word_size;
requires(bit_size <= max_big_int_length * native_word_size) struct StaticStorage {
constexpr static size_t static_size = (bit_size + native_word_size - 1) / native_word_size;
constexpr static bool is_signed = is_signed_;
// We store integers in little-endian regardless of the host endianness. We use two's complement
@ -102,7 +120,7 @@ requires(bit_size <= max_big_int_length * word_size) struct StaticStorage {
constexpr bool is_negative() const
{
return is_signed_ && m_data[static_size - 1] >> (word_size - 1);
return is_signed_ && m_data[static_size - 1] >> (native_word_size - 1);
}
constexpr static size_t size()
@ -152,41 +170,51 @@ constexpr StaticStorage<false, bit_width<T>> get_storage_of(T value)
{
if constexpr (sizeof(T) > sizeof(NativeWord)) {
static_assert(sizeof(T) == 2 * sizeof(NativeWord));
return { static_cast<NativeWord>(value), static_cast<NativeWord>(value >> word_size) };
return { static_cast<NativeWord>(value), static_cast<NativeWord>(value >> native_word_size) };
}
return { static_cast<NativeWord>(value) };
}
// ===== Utilities =====
ALWAYS_INLINE constexpr NativeWord extend_sign(bool sign)
template<typename Word>
ALWAYS_INLINE constexpr Word extend_sign(bool sign)
{
return sign ? max_word : 0;
return sign ? NumericLimits<Word>::max() : 0;
}
// FIXME: If available, we might try to use AVX2 and AVX512.
ALWAYS_INLINE constexpr NativeWord add_words(NativeWord word1, NativeWord word2, bool& carry)
template<typename WordType>
ALWAYS_INLINE constexpr WordType add_words(WordType word1, WordType word2, bool& carry)
{
if (!is_constant_evaluated()) {
#if __has_builtin(__builtin_addc)
NativeWord ncarry, output;
if constexpr (SameAs<NativeWord, unsigned int>)
WordType ncarry, output;
if constexpr (SameAs<WordType, unsigned int>)
output = __builtin_addc(word1, word2, carry, reinterpret_cast<unsigned int*>(&ncarry));
else if constexpr (SameAs<NativeWord, unsigned long>)
else if constexpr (SameAs<WordType, unsigned long>)
output = __builtin_addcl(word1, word2, carry, reinterpret_cast<unsigned long*>(&ncarry));
else if constexpr (SameAs<NativeWord, unsigned long long>)
else if constexpr (SameAs<WordType, unsigned long long>)
output = __builtin_addcll(word1, word2, carry, reinterpret_cast<unsigned long long*>(&ncarry));
else
VERIFY_NOT_REACHED();
carry = ncarry;
return output;
#elif ARCH(X86_64)
unsigned long long output;
carry = __builtin_ia32_addcarryx_u64(carry, word1, word2, &output);
return static_cast<NativeWord>(output);
if constexpr (SameAs<WordType, unsigned int>) {
unsigned int output;
carry = __builtin_ia32_addcarryx_u32(carry, word1, word2, &output);
return output;
} else if constexpr (OneOf<WordType, unsigned long, unsigned long long>) {
unsigned long long output;
carry = __builtin_ia32_addcarryx_u64(carry, word1, word2, &output);
return output;
} else {
VERIFY_NOT_REACHED();
}
#endif
}
// Note: This is usually too confusing for both GCC and Clang.
NativeWord output;
WordType output;
bool ncarry = __builtin_add_overflow(word1, word2, &output);
if (carry) {
++output;
@ -197,28 +225,38 @@ ALWAYS_INLINE constexpr NativeWord add_words(NativeWord word1, NativeWord word2,
return output;
}
ALWAYS_INLINE constexpr NativeWord sub_words(NativeWord word1, NativeWord word2, bool& carry)
template<typename WordType>
ALWAYS_INLINE constexpr WordType sub_words(WordType word1, WordType word2, bool& carry)
{
if (!is_constant_evaluated()) {
#if __has_builtin(__builtin_subc) && !defined(AK_BUILTIN_SUBC_BROKEN)
NativeWord ncarry, output;
if constexpr (SameAs<NativeWord, unsigned int>)
WordType ncarry, output;
if constexpr (SameAs<WordType, unsigned int>)
output = __builtin_subc(word1, word2, carry, reinterpret_cast<unsigned int*>(&ncarry));
else if constexpr (SameAs<NativeWord, unsigned long>)
else if constexpr (SameAs<WordType, unsigned long>)
output = __builtin_subcl(word1, word2, carry, reinterpret_cast<unsigned long*>(&ncarry));
else if constexpr (SameAs<NativeWord, unsigned long long>)
else if constexpr (SameAs<WordType, unsigned long long>)
output = __builtin_subcll(word1, word2, carry, reinterpret_cast<unsigned long long*>(&ncarry));
else
VERIFY_NOT_REACHED();
carry = ncarry;
return output;
#elif ARCH(X86_64) && defined(AK_COMPILER_GCC)
unsigned long long output;
carry = __builtin_ia32_sbb_u64(carry, word1, word2, &output);
return static_cast<NativeWord>(output);
if constexpr (SameAs<WordType, unsigned int>) {
unsigned int output;
carry = __builtin_ia32_sbb_u32(carry, word1, word2, &output);
return output;
} else if constexpr (OneOf<WordType, unsigned long, unsigned long long>) {
unsigned long long output;
carry = __builtin_ia32_sbb_u64(carry, word1, word2, &output);
return output;
} else {
VERIFY_NOT_REACHED();
}
#endif
}
NativeWord output;
// Note: This is usually too confusing for both GCC and Clang.
WordType output;
bool ncarry = __builtin_sub_overflow(word1, word2, &output);
if (carry) {
if (output == 0)
@ -229,30 +267,41 @@ ALWAYS_INLINE constexpr NativeWord sub_words(NativeWord word1, NativeWord word2,
return output;
}
// Calculate ((dividend1 << word_size) + dividend0) / divisor. Quotient should be guaranteed to fit
// into NativeWord.
ALWAYS_INLINE constexpr NativeWord div_mod_words(NativeWord dividend0, NativeWord dividend1, NativeWord divisor, NativeWord& remainder)
template<typename WordType>
constexpr DoubleWord<WordType> dword(WordType low, WordType high)
{
auto dividend = (static_cast<DoubleWord>(dividend1) << word_size) + dividend0;
remainder = static_cast<NativeWord>(dividend % divisor);
return static_cast<NativeWord>(dividend / divisor);
return (static_cast<DoubleWord<WordType>>(high) << bit_width<WordType>) | low;
}
// Calculate ((dividend_high << word_size) + dividend_low) / divisor. Quotient should be guaranteed to fit
// into WordType.
template<typename WordType>
ALWAYS_INLINE constexpr WordType div_mod_words(WordType dividend_low, WordType dividend_high, WordType divisor, WordType& remainder)
{
auto dividend = dword(dividend_low, dividend_high);
remainder = static_cast<WordType>(dividend % divisor);
return static_cast<WordType>(dividend / divisor);
}
// ===== Operations on integer storages =====
// Naming scheme for variables belonging to one of the operands or the result is as follows:
// trailing digit in a name is 1 if a variable belongs to `operand1` (or the only `operand`), 2 --
// for `operand2` and no trailing digit -- for `result`.
template<typename WordType = NativeWord>
struct StorageOperations {
static constexpr void copy(IntegerReadonlyStorage auto const& operand, IntegerStorage auto&& result, size_t offset = 0)
static constexpr size_t word_size = bit_width<WordType>;
using DoubleWordType = DoubleWord<WordType>;
static constexpr void copy(IntegerReadonlyStorage<WordType> auto const& operand, IntegerStorage<WordType> auto&& result, size_t offset = 0)
{
auto fill = extend_sign(operand.is_negative());
auto fill = extend_sign<WordType>(operand.is_negative());
size_t size1 = operand.size(), size = result.size();
for (size_t i = 0; i < size; ++i)
result[i] = i + offset < size1 ? operand[i + offset] : fill;
}
static constexpr void set(NativeWord value, auto&& result)
static constexpr void set(WordType value, auto&& result)
{
result[0] = value;
for (size_t i = 1; i < result.size(); ++i)
@ -260,7 +309,7 @@ struct StorageOperations {
}
// `is_for_inequality' is a hint to compiler that we do not need to differentiate between < and >.
static constexpr int compare(IntegerReadonlyStorage auto const& operand1, IntegerReadonlyStorage auto const& operand2, bool is_for_inequality)
static constexpr int compare(IntegerReadonlyStorage<WordType> auto const& operand1, IntegerReadonlyStorage<WordType> auto const& operand2, bool is_for_inequality)
{
bool sign1 = operand1.is_negative(), sign2 = operand2.is_negative();
size_t size1 = operand1.size(), size2 = operand2.size();
@ -271,7 +320,7 @@ struct StorageOperations {
return 1;
}
NativeWord compare_value = extend_sign(sign1);
WordType compare_value = extend_sign<WordType>(sign1);
bool differ_in_high_bits = false;
if (size1 > size2) {
@ -317,7 +366,7 @@ struct StorageOperations {
// - !operand1.is_signed && !operand2.is_signed && !result.is_signed (the function will also work
// for signed storages but will extend them with zeroes regardless of the actual sign).
template<Bitwise operation>
static constexpr void compute_bitwise(IntegerReadonlyStorage auto const& operand1, IntegerReadonlyStorage auto const& operand2, IntegerStorage auto&& result)
static constexpr void compute_bitwise(IntegerReadonlyStorage<WordType> auto const& operand1, IntegerReadonlyStorage<WordType> auto const& operand2, IntegerStorage<WordType> auto&& result)
{
size_t size1 = operand1.size(), size2 = operand2.size(), size = result.size();
@ -345,7 +394,7 @@ struct StorageOperations {
// to then easily generate most of the operators via defines). That is why we have unused
// first operand here.
template<Bitwise operation>
static constexpr void compute_inplace_bitwise(IntegerReadonlyStorage auto const&, IntegerReadonlyStorage auto const& operand2, IntegerStorage auto&& result)
static constexpr void compute_inplace_bitwise(IntegerReadonlyStorage<WordType> auto const&, IntegerReadonlyStorage<WordType> auto const& operand2, IntegerStorage<WordType> auto&& result)
{
size_t min_size = min(result.size(), operand2.size());
@ -364,7 +413,7 @@ struct StorageOperations {
// Requirements for the next two functions:
// - shift < result.size() * word_size;
// - result.size() == operand.size().
static constexpr void shift_left(IntegerReadonlyStorage auto const& operand, size_t shift, IntegerStorage auto&& result)
static constexpr void shift_left(IntegerReadonlyStorage<WordType> auto const& operand, size_t shift, IntegerStorage<WordType> auto&& result)
{
size_t size = operand.size();
size_t offset = shift / word_size, remainder = shift % word_size;
@ -383,7 +432,7 @@ struct StorageOperations {
}
}
static constexpr void shift_right(IntegerReadonlyStorage auto const& operand, size_t shift, IntegerStorage auto&& result)
static constexpr void shift_right(IntegerReadonlyStorage<WordType> auto const& operand, size_t shift, IntegerStorage<WordType> auto&& result)
{
size_t size = operand.size();
size_t offset = shift / word_size, remainder = shift % word_size;
@ -412,10 +461,10 @@ struct StorageOperations {
// a + b * (-1) ** subtract = c + r * 2 ** (result.size() * word_size).
// In particular, r equals 0 iff no overflow has happened.
template<bool subtract>
static constexpr int add(IntegerReadonlyStorage auto const& operand1, IntegerReadonlyStorage auto const& operand2, IntegerStorage auto&& result, bool carry = false)
static constexpr int add(IntegerReadonlyStorage<WordType> auto const& operand1, IntegerReadonlyStorage<WordType> auto const& operand2, IntegerStorage<WordType> auto&& result, bool carry = false)
{
bool sign1 = operand1.is_negative(), sign2 = operand2.is_negative();
auto fill1 = extend_sign(sign1), fill2 = extend_sign(sign2);
auto fill1 = extend_sign<WordType>(sign1), fill2 = extend_sign<WordType>(sign2);
size_t size1 = operand1.size(), size2 = operand2.size(), size = result.size();
for (size_t i = 0; i < size; ++i) {
@ -436,7 +485,7 @@ struct StorageOperations {
// See `storage_add` for the meaning of the return value.
template<bool subtract>
static constexpr int increment(IntegerStorage auto&& operand)
static constexpr int increment(IntegerStorage<WordType> auto&& operand)
{
bool carry = true;
bool sign = operand.is_negative();
@ -444,9 +493,9 @@ struct StorageOperations {
for (size_t i = 0; i < size; ++i) {
if constexpr (!subtract)
operand[i] = add_words(operand[i], 0, carry);
operand[i] = add_words<WordType>(operand[i], 0, carry);
else
operand[i] = sub_words(operand[i], 0, carry);
operand[i] = sub_words<WordType>(operand[i], 0, carry);
}
if constexpr (!subtract)
@ -459,33 +508,33 @@ struct StorageOperations {
// - result.size() == operand.size().
//
// Return value: operand != 0.
static constexpr bool negate(IntegerReadonlyStorage auto const& operand, IntegerStorage auto&& result)
static constexpr bool negate(IntegerReadonlyStorage<WordType> auto const& operand, IntegerStorage<WordType> auto&& result)
{
bool carry = false;
size_t size = operand.size();
for (size_t i = 0; i < size; ++i)
result[i] = sub_words(0, operand[i], carry);
result[i] = sub_words<WordType>(0, operand[i], carry);
return carry;
}
// No allocations will occur if both operands are unsigned.
template<IntegerReadonlyStorage Operand1, IntegerReadonlyStorage Operand2>
static constexpr void baseline_mul(Operand1 const& operand1, Operand2 const& operand2, IntegerStorage auto&& __restrict__ result, auto&& buffer)
template<IntegerReadonlyStorage<WordType> Operand1, IntegerReadonlyStorage<WordType> Operand2>
static constexpr void baseline_mul(Operand1 const& operand1, Operand2 const& operand2, IntegerStorage<WordType> auto&& __restrict__ result, auto&& buffer)
{
bool sign1 = operand1.is_negative(), sign2 = operand2.is_negative();
size_t size1 = operand1.size(), size2 = operand2.size(), size = result.size();
if (size1 == 1 && size2 == 1) {
// We do not want to compete with the cleverness of the compiler of multiplying NativeWords.
ConditionallySignedDoubleWord<Operand1::is_signed> word1 = operand1[0];
ConditionallySignedDoubleWord<Operand2::is_signed> word2 = operand2[0];
auto value = static_cast<DoubleWord>(word1 * word2);
ConditionallySignedDoubleWord<WordType, Operand1::is_signed> word1 = operand1[0];
ConditionallySignedDoubleWord<WordType, Operand2::is_signed> word2 = operand2[0];
auto value = static_cast<DoubleWordType>(word1 * word2);
result[0] = value;
if (size > 1) {
result[1] = value >> word_size;
auto fill = extend_sign(sign1 ^ sign2);
auto fill = extend_sign<WordType>(sign1 ^ sign2);
for (size_t i = 2; i < result.size(); ++i)
result[i] = fill;
}
@ -503,31 +552,31 @@ struct StorageOperations {
if (size2 < size) {
if (sign1) {
auto inverted = buffer.allocate(size1);
negate(operand1, UnsignedStorageSpan { inverted, size1 });
negate(operand1, StorageSpan<WordType, false> { inverted, size1 });
data1 = inverted;
}
if (sign2) {
auto inverted = buffer.allocate(size2);
negate(operand2, UnsignedStorageSpan { inverted, size2 });
negate(operand2, StorageSpan<WordType, false> { inverted, size2 });
data2 = inverted;
}
}
size1 = min(size1, size), size2 = min(size2, size);
// Do schoolbook O(size1 * size2).
DoubleWord carry = 0;
DoubleWordType carry = 0;
for (size_t i = 0; i < size; ++i) {
result[i] = static_cast<NativeWord>(carry);
result[i] = static_cast<WordType>(carry);
carry >>= word_size;
size_t start_index = i >= size2 ? i - size2 + 1 : 0;
size_t end_index = min(i + 1, size1);
for (size_t j = start_index; j < end_index; ++j) {
auto x = static_cast<DoubleWord>(data1[j]) * data2[i - j];
auto x = static_cast<DoubleWordType>(data1[j]) * data2[i - j];
bool ncarry = false;
result[i] = add_words(result[i], static_cast<NativeWord>(x), ncarry);
result[i] = add_words(result[i], static_cast<WordType>(x), ncarry);
carry += (x >> word_size) + ncarry;
}
}
@ -536,9 +585,10 @@ struct StorageOperations {
negate(result, result);
}
};
}
using Detail::StorageOperations, Detail::NativeWord, Detail::word_size, Detail::max_word,
using Detail::StorageOperations, Detail::NativeWord, Detail::native_word_size, Detail::max_native_word,
Detail::UnsignedStorageSpan, Detail::UnsignedStorageReadonlySpan;
inline Detail::NullAllocator g_null_allocator;

View file

@ -1367,11 +1367,13 @@ static FloatingPointBuilder binary_to_decimal(u64 mantissa, i64 exponent)
}
class MinimalBigInt {
using Ops = StorageOperations<>;
public:
MinimalBigInt() = default;
MinimalBigInt(u64 value)
{
StorageOperations::copy(Detail::get_storage_of(value), get_storage(words_in_u64));
Ops::copy(Detail::get_storage_of(value), get_storage(words_in_u64));
}
static MinimalBigInt from_decimal_floating_point(BasicParseResult const& parse_result, size_t& digits_parsed, size_t max_total_digits)
@ -1492,8 +1494,8 @@ public:
u64 top_u64 = 0;
for (size_t i = 0; i < m_used_length; ++i) {
size_t word_start = i * word_size;
size_t word_end = word_start + word_size;
size_t word_start = i * native_word_size;
size_t word_end = word_start + native_word_size;
if (top_u64_start < word_end) {
if (top_u64_start >= word_start) {
@ -1516,7 +1518,7 @@ public:
if (m_used_length == 0)
return 0;
// This is guaranteed to be at most max_words_needed * word_size so not above i32 max
return static_cast<i32>(word_size * m_used_length) - count_leading_zeroes(m_words[m_used_length - 1]);
return static_cast<i32>(native_word_size * m_used_length) - count_leading_zeroes(m_words[m_used_length - 1]);
}
void multiply_with_power_of_10(u32 exponent)
@ -1646,7 +1648,7 @@ public:
multiply_with_small(power_of_5[i][0]);
} else {
auto copy = *this;
StorageOperations::baseline_mul(copy.get_storage(), power_of_5[i],
Ops::baseline_mul(copy.get_storage(), power_of_5[i],
get_storage(m_used_length + power_of_5[i].size()), g_null_allocator);
trim_last_word_if_zero();
}
@ -1657,12 +1659,12 @@ public:
void multiply_with_power_of_2(u32 exponent)
{
if (exponent) {
size_t max_new_length = m_used_length + (exponent + word_size - 1) / word_size;
size_t max_new_length = m_used_length + (exponent + native_word_size - 1) / native_word_size;
if (m_used_length != max_words_needed)
m_words[m_used_length] = 0;
auto storage = get_storage(max_new_length);
StorageOperations::shift_left(storage, exponent, storage);
Ops::shift_left(storage, exponent, storage);
trim_last_word_if_zero();
}
}
@ -1675,7 +1677,7 @@ public:
CompareResult compare_to(MinimalBigInt const& other) const
{
return static_cast<CompareResult>(StorageOperations::compare(get_storage(), other.get_storage(), false));
return static_cast<CompareResult>(Ops::compare(get_storage(), other.get_storage(), false));
}
private:
@ -1706,11 +1708,11 @@ private:
void multiply_with_small(u64 value)
{
if (value <= max_word) {
if (value <= max_native_word) {
auto native_value = static_cast<NativeWord>(value);
NativeWord carry = 0;
for (size_t i = 0; i < m_used_length; ++i) {
auto result = UFixedBigInt<word_size>(m_words[i]).wide_multiply(native_value) + carry;
auto result = UFixedBigInt<native_word_size>(m_words[i]).wide_multiply(native_value) + carry;
carry = result.high();
m_words[i] = result.low();
}
@ -1719,21 +1721,21 @@ private:
} else {
// word_size == 32 && value > NumericLimits<u32>::max()
auto copy = *this;
StorageOperations::baseline_mul(copy.get_storage(), Detail::get_storage_of(value), get_storage(m_used_length + 2), g_null_allocator);
Ops::baseline_mul(copy.get_storage(), Detail::get_storage_of(value), get_storage(m_used_length + 2), g_null_allocator);
trim_last_word_if_zero();
}
}
void add_small(u64 value)
{
if (m_used_length == 0 && value <= max_word) {
if (m_used_length == 0 && value <= max_native_word) {
m_words[m_used_length++] = static_cast<NativeWord>(value);
return;
}
auto initial_storage = get_storage();
auto expanded_storage = get_storage(max(m_used_length, words_in_u64));
if (StorageOperations::add<false>(initial_storage, Detail::get_storage_of(value), expanded_storage))
if (Ops::add<false>(initial_storage, Detail::get_storage_of(value), expanded_storage))
m_words[m_used_length++] = 1;
}
@ -1746,7 +1748,7 @@ private:
}
// The max valid words we might need are log2(10^(769 + 342)), max digits + max exponent
static constexpr size_t words_in_u64 = word_size == 64 ? 1 : 2;
static constexpr size_t words_in_u64 = native_word_size == 64 ? 1 : 2;
static constexpr size_t max_words_needed = 58 * words_in_u64;
size_t m_used_length = 0;

View file

@ -58,7 +58,7 @@ constexpr auto& get_storage_of(UFixedBigInt<bit_size> const& value) { return val
template<typename Operand1, typename Operand2, typename Result>
constexpr void mul_internal(Operand1 const& operand1, Operand2 const& operand2, Result& result)
{
StorageOperations::baseline_mul(operand1, operand2, result, g_null_allocator);
StorageOperations<>::baseline_mul(operand1, operand2, result, g_null_allocator);
}
template<size_t dividend_size, size_t divisor_size, bool restore_remainder>
@ -72,27 +72,28 @@ template<size_t bit_size, typename Storage>
class UFixedBigInt {
constexpr static size_t static_size = Storage::static_size;
constexpr static size_t part_size = static_size / 2;
using UFixedBigIntPart = Conditional<part_size * word_size <= 64, u64, UFixedBigInt<part_size * word_size>>;
using UFixedBigIntPart = Conditional<part_size * native_word_size <= 64, u64, UFixedBigInt<part_size * native_word_size>>;
using Ops = StorageOperations<>;
public:
constexpr UFixedBigInt() = default;
explicit constexpr UFixedBigInt(IntegerWrapper value) { StorageOperations::copy(value.m_data, m_data); }
explicit constexpr UFixedBigInt(IntegerWrapper value) { Ops::copy(value.m_data, m_data); }
consteval UFixedBigInt(int value)
{
StorageOperations::copy(IntegerWrapper(value).m_data, m_data);
Ops::copy(IntegerWrapper(value).m_data, m_data);
}
template<UFixedInt T>
requires(sizeof(T) > sizeof(Storage)) explicit constexpr UFixedBigInt(T const& value)
{
StorageOperations::copy(get_storage_of(value), m_data);
Ops::copy(get_storage_of(value), m_data);
}
template<UFixedInt T>
requires(sizeof(T) <= sizeof(Storage)) constexpr UFixedBigInt(T const& value)
{
StorageOperations::copy(get_storage_of(value), m_data);
Ops::copy(get_storage_of(value), m_data);
}
constexpr UFixedBigInt(UFixedBigIntPart const& low, UFixedBigIntPart const& high)
@ -112,21 +113,21 @@ public:
size_t offset = 0;
for (size_t i = 0; i < n; ++i) {
if (offset % word_size == 0) {
if (offset % native_word_size == 0) {
// Aligned initialization (i. e. u256 from two u128)
decltype(auto) storage = get_storage_of(value[i]);
for (size_t i = 0; i < storage.size(); ++i)
m_data[i + offset / word_size] = storage[i];
} else if (offset % word_size == 32 && IsSame<T, u32>) {
m_data[i + offset / native_word_size] = storage[i];
} else if (offset % native_word_size == 32 && IsSame<T, u32>) {
// u32 vector initialization on 64-bit platforms
m_data[offset / word_size] |= static_cast<DoubleWord>(value[i]) << 32;
m_data[offset / native_word_size] |= static_cast<NativeDoubleWord>(value[i]) << 32;
} else {
VERIFY_NOT_REACHED();
}
offset += assumed_bit_size<T>;
}
for (size_t i = (offset + word_size - 1) / word_size; i < m_data.size(); ++i)
for (size_t i = (offset + native_word_size - 1) / native_word_size; i < m_data.size(); ++i)
m_data[i] = 0;
}
@ -135,7 +136,7 @@ public:
constexpr explicit operator T() const
{
T result;
StorageOperations::copy(m_data, result.m_data);
Ops::copy(m_data, result.m_data);
return result;
}
@ -146,9 +147,9 @@ public:
}
template<BuiltInUFixedInt T>
requires(sizeof(T) == sizeof(DoubleWord)) constexpr explicit operator T() const
requires(sizeof(T) == sizeof(NativeDoubleWord)) constexpr explicit operator T() const
{
return (static_cast<DoubleWord>(m_data[1]) << word_size) + m_data[0];
return (static_cast<NativeDoubleWord>(m_data[1]) << native_word_size) + m_data[0];
}
constexpr UFixedBigIntPart low() const
@ -156,11 +157,11 @@ public:
{
if constexpr (part_size == 1) {
return m_data[0];
} else if constexpr (IsSame<UFixedBigIntPart, DoubleWord>) {
return m_data[0] + (static_cast<DoubleWord>(m_data[1]) << word_size);
} else if constexpr (IsSame<UFixedBigIntPart, NativeDoubleWord>) {
return m_data[0] + (static_cast<NativeDoubleWord>(m_data[1]) << native_word_size);
} else {
UFixedBigInt<part_size * word_size> result;
StorageOperations::copy(m_data, result.m_data);
UFixedBigInt<part_size * native_word_size> result;
Ops::copy(m_data, result.m_data);
return result;
}
}
@ -170,11 +171,11 @@ public:
{
if constexpr (part_size == 1) {
return m_data[part_size];
} else if constexpr (IsSame<UFixedBigIntPart, DoubleWord>) {
return m_data[part_size] + (static_cast<DoubleWord>(m_data[part_size + 1]) << word_size);
} else if constexpr (IsSame<UFixedBigIntPart, NativeDoubleWord>) {
return m_data[part_size] + (static_cast<NativeDoubleWord>(m_data[part_size + 1]) << native_word_size);
} else {
UFixedBigInt<part_size * word_size> result;
StorageOperations::copy(m_data, result.m_data, part_size);
UFixedBigInt<part_size * native_word_size> result;
Ops::copy(m_data, result.m_data, part_size);
return result;
}
}
@ -216,7 +217,7 @@ public:
result += count_trailing_zeroes(m_data[i]);
break;
} else {
result += word_size;
result += native_word_size;
}
}
return result;
@ -230,10 +231,10 @@ public:
result += count_leading_zeroes(m_data[i]);
break;
} else {
result += word_size;
result += native_word_size;
}
}
return result + bit_size - word_size * static_size;
return result + bit_size - native_word_size * static_size;
}
// Comparisons
@ -255,22 +256,22 @@ public:
constexpr bool operator==(UFixedInt auto const& other) const
{
return StorageOperations::compare(m_data, get_storage_of(other), true) == 0;
return Ops::compare(m_data, get_storage_of(other), true) == 0;
}
constexpr bool operator==(IntegerWrapper other) const
{
return StorageOperations::compare(m_data, get_storage_of(other), true) == 0;
return Ops::compare(m_data, get_storage_of(other), true) == 0;
}
constexpr int operator<=>(UFixedInt auto const& other) const
{
return StorageOperations::compare(m_data, get_storage_of(other), false);
return Ops::compare(m_data, get_storage_of(other), false);
}
constexpr int operator<=>(IntegerWrapper other) const
{
return StorageOperations::compare(m_data, get_storage_of(other), false);
return Ops::compare(m_data, get_storage_of(other), false);
}
#define DEFINE_STANDARD_BINARY_OPERATOR(op, function) \
@ -302,43 +303,43 @@ public:
}
// Binary operators
DEFINE_STANDARD_BINARY_OPERATOR(^, StorageOperations::compute_bitwise<StorageOperations::Bitwise::XOR>)
DEFINE_STANDARD_BINARY_OPERATOR(&, StorageOperations::compute_bitwise<StorageOperations::Bitwise::AND>)
DEFINE_STANDARD_BINARY_OPERATOR(|, StorageOperations::compute_bitwise<StorageOperations::Bitwise::OR>)
DEFINE_STANDARD_COMPOUND_ASSIGNMENT(^=, StorageOperations::compute_inplace_bitwise<StorageOperations::Bitwise::XOR>)
DEFINE_STANDARD_COMPOUND_ASSIGNMENT(&=, StorageOperations::compute_inplace_bitwise<StorageOperations::Bitwise::AND>)
DEFINE_STANDARD_COMPOUND_ASSIGNMENT(|=, StorageOperations::compute_inplace_bitwise<StorageOperations::Bitwise::OR>)
DEFINE_STANDARD_BINARY_OPERATOR(^, Ops::compute_bitwise<Ops::Bitwise::XOR>)
DEFINE_STANDARD_BINARY_OPERATOR(&, Ops::compute_bitwise<Ops::Bitwise::AND>)
DEFINE_STANDARD_BINARY_OPERATOR(|, Ops::compute_bitwise<Ops::Bitwise::OR>)
DEFINE_STANDARD_COMPOUND_ASSIGNMENT(^=, Ops::compute_inplace_bitwise<Ops::Bitwise::XOR>)
DEFINE_STANDARD_COMPOUND_ASSIGNMENT(&=, Ops::compute_inplace_bitwise<Ops::Bitwise::AND>)
DEFINE_STANDARD_COMPOUND_ASSIGNMENT(|=, Ops::compute_inplace_bitwise<Ops::Bitwise::OR>)
constexpr auto operator~() const
{
UFixedBigInt<bit_size> result;
StorageOperations::compute_bitwise<StorageOperations::Bitwise::INVERT>(m_data, m_data, result.m_data);
Ops::compute_bitwise<Ops::Bitwise::INVERT>(m_data, m_data, result.m_data);
return result;
}
constexpr auto operator<<(size_t shift) const
{
UFixedBigInt<bit_size> result;
StorageOperations::shift_left(m_data, shift, result.m_data);
Ops::shift_left(m_data, shift, result.m_data);
return result;
}
constexpr auto& operator<<=(size_t shift)
{
StorageOperations::shift_left(m_data, shift, m_data);
Ops::shift_left(m_data, shift, m_data);
return *this;
}
constexpr auto operator>>(size_t shift) const
{
UFixedBigInt<bit_size> result;
StorageOperations::shift_right(m_data, shift, result.m_data);
Ops::shift_right(m_data, shift, result.m_data);
return result;
}
constexpr auto& operator>>=(size_t shift)
{
StorageOperations::shift_right(m_data, shift, m_data);
Ops::shift_right(m_data, shift, m_data);
return *this;
}
@ -347,7 +348,7 @@ public:
constexpr auto addc(T const& other, bool& carry) const
{
UFixedBigInt<max(bit_size, assumed_bit_size<T>)> result;
carry = StorageOperations::add<false>(m_data, get_storage_of(other), result.m_data, carry);
carry = Ops::add<false>(m_data, get_storage_of(other), result.m_data, carry);
return result;
}
@ -355,38 +356,38 @@ public:
constexpr auto subc(T const& other, bool& borrow) const
{
UFixedBigInt<max(bit_size, assumed_bit_size<T>)> result;
borrow = StorageOperations::add<true>(m_data, get_storage_of(other), result.m_data, borrow);
borrow = Ops::add<true>(m_data, get_storage_of(other), result.m_data, borrow);
return result;
}
DEFINE_STANDARD_BINARY_OPERATOR(+, StorageOperations::add<false>)
DEFINE_STANDARD_BINARY_OPERATOR(-, StorageOperations::add<true>)
DEFINE_STANDARD_COMPOUND_ASSIGNMENT(+=, StorageOperations::add<false>)
DEFINE_STANDARD_COMPOUND_ASSIGNMENT(-=, StorageOperations::add<true>)
DEFINE_STANDARD_BINARY_OPERATOR(+, Ops::add<false>)
DEFINE_STANDARD_BINARY_OPERATOR(-, Ops::add<true>)
DEFINE_STANDARD_COMPOUND_ASSIGNMENT(+=, Ops::add<false>)
DEFINE_STANDARD_COMPOUND_ASSIGNMENT(-=, Ops::add<true>)
constexpr auto& operator++()
{
StorageOperations::increment<false>(m_data);
Ops::increment<false>(m_data);
return *this;
}
constexpr auto& operator--()
{
StorageOperations::increment<true>(m_data);
Ops::increment<true>(m_data);
return *this;
}
constexpr auto operator++(int)
{
UFixedBigInt<bit_size> result = *this;
StorageOperations::increment<false>(m_data);
Ops::increment<false>(m_data);
return result;
}
constexpr auto operator--(int)
{
UFixedBigInt<bit_size> result = *this;
StorageOperations::increment<true>(m_data);
Ops::increment<true>(m_data);
return result;
}

View file

@ -9,8 +9,7 @@
#include <AK/Diagnostics.h>
#include <AK/UFixedBigInt.h>
namespace AK {
namespace Detail {
namespace AK::Detail {
template<size_t dividend_bit_size, size_t divisor_bit_size, bool restore_remainder>
constexpr void div_mod_internal(
@ -19,6 +18,8 @@ constexpr void div_mod_internal(
StaticStorage<false, dividend_bit_size>& quotient,
StaticStorage<false, divisor_bit_size>& remainder)
{
using Ops = StorageOperations<>;
size_t dividend_len = operand1.size(), divisor_len = operand2.size();
while (divisor_len > 0 && !operand2[divisor_len - 1])
--divisor_len;
@ -32,30 +33,30 @@ constexpr void div_mod_internal(
if (divisor_len == 1 && operand2[0] == 1) { // divisor == 1
quotient = operand1;
if constexpr (restore_remainder)
StorageOperations::set(0, remainder);
Ops::set(0, remainder);
return;
}
if (dividend_len < divisor_len) { // dividend < divisor
StorageOperations::set(0, quotient);
Ops::set(0, quotient);
if constexpr (restore_remainder)
StorageOperations::copy(operand1, remainder);
Ops::copy(operand1, remainder);
return;
}
if (divisor_len == 1 && dividend_len == 1) { // NativeWord / NativeWord
StorageOperations::set(operand1[0] / operand2[0], quotient);
Ops::set(operand1[0] / operand2[0], quotient);
if constexpr (restore_remainder)
StorageOperations::set(operand1[0] % operand2[0], remainder);
Ops::set(operand1[0] % operand2[0], remainder);
return;
}
if (divisor_len == 1) { // BigInt by NativeWord
auto u = (static_cast<DoubleWord>(operand1[dividend_len - 1]) << word_size) + operand1[dividend_len - 2];
auto u = (static_cast<NativeDoubleWord>(operand1[dividend_len - 1]) << native_word_size) + operand1[dividend_len - 2];
auto divisor = operand2[0];
auto top = u / divisor;
quotient[dividend_len - 1] = static_cast<NativeWord>(top >> word_size);
quotient[dividend_len - 1] = static_cast<NativeWord>(top >> native_word_size);
quotient[dividend_len - 2] = static_cast<NativeWord>(top);
auto carry = static_cast<NativeWord>(u % divisor);
@ -64,13 +65,13 @@ constexpr void div_mod_internal(
for (size_t i = dividend_len; i < quotient.size(); ++i)
quotient[i] = 0;
if constexpr (restore_remainder)
StorageOperations::set(carry, remainder);
Ops::set(carry, remainder);
return;
}
// Knuth's algorithm D
StaticStorage<false, dividend_bit_size + word_size> dividend;
StorageOperations::copy(operand1, dividend);
StaticStorage<false, dividend_bit_size + native_word_size> dividend;
Ops::copy(operand1, dividend);
auto divisor = operand2;
// D1. Normalize
@ -78,8 +79,8 @@ constexpr void div_mod_internal(
// should not be reachable at all in this case because fast paths above cover all cases
// when `operand2.size() == 1`.
AK_IGNORE_DIAGNOSTIC("-Warray-bounds", size_t shift = count_leading_zeroes(divisor[divisor_len - 1]);)
StorageOperations::shift_left(dividend, shift, dividend);
StorageOperations::shift_left(divisor, shift, divisor);
Ops::shift_left(dividend, shift, dividend);
Ops::shift_left(divisor, shift, divisor);
auto divisor_approx = divisor[divisor_len - 1];
@ -88,13 +89,13 @@ constexpr void div_mod_internal(
NativeWord qhat;
VERIFY(dividend[i] <= divisor_approx);
if (dividend[i] == divisor_approx) {
qhat = max_word;
qhat = max_native_word;
} else {
NativeWord rhat;
qhat = div_mod_words(dividend[i - 1], dividend[i], divisor_approx, rhat);
auto is_qhat_too_large = [&] {
return UFixedBigInt<word_size> { qhat }.wide_multiply(divisor[divisor_len - 2]) > u128 { dividend[i - 2], rhat };
return UFixedBigInt<native_word_size> { qhat }.wide_multiply(divisor[divisor_len - 2]) > u128 { dividend[i - 2], rhat };
};
if (is_qhat_too_large()) {
--qhat;
@ -109,7 +110,7 @@ constexpr void div_mod_internal(
NativeWord mul_carry = 0;
bool sub_carry = false;
for (size_t j = 0; j < divisor_len; ++j) {
auto mul_result = UFixedBigInt<word_size> { qhat }.wide_multiply(divisor[j]) + mul_carry;
auto mul_result = UFixedBigInt<native_word_size> { qhat }.wide_multiply(divisor[j]) + mul_carry;
auto& output = dividend[i + j - divisor_len];
output = sub_words(output, mul_result.low(), sub_carry);
mul_carry = mul_result.high();
@ -119,7 +120,7 @@ constexpr void div_mod_internal(
if (sub_carry) {
// D6. Add back
auto dividend_part = UnsignedStorageSpan { dividend.data() + i - divisor_len, divisor_len + 1 };
VERIFY(StorageOperations::add<false>(dividend_part, divisor, dividend_part));
VERIFY(Ops::add<false>(dividend_part, divisor, dividend_part));
}
quotient[i - divisor_len] = qhat - sub_carry;
@ -130,7 +131,7 @@ constexpr void div_mod_internal(
// D8. Unnormalize
if constexpr (restore_remainder)
StorageOperations::shift_right(UnsignedStorageSpan { dividend.data(), remainder.size() }, shift, remainder);
}
Ops::shift_right(UnsignedStorageSpan { dividend.data(), remainder.size() }, shift, remainder);
}
}

View file

@ -99,15 +99,15 @@ TEST_CASE(div_anti_knuth)
1,
2,
3,
max_word / 4 - 1,
max_word / 4,
max_word / 2 - 1,
max_word / 2,
max_word / 2 + 1,
max_word / 2 + 2,
max_word - 3,
max_word - 2,
max_word - 1,
max_native_word / 4 - 1,
max_native_word / 4,
max_native_word / 2 - 1,
max_native_word / 2,
max_native_word / 2 + 1,
max_native_word / 2 + 2,
max_native_word - 3,
max_native_word - 2,
max_native_word - 1,
};
for (size_t i = 0; i < storage.size(); ++i) {
u32 type = get_random_uniform(interesting_words_count + 1);