From f95abe8c0ee8b136108b8dab646b0adeb9f9f1b7 Mon Sep 17 00:00:00 2001 From: Hendiadyoin1 Date: Sun, 17 Mar 2024 19:33:41 +0100 Subject: [PATCH] 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 --- AK/BigIntBase.h | 196 ++++++++++++++++---------- AK/FloatingPointStringConversions.cpp | 30 ++-- AK/UFixedBigInt.h | 101 ++++++------- AK/UFixedBigIntDivision.h | 41 +++--- Tests/AK/TestUFixedBigInt.cpp | 18 +-- 5 files changed, 220 insertions(+), 166 deletions(-) diff --git a/AK/BigIntBase.h b/AK/BigIntBase.h index cc481d2f0f..9dc9133308 100644 --- a/AK/BigIntBase.h +++ b/AK/BigIntBase.h @@ -14,33 +14,51 @@ namespace AK { namespace Detail { + +template +struct DoubleWordHelper; + +template<> +struct DoubleWordHelper { + using Type = u64; + using SignedType = i64; +}; +template +using DoubleWord = typename DoubleWordHelper::Type; +template +using SignedDoubleWord = typename DoubleWordHelper::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 { + 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 -using ConditionallySignedDoubleWord = Conditional; +using NativeDoubleWord = DoubleWord; +using SignedNativeDoubleWord = SignedDoubleWord; + +template +using ConditionallySignedDoubleWord = Conditional, DoubleWord>; template -concept BuiltInUFixedInt = OneOf; +concept BuiltInUFixedInt = OneOf; template constexpr inline size_t bit_width = sizeof(T) * 8; -constexpr size_t word_size = bit_width; -constexpr NativeWord max_word = ~static_cast(0); -static_assert(word_size == 32 || word_size == 64); +constexpr size_t native_word_size = bit_width; +constexpr NativeWord max_native_word = NumericLimits::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 @@ -59,8 +77,8 @@ concept IntegerStorage = requires(T storage, size_t index) { } -> ConvertibleTo; }; -template -concept IntegerReadonlyStorage = IntegerStorage; +template +concept IntegerReadonlyStorage = IntegerStorage; struct NullAllocator { NativeWord* allocate(size_t) { VERIFY_NOT_REACHED(); } @@ -79,7 +97,7 @@ struct StorageSpan : AK::Span { constexpr bool is_negative() const { - return is_signed && this->last() >> (word_size - 1); + return is_signed && this->last() >> (bit_width - 1); } }; @@ -91,8 +109,8 @@ using UnsignedStorageReadonlySpan = StorageSpan; // `bit_size`-sized and `ceil(bit_size / word_size) * word_size`-sized `StaticStorage`s will act the // same. template -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> get_storage_of(T value) { if constexpr (sizeof(T) > sizeof(NativeWord)) { static_assert(sizeof(T) == 2 * sizeof(NativeWord)); - return { static_cast(value), static_cast(value >> word_size) }; + return { static_cast(value), static_cast(value >> native_word_size) }; } return { static_cast(value) }; } // ===== Utilities ===== -ALWAYS_INLINE constexpr NativeWord extend_sign(bool sign) +template +ALWAYS_INLINE constexpr Word extend_sign(bool sign) { - return sign ? max_word : 0; + return sign ? NumericLimits::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 +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) + WordType ncarry, output; + if constexpr (SameAs) output = __builtin_addc(word1, word2, carry, reinterpret_cast(&ncarry)); - else if constexpr (SameAs) + else if constexpr (SameAs) output = __builtin_addcl(word1, word2, carry, reinterpret_cast(&ncarry)); - else if constexpr (SameAs) + else if constexpr (SameAs) output = __builtin_addcll(word1, word2, carry, reinterpret_cast(&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(output); + if constexpr (SameAs) { + unsigned int output; + carry = __builtin_ia32_addcarryx_u32(carry, word1, word2, &output); + return output; + } else if constexpr (OneOf) { + 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 +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) + WordType ncarry, output; + if constexpr (SameAs) output = __builtin_subc(word1, word2, carry, reinterpret_cast(&ncarry)); - else if constexpr (SameAs) + else if constexpr (SameAs) output = __builtin_subcl(word1, word2, carry, reinterpret_cast(&ncarry)); - else if constexpr (SameAs) + else if constexpr (SameAs) output = __builtin_subcll(word1, word2, carry, reinterpret_cast(&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(output); + if constexpr (SameAs) { + unsigned int output; + carry = __builtin_ia32_sbb_u32(carry, word1, word2, &output); + return output; + } else if constexpr (OneOf) { + 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 +constexpr DoubleWord dword(WordType low, WordType high) { - auto dividend = (static_cast(dividend1) << word_size) + dividend0; - remainder = static_cast(dividend % divisor); - return static_cast(dividend / divisor); + return (static_cast>(high) << bit_width) | low; +} + +// Calculate ((dividend_high << word_size) + dividend_low) / divisor. Quotient should be guaranteed to fit +// into WordType. +template +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(dividend % divisor); + return static_cast(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 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; + using DoubleWordType = DoubleWord; + + static constexpr void copy(IntegerReadonlyStorage auto const& operand, IntegerStorage auto&& result, size_t offset = 0) { - auto fill = extend_sign(operand.is_negative()); + auto fill = extend_sign(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 auto const& operand1, IntegerReadonlyStorage 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(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 - static constexpr void compute_bitwise(IntegerReadonlyStorage auto const& operand1, IntegerReadonlyStorage auto const& operand2, IntegerStorage auto&& result) + static constexpr void compute_bitwise(IntegerReadonlyStorage auto const& operand1, IntegerReadonlyStorage auto const& operand2, IntegerStorage 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 - static constexpr void compute_inplace_bitwise(IntegerReadonlyStorage auto const&, IntegerReadonlyStorage auto const& operand2, IntegerStorage auto&& result) + static constexpr void compute_inplace_bitwise(IntegerReadonlyStorage auto const&, IntegerReadonlyStorage auto const& operand2, IntegerStorage 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 auto const& operand, size_t shift, IntegerStorage 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 auto const& operand, size_t shift, IntegerStorage 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 - static constexpr int add(IntegerReadonlyStorage auto const& operand1, IntegerReadonlyStorage auto const& operand2, IntegerStorage auto&& result, bool carry = false) + static constexpr int add(IntegerReadonlyStorage auto const& operand1, IntegerReadonlyStorage auto const& operand2, IntegerStorage 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(sign1), fill2 = extend_sign(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 - static constexpr int increment(IntegerStorage auto&& operand) + static constexpr int increment(IntegerStorage 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(operand[i], 0, carry); else - operand[i] = sub_words(operand[i], 0, carry); + operand[i] = sub_words(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 auto const& operand, IntegerStorage 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(0, operand[i], carry); return carry; } // No allocations will occur if both operands are unsigned. - template - static constexpr void baseline_mul(Operand1 const& operand1, Operand2 const& operand2, IntegerStorage auto&& __restrict__ result, auto&& buffer) + template Operand1, IntegerReadonlyStorage Operand2> + static constexpr void baseline_mul(Operand1 const& operand1, Operand2 const& operand2, IntegerStorage 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 word1 = operand1[0]; - ConditionallySignedDoubleWord word2 = operand2[0]; - auto value = static_cast(word1 * word2); + ConditionallySignedDoubleWord word1 = operand1[0]; + ConditionallySignedDoubleWord word2 = operand2[0]; + auto value = static_cast(word1 * word2); result[0] = value; if (size > 1) { result[1] = value >> word_size; - auto fill = extend_sign(sign1 ^ sign2); + auto fill = extend_sign(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 { inverted, size1 }); data1 = inverted; } if (sign2) { auto inverted = buffer.allocate(size2); - negate(operand2, UnsignedStorageSpan { inverted, size2 }); + negate(operand2, StorageSpan { 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(carry); + result[i] = static_cast(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(data1[j]) * data2[i - j]; + auto x = static_cast(data1[j]) * data2[i - j]; bool ncarry = false; - result[i] = add_words(result[i], static_cast(x), ncarry); + result[i] = add_words(result[i], static_cast(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; diff --git a/AK/FloatingPointStringConversions.cpp b/AK/FloatingPointStringConversions.cpp index d63aaa5231..9298d62241 100644 --- a/AK/FloatingPointStringConversions.cpp +++ b/AK/FloatingPointStringConversions.cpp @@ -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(word_size * m_used_length) - count_leading_zeroes(m_words[m_used_length - 1]); + return static_cast(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(StorageOperations::compare(get_storage(), other.get_storage(), false)); + return static_cast(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(value); NativeWord carry = 0; for (size_t i = 0; i < m_used_length; ++i) { - auto result = UFixedBigInt(m_words[i]).wide_multiply(native_value) + carry; + auto result = UFixedBigInt(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::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(value); return; } auto initial_storage = get_storage(); auto expanded_storage = get_storage(max(m_used_length, words_in_u64)); - if (StorageOperations::add(initial_storage, Detail::get_storage_of(value), expanded_storage)) + if (Ops::add(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; diff --git a/AK/UFixedBigInt.h b/AK/UFixedBigInt.h index 28f4b7b35d..cf60001ae3 100644 --- a/AK/UFixedBigInt.h +++ b/AK/UFixedBigInt.h @@ -58,7 +58,7 @@ constexpr auto& get_storage_of(UFixedBigInt const& value) { return val template 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 @@ -72,27 +72,28 @@ template class UFixedBigInt { constexpr static size_t static_size = Storage::static_size; constexpr static size_t part_size = static_size / 2; - using UFixedBigIntPart = Conditional>; + using UFixedBigIntPart = Conditional>; + 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 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 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) { + m_data[i + offset / native_word_size] = storage[i]; + } else if (offset % native_word_size == 32 && IsSame) { // u32 vector initialization on 64-bit platforms - m_data[offset / word_size] |= static_cast(value[i]) << 32; + m_data[offset / native_word_size] |= static_cast(value[i]) << 32; } else { VERIFY_NOT_REACHED(); } offset += assumed_bit_size; } - 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 - requires(sizeof(T) == sizeof(DoubleWord)) constexpr explicit operator T() const + requires(sizeof(T) == sizeof(NativeDoubleWord)) constexpr explicit operator T() const { - return (static_cast(m_data[1]) << word_size) + m_data[0]; + return (static_cast(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) { - return m_data[0] + (static_cast(m_data[1]) << word_size); + } else if constexpr (IsSame) { + return m_data[0] + (static_cast(m_data[1]) << native_word_size); } else { - UFixedBigInt result; - StorageOperations::copy(m_data, result.m_data); + UFixedBigInt 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) { - return m_data[part_size] + (static_cast(m_data[part_size + 1]) << word_size); + } else if constexpr (IsSame) { + return m_data[part_size] + (static_cast(m_data[part_size + 1]) << native_word_size); } else { - UFixedBigInt result; - StorageOperations::copy(m_data, result.m_data, part_size); + UFixedBigInt 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) - DEFINE_STANDARD_BINARY_OPERATOR(&, StorageOperations::compute_bitwise) - DEFINE_STANDARD_BINARY_OPERATOR(|, StorageOperations::compute_bitwise) - DEFINE_STANDARD_COMPOUND_ASSIGNMENT(^=, StorageOperations::compute_inplace_bitwise) - DEFINE_STANDARD_COMPOUND_ASSIGNMENT(&=, StorageOperations::compute_inplace_bitwise) - DEFINE_STANDARD_COMPOUND_ASSIGNMENT(|=, StorageOperations::compute_inplace_bitwise) + DEFINE_STANDARD_BINARY_OPERATOR(^, Ops::compute_bitwise) + DEFINE_STANDARD_BINARY_OPERATOR(&, Ops::compute_bitwise) + DEFINE_STANDARD_BINARY_OPERATOR(|, Ops::compute_bitwise) + DEFINE_STANDARD_COMPOUND_ASSIGNMENT(^=, Ops::compute_inplace_bitwise) + DEFINE_STANDARD_COMPOUND_ASSIGNMENT(&=, Ops::compute_inplace_bitwise) + DEFINE_STANDARD_COMPOUND_ASSIGNMENT(|=, Ops::compute_inplace_bitwise) constexpr auto operator~() const { UFixedBigInt result; - StorageOperations::compute_bitwise(m_data, m_data, result.m_data); + Ops::compute_bitwise(m_data, m_data, result.m_data); return result; } constexpr auto operator<<(size_t shift) const { UFixedBigInt 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 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)> result; - carry = StorageOperations::add(m_data, get_storage_of(other), result.m_data, carry); + carry = Ops::add(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)> result; - borrow = StorageOperations::add(m_data, get_storage_of(other), result.m_data, borrow); + borrow = Ops::add(m_data, get_storage_of(other), result.m_data, borrow); return result; } - DEFINE_STANDARD_BINARY_OPERATOR(+, StorageOperations::add) - DEFINE_STANDARD_BINARY_OPERATOR(-, StorageOperations::add) - DEFINE_STANDARD_COMPOUND_ASSIGNMENT(+=, StorageOperations::add) - DEFINE_STANDARD_COMPOUND_ASSIGNMENT(-=, StorageOperations::add) + DEFINE_STANDARD_BINARY_OPERATOR(+, Ops::add) + DEFINE_STANDARD_BINARY_OPERATOR(-, Ops::add) + DEFINE_STANDARD_COMPOUND_ASSIGNMENT(+=, Ops::add) + DEFINE_STANDARD_COMPOUND_ASSIGNMENT(-=, Ops::add) constexpr auto& operator++() { - StorageOperations::increment(m_data); + Ops::increment(m_data); return *this; } constexpr auto& operator--() { - StorageOperations::increment(m_data); + Ops::increment(m_data); return *this; } constexpr auto operator++(int) { UFixedBigInt result = *this; - StorageOperations::increment(m_data); + Ops::increment(m_data); return result; } constexpr auto operator--(int) { UFixedBigInt result = *this; - StorageOperations::increment(m_data); + Ops::increment(m_data); return result; } diff --git a/AK/UFixedBigIntDivision.h b/AK/UFixedBigIntDivision.h index 0c133cd3ec..171f9a44ef 100644 --- a/AK/UFixedBigIntDivision.h +++ b/AK/UFixedBigIntDivision.h @@ -9,8 +9,7 @@ #include #include -namespace AK { -namespace Detail { +namespace AK::Detail { template constexpr void div_mod_internal( @@ -19,6 +18,8 @@ constexpr void div_mod_internal( StaticStorage& quotient, StaticStorage& 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(operand1[dividend_len - 1]) << word_size) + operand1[dividend_len - 2]; + auto u = (static_cast(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(top >> word_size); + quotient[dividend_len - 1] = static_cast(top >> native_word_size); quotient[dividend_len - 2] = static_cast(top); auto carry = static_cast(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 dividend; - StorageOperations::copy(operand1, dividend); + StaticStorage 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 { qhat }.wide_multiply(divisor[divisor_len - 2]) > u128 { dividend[i - 2], rhat }; + return UFixedBigInt { 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 { qhat }.wide_multiply(divisor[j]) + mul_carry; + auto mul_result = UFixedBigInt { 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(dividend_part, divisor, dividend_part)); + VERIFY(Ops::add(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); } + } diff --git a/Tests/AK/TestUFixedBigInt.cpp b/Tests/AK/TestUFixedBigInt.cpp index c5a5b3cd07..f5e356ac74 100644 --- a/Tests/AK/TestUFixedBigInt.cpp +++ b/Tests/AK/TestUFixedBigInt.cpp @@ -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);