LibCrypto: Add support for shift_right more than one word

- Before: UnsignedBigInteger::shift_right( n ) trigger index
  verification error for n>31. An assumption of
  num_bits<UnsignedBigInteger::BITS_IN_WORD was being made
- After: shift_right( n ) works correctly for n>31.

NOTE: "bonus" change; not necessary for fixing BigFraction::to_double
This commit is contained in:
Manuel Zahariev 2025-01-07 13:37:33 -08:00 committed by Ali Mohammad Pur
commit 05cfbdd6fb
Notes: github-actions[bot] 2025-03-23 18:34:34 +00:00
2 changed files with 74 additions and 2 deletions

View file

@ -1,6 +1,7 @@
/*
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
* Copyright (c) 2020-2021, Dex <dexes.ttp@gmail.com>
* Copyright (c) 2025, Manuel Zahariev <manuel@duck.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -221,13 +222,49 @@ FLATTEN ErrorOr<void> UnsignedBigIntegerAlgorithms::try_shift_left_without_alloc
return {};
}
/**
* Complexity : O(N) where N is the number of words in the number
*/
FLATTEN void UnsignedBigIntegerAlgorithms::shift_right_without_allocation(
UnsignedBigInteger const& number,
size_t num_bits,
UnsignedBigInteger& output)
{
output.m_words.resize_and_keep_capacity(number.length() - (num_bits / UnsignedBigInteger::BITS_IN_WORD));
Ops::shift_right(number.words_span(), num_bits, output.words_span());
size_t const bit_shift = num_bits % UnsignedBigInteger::BITS_IN_WORD;
size_t const bit_shift_complement = UnsignedBigInteger::BITS_IN_WORD - bit_shift;
size_t const zero_based_index_of_highest_set_bit_in_hiword = (number.one_based_index_of_highest_set_bit() - 1) % UnsignedBigInteger::BITS_IN_WORD;
// true if the high word will be zeroed as a result of the shift
bool const hiword_zero = (bit_shift > zero_based_index_of_highest_set_bit_in_hiword);
size_t const word_shift = num_bits / UnsignedBigInteger::BITS_IN_WORD + (hiword_zero ? 1 : 0);
if (word_shift >= number.length()) { // all non-zero digits have been shifted right; result is zero
output.set_to_0();
return;
}
shift_right_by_n_words(number, word_shift, output);
if (bit_shift == 0) // shifting right by an exact number of words)
return;
size_t const output_length = output.length();
size_t number_index = number.length() - 1;
UnsignedBigInteger::Word carry = 0;
if (hiword_zero) {
carry = number.words().at(number_index) << bit_shift_complement;
--number_index;
}
for (size_t i = 0; i < output_length; ++i) {
size_t const output_index = output_length - i - 1; // downto index 0
output.m_words[output_index] = ((number.m_words.at(number_index) >> bit_shift)) | carry;
carry = (number.m_words.at(number_index) << bit_shift_complement);
--number_index;
}
}
void UnsignedBigIntegerAlgorithms::shift_left_by_n_words(

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Tuple.h>
#include <LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h>
#include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
@ -570,6 +571,40 @@ TEST_CASE(test_signed_bigint_bitwise_xor)
EXPECT_EQ(num2.bitwise_xor(num2), "0"_sbigint);
}
TEST_CASE(test_bigint_shift_right)
{
Crypto::UnsignedBigInteger const num1(Vector<u32> { 0x100, 0x20, 0x4, 0x2, 0x1 });
size_t const tests1 = 11;
AK::Tuple<size_t, Vector<u32>> results1[] = {
{ 8, { 0x20000001, 0x04000000, 0x02000000, 0x01000000 } },
{ 16, { 0x00200000, 0x00040000, 0x00020000, 0x00010000 } }, // shift by exact number of words
{ 32, { 0x00000020, 0x00000004, 0x00000002, 0x00000001 } }, // shift by exact number of words
{ 36, { 0x40000002, 0x20000000, 0x10000000 } },
{ 64, { 0x00000004, 0x00000002, 0x00000001 } }, // shift by exact number of words
{ 72, { 0x02000000, 0x01000000 } },
{ 80, { 0x00020000, 0x00010000 } },
{ 88, { 0x00000200, 0x00000100 } },
{ 128, { 0x00000001 } }, // shifted to most significant digit
{ 129, {} }, // all digits have been shifted right
{ 160, {} },
};
size_t const tests2 = 2;
Crypto::UnsignedBigInteger const num2(Vector<u32> { 0x44444444, 0xffffffff });
AK::Tuple<size_t, Vector<u32>> results2[] = {
{ 1, { 0xa2222222, 0x7fffffff } },
{ 2, { 0xd1111111, 0x3fffffff } },
};
for (size_t i = 0; i < tests1; ++i)
EXPECT_EQ(num1.shift_right(results1[i].get<0>()).words(), results1[i].get<1>());
for (size_t i = 0; i < tests2; ++i)
EXPECT_EQ(num2.shift_right(results2[i].get<0>()).words(), results2[i].get<1>());
}
TEST_CASE(test_signed_bigint_fibo500)
{
Vector<u32> expected_result {