mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 00:50:22 +00:00
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:
parent
1a312f4265
commit
f95abe8c0e
Notes:
sideshowbarker
2024-07-17 07:11:12 +09:00
Author: https://github.com/Hendiadyoin1 Commit: https://github.com/SerenityOS/serenity/commit/f95abe8c0e Pull-request: https://github.com/SerenityOS/serenity/pull/23619 Issue: https://github.com/SerenityOS/serenity/issues/23575 Reviewed-by: https://github.com/ADKaster ✅ Reviewed-by: https://github.com/DanShaders ✅
5 changed files with 220 additions and 166 deletions
196
AK/BigIntBase.h
196
AK/BigIntBase.h
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue