upd: write comments

This commit is contained in:
2023-11-01 23:14:46 +08:00
parent ddde29da3a
commit 3d90acc263
2 changed files with 96 additions and 4 deletions

View File

@ -14,6 +14,7 @@ class int2048 {
* If the interger is negative, flag = -1, otherwise flag = 1.
* num_length is the length of the integer, (num_length+kNum-1)/kNum is the
* length of val with data. Note that position in val without data is 0.
* buf_length is the accessiable length of val.
*/
const static int kStoreBase = 100000000, kNum = 8, kDefaultLength = 10;
const static int kMemAdditionScalar = 2, kMemDeleteScalar = 4;
@ -59,15 +60,27 @@ root= 6
int2048(int2048 &&) noexcept;
~int2048();
/**
* @brief Move the integer left (*10^k) or right(//10^k).
*/
void LeftMoveBy(int);
void RightMoveBy(int);
void read(const std::string &);
void print();
void print_debug();
// function print_debug is used to print the data in val to stderr(with '\n'),
// for debug
void ClaimMem(size_t);
// something like vector's reserve
/**
* Fuctions whose name starts with Unsigned are basic operation. It receives
* int2048& A and const int2048* pB(UnsignedCmp receives const int2048& B),
* and do the operation A op= *pB. Warning: the address of A and pB must be
* different.
*/
inline friend int UnsignedCmp(const int2048 &, const int2048 &);
inline friend void UnsignedAdd(int2048 &, const int2048 *);
inline friend void UnsignedMinus(int2048 &, const int2048 *);
@ -100,6 +113,11 @@ root= 6
int2048 &Divide(const int2048 &);
friend int2048 Divide(int2048, const int2048 &);
/**
* If the denominator is zero, the result is undefined.
* But actually it will return 0 with no exception thrown.
* And % operation is also based on this rule.
*/
int2048 &operator/=(const int2048 &);
friend int2048 operator/(int2048, const int2048 &);

View File

@ -100,6 +100,13 @@ int2048::int2048(const int2048 &input_value) {
num_length = input_value.num_length;
}
/**
* @brief Move constructor
*
* @param input_value
*
* @details Note that after moving, the input_value will be set to 0.
*/
int2048::int2048(int2048 &&input_value) noexcept {
buf_length = input_value.buf_length;
val = input_value.val;
@ -164,6 +171,15 @@ void int2048::print_debug() {
delete[] buf;
}
/**
* @brief Claim memory for the number
*
* @param number_length The length of the number
*
* @details If the number_length is smaller than the current buf_length/4 or
* bigger than current buf_length, then val will be reset to accomodate
* number_length*2 numbers. Something like std::vector.
*/
void int2048::ClaimMem(size_t number_length) {
size_t new_number_blocks = (number_length + kNum - 1) / kNum;
if (new_number_blocks > buf_length) {
@ -181,6 +197,17 @@ void int2048::ClaimMem(size_t number_length) {
}
}
/**
* @brief Compare two unsigned numbers
*
* @param A The first number
* @param B The second number
*
* @return -1 if A < B, 0 if A == B, 1 if A > B
*
* @details This function is used to compare two numbers wittout considering the
* sign.
*/
inline int UnsignedCmp(const int2048 &A, const int2048 &B) {
if (A.num_length != B.num_length) return A.num_length < B.num_length ? -1 : 1;
int number_of_blocks = (A.num_length + int2048::kNum - 1) / int2048::kNum;
@ -440,6 +467,16 @@ void int2048::RightMoveBy(int L) {
this->num_length -= small_move;
}
/**
* @brief the definition of NTTTransform
*
* @param a the array to be transformed
* @param NTT_blocks the length of the array
* @param inverse whether to do inverse transform
*
* @details the array a will be transformed to a new array a', which is the NTT.
* Note that the base in NTT is 10000.
*/
void int2048::NTTTransform(__int128_t *a, int NTT_blocks,
bool inverse = false) {
for (int i = 1, j = 0; i < NTT_blocks; i++) {
@ -469,6 +506,16 @@ void int2048::NTTTransform(__int128_t *a, int NTT_blocks,
for (int i = 0; i < NTT_blocks; i++) (a[i] *= inv) %= int2048::kNTTMod;
}
}
/**
* @brief Unsigned multiply two numbers
*
* @param A The first number
* @param pB The second number
*
* @details This function will multiply two numbers without considering the
* sign.
*/
inline void UnsignedMultiply(int2048 &A, const int2048 *pB) {
if (&A == pB) throw "UnsignedMultiply: A and B are the same object";
int blocks_of_A = ((A.num_length + int2048::kNum - 1) / int2048::kNum);
@ -552,6 +599,15 @@ int2048 operator*(int2048 A, const int2048 &B) {
A.Multiply(B);
return std::move(A);
}
/**
* @brief Unsigned multiply a number by an integer
*
* @param v The integer
*
* @details This function will multiply a number by an integer without
* considering the sign.
*/
void int2048::UnsignedMultiplyByInt(int v) {
if (v < 0) throw "UnsignedMultiplyByInt: v < 0";
if (v == 0) {
@ -586,6 +642,9 @@ int2048 GetInv(const int2048 &B, int n) {
100000, 1000000, 10000000, 100000000};
int total_blocks = (B.num_length + int2048::kNum - 1) / int2048::kNum;
if (n <= 16) {
/**
* This code block is used to calculate the inverse of B when n is small.
*/
long long b = 0;
for (int j = B.num_length - 1, i = 0; i < n; i++, j--)
b = b * 10 + (B.val[j / int2048::kNum] / kPow10[j % int2048::kNum]) % 10;
@ -614,6 +673,7 @@ int2048 GetInv(const int2048 &B, int n) {
int2048 sub_soluton_copy_2(sub_soluton);
sub_soluton_copy_1.UnsignedMultiplyByInt(2);
sub_soluton_copy_1.LeftMoveBy((n - k));
// Now sub_soluton_copy_1 is 2*sub_soluton*10^(n-k)
/*now we get the current B*/
int2048 current_B;
current_B.ClaimMem(n);
@ -632,8 +692,11 @@ int2048 GetInv(const int2048 &B, int n) {
UnsignedMultiply(sub_soluton_copy_2, &current_B);
UnsignedMultiply(sub_soluton_copy_2, &sub_soluton);
sub_soluton_copy_2.RightMoveBy(2 * k);
// Now sub_soluton_copy_2 is sub_soluton^2*current_B/(10^2k)
UnsignedMinus(sub_soluton_copy_1, &sub_soluton_copy_2);
int2048 res = sub_soluton_copy_1;
// Now we get an estimation for [10^2n/current_B]
/*Now begin doing some ajustments*/
int2048 remain;
remain.ClaimMem((2 * n) + 1);
remain.val[2 * n / int2048::kNum] = kPow10[2 * n % int2048::kNum];
@ -667,14 +730,21 @@ int2048 GetInv(const int2048 &B, int n) {
return std::move(res);
}
}
/**
* @brief Unsigned divide two numbers
*
* @param A The first number
* @param pB The second number
*
* @details This function will divide two numbers without considering the sign.
* reference:
* <https://github.com/lzyrapx/Competitive-Programming-Docs/blob/master/WC%E8%AE%B2%E8%AF%BE%E8%B5%84%E6%96%99/%E7%90%86%E6%80%A7%E6%84%89%E6%82%A6%E2%80%94%E2%80%94%E9%AB%98%E7%B2%BE%E5%BA%A6%E6%95%B0%E5%80%BC%E8%AE%A1%E7%AE%97%EF%BC%882012WC%EF%BC%89.pdf>
*/
inline void UnsignedDivide(int2048 &A, const int2048 *pB) {
int2048 B(*pB);
int L1 = A.num_length;
int L2 = B.num_length;
/**
* reference:
* https://github.com/lzyrapx/Competitive-Programming-Docs/blob/master/WC%E8%AE%B2%E8%AF%BE%E8%B5%84%E6%96%99/%E7%90%86%E6%80%A7%E6%84%89%E6%82%A6%E2%80%94%E2%80%94%E9%AB%98%E7%B2%BE%E5%BA%A6%E6%95%B0%E5%80%BC%E8%AE%A1%E7%AE%97%EF%BC%882012WC%EF%BC%89.pdf
*/
if (L2 <= 16) {
long long b = B.val[0] + (long long)B.val[1] * int2048::kStoreBase;
__uint128_t c = 0;
@ -709,12 +779,16 @@ inline void UnsignedDivide(int2048 &A, const int2048 *pB) {
assert(L1 == A.num_length);
assert(L2 == B.num_length);
assert(2 * L2 >= L1);
/*Now we calculate [10^2n/B]*/
int2048 res_hat = std::move(GetInv(B, L2));
UnsignedMultiply(res_hat, &A);
res_hat.RightMoveBy(2 * L2);
// now res_hat is [[10^2n/B]*A/(10^2L2)]
/*Now begin doing ajustments*/
int2048 remain(A), tmp_B(B);
UnsignedMultiply(tmp_B, &res_hat);
UnsignedMinus(remain, &tmp_B);
// now remain is A-res_hat*B
for (int i = 8; i > 0; i >>= 1) {
tmp_B = B;
tmp_B.UnsignedMultiplyByInt(i);