# Cộng, trừ, nhân 2 số nguyên lớn **1. Cộng 2 số nguyên lớn** **Bước 1**: Chuẩn hóa 2 xâu a và b để 2 xâu có độ dài bằng nhau. Xâu nào ngắn hơn thì ta thêm ký tự ‘0’ vào đầu xâu đó cho đến khi 2 xâu có độ dài bằng nhau thì dừng. **Bước 2**: Duyệt từ cuối xâu về đầu xâu: * Khởi tạo 1 biến lưu kết quả **ans**. * Xét từng ký tự chuyển sang kiểu **int** xong cộng lại và lưu vào biến **sum**, đồng thời khởi tạo biến **carry** để nhớ 1 (trong trường hợp hai số cộng lại lớn hơn 9). **Tổng quát:** * **ans = a;** *// Gán a cho ans* * for(int i từ cuối về đầu) * **sum = a – ‘0’ + b – ‘0’ + carry;** *// Cộng 2 số, để chuyển kiểu ký tự sang int thì ta trừ đi ‘0’ hoặc 48.* * **carry = sum / 10;** *// Nếu sum > 9 thì chia 10 chắc chắn được 1.* * **ans[i] = (sum % 10) + ‘0’;** *// (sum % 10) là số vừa được tính, sau đó + '0' để chuyển sang kiểu ký tự* **Ví dụ:** **string** a = “15”; **string** b = “89”; ❖ **for lần 1** (i = 1), **carry** lúc đầu = 0, **ans** = **a** = “15”: * sum = 5 - ‘0’ + 9 - ‘0’ + 0 = 14; * carry = 14 / 10 = 1; * ans[1] = (14 % 10) + ‘0’ = 4; **=>** ans lúc này = “14”. ❖ **for lần 2** (i = 0), **carry** lúc này = 1, **ans** = “14”: * sum = 1 - ‘0’ + 8 - ‘0’ + 1 = 10; * carry = 10 / 10 = 1; * ans[0] = (10 % 10) + ‘0’ = 0; **=>** ans lúc này = “04”. Tới đây thì rõ ràng là **carry** vẫn còn đang nhớ 1, nên ta phải thêm 1 bước xử lí nữa để kiểm tra xem nếu **carry = 1** thì ta thêm ký tự **‘1’** vào trước xâu **ans**. Cuối cùng ta thu được kết quả là **ans = “104”** là kết quả của bài toán. **Code mẫu:** ```c=1 // author: hatakaze (a.k.a Tran Gia Huy) #include "bits/stdc++.h" using namespace std; typedef long long ll; string add_num(string a, string b) { ll carry = 0, sum; string ans; // Xử lí để hai xâu a và b có độ dài bằng nhau for(int i = 0; i < max(a.size(), b.size()); i++) { if(a.size() == b.size()) break; // Nếu bằng nhau thì dừng else if(a.size() < b.size()) { a = '0' + a; } else { b = '0' + b; } } // Lưu ý gán a cho ans sau khi cả 2 xâu a và b đã có độ dài bằng nhau ans = a; for(int i = a.size() - 1; i >= 0; i--) { sum = a[i] - '0' + b[i] - '0' + carry; // - ‘0’ để chuyển từ chuỗi sang số carry = sum / 10; ans[i] = (sum % 10) + '0'; // + ‘0’ để chuyển từ số sang chuỗi } if(carry == 1) ans = '1' + ans; // Nếu nhớ = 1 thì thêm ‘1’ vào trước ans return ans; } int main() { string a, b; cin >> a >> b; cout << add_num(a, b); } ``` \ **2. Trừ 2 số nguyên lớn (Số lớn trừ số bé)** Các bước đầu tương tự ở phần 1 nhưng sẽ khác một chút. Ở dạng trừ này thì sẽ có trường hợp như là 10 – 06 = 04 nên ta cần phải bỏ đi số 0 vô nghĩa kia để ra được đáp án cuối cùng là 4. **Tổng quát:** * **ans = a** *// Gán a cho ans* * for(int i từ **cuối** về **đầu**): * **sum = (a – ‘0’) – (b – ‘0’) – carry;** * **if(sum < 0)** thì ta sẽ làm 2 thao tác: * **sum += 10;** *// Khi đã chạy dòng này nghĩa là sum tính ra số âm, nên ta phải mượn 1 chèn vào phía trước, nghĩa là +10 vào số đó.* * **carry = 1;** *// Nhớ 1* * **else** thì chỉ cần cho **carry = 0**; * **ans[i] = sum + ‘0’;** *// Chuyển từ kiểu int sang ký tự* Như đã nói ở trên, ta thêm vào bước xử lý số 0 vô nghĩa: > **while**(ans.size() > 1 && ans[0] == '0') ans.erase(0, 1); > [color=#138ec6] **Giải thích**: Dòng này có nghĩa là chỉ khi **ans** có độ dài từ 2 trở lên (ví dụ **ans** = “04” thì ans.size() khi này = 2) và ký tự đầu tiên của **ans** = ‘0’ thì dòng ans.erase(0, 1) **(VariableName.erase(iterator_first, iterator_last))** sẽ xóa đi 1 ký tự từ vị trí 0. Giả sử ta có chuỗi **“0000019”** thì vòng **while** sẽ lặp 5 lần, mỗi lần sẽ xóa đi 1 ký tự ở đầu: **Lần 1:** “000019”; **Lần 2:** “00019”; **Lần 3:** “0019”; **Lần 4:** “019”; **Lần 5:** “19”; **Ví dụ:** **string** a = “55”; **string** b = “19”; ❖ **for lần 1** (i = 1), **carry** lúc đầu = 0, **ans** = **a** = “55”: * sum = 5 - ‘0’ – 9 - ‘0’ – 0 = -4; * if(sum < 0) mệnh đề đúng { * sum = sum + 10 = -4 + 10 = 6; * carry = 1; // *update carry* * else *vì đã chạy if ở trên nên else được bỏ qua:* { carry = 0; } * ans[1] = sum + ‘0’ = ‘6’; **=>** ans lúc này = “56” ❖ **for lần 2** (i = 0), **carry** lúc này = 1, **ans** = “56”: * sum = 5 - ‘0’ – 1 - ‘0’ – 1 = 3; * if(sum < 0) *// mệnh đề sai -> không được chạy* * sum += 10; * carry = 1; * else *// vì if ở trên không được chạy nên else sẽ được chạy* { carry = 0; } * ans[0] = sum + ‘0’ = ‘3’; **=>** ans lúc này = “36” **Code mẫu:** ```c=1 string sub_num(string a, string b) { ll carry = 0, sum; string ans; // Xử lí để hai xâu a và b có độ dài bằng nhau for(int i = 0; i < max(a.size(), b.size()); i++) { if(a.size() == b.size()) break; // Nếu bằng nhau thì dừng else if(a.size() < b.size()) { a = '0' + a; } else { b = '0' + b; } } ans = a; for(int i = a.size() - 1; i >= 0; i--) { sum = (a[i] - '0') - (b[i] - '0') – carry; if(sum < 0) { sum += 10; // Đồng thời biến nhớ sẽ gán cho = 1 carry = 1; } else carry = 0; ans[i] = (sum + '0'); } while(ans.size() > 1 && ans[0] == '0') ans.erase(0, 1); return ans; } ``` \ **3. Nhân 2 số nguyên lớn** Ở đây ta sẽ sử dụng **thuật toán nhân với số nhỏ** và **thuật toán cộng hai số** (Lưu ý: trước khi cộng hai xâu, ta thêm ký tự ‘0’ vào cuối xâu thứ hai). ```c=1 // Thuật toán nhân với số nhỏ string small_mul(string a, int k) { ll carry = 0, sum; string ans = a; for(int i = a.size() - 1; i >= 0; i--) { sum = (a[i] - '0') * k + carry; carry = sum / 10; // Đối với nhân thì nhớ có thể lên đến 8 ans[i] = (sum % 10 + '0'); } if(carry != 0) { // Phải để là != vì có thể carry là một số lớn hơn 1 ans = char(carry + '0') + ans; } while(ans.size() > 1 && ans[0] == '0') ans.erase(0, 1); return ans; } ``` ```c=1 // Thuật toán cộng hai số string add_num(string a, string b) { ll carry = 0, sum; string ans; // Lưu ý gán a cho ans sau khi cả 2 xâu a và b đã có độ dài bằng nhau ans = a; for(int i = a.size() - 1; i >= 0; i--) { sum = a[i] - '0' + b[i] - '0' + carry; // - ‘0’ để chuyển từ chuỗi sang số carry = sum / 10; ans[i] = (sum % 10) + '0'; // + ‘0’ để chuyển từ số sang chuỗi } if(carry == 1) ans = '1' + ans; // Nếu nhớ = 1 thì thêm ‘1’ vào trước ans return ans; } ``` Biến **ans** để lưu kết quả cuối cùng, **ax** để lưu kết quả mỗi lần cộng hai số, **bx** để lưu kết quả mỗi lần nhân hai số nhỏ, **tight** mỗi lần lặp sẽ +1 lên tương ứng với số lượng ký tự ‘0’ được thêm vào. ```c=0 // Hàm tính toán nhân 2 số nguyên lớn thông qua 2 hàm trên string big_mul(string a, string b) { string ans, ax = "0", bx; int tight = 0; for(int i = b.size() - 1; i >= 0; i--) { bx = small_mul(a, b[i] - '0'); bx.insert(bx.size(), tight, '0'); ++tight; ans = add_num(ax, bx); ax = ans; } return ans; } ``` \ **Giải thích:** + **Dòng 4:** ta duyệt từ cuối về đầu như thông thường. + **Dòng 5:** **bx** sẽ lưu kết quả của số ở vị trí cuối trong xâu b nhân lần lượt với từng số trong xâu a. Ví dụ ở đây ta thấy số 2 sẽ nhân lần lượt với các số ở hàng trên, sau đó sẽ là số 1 nhân lần lượt với các hàng ở trên. Sau đó ta sẽ có những hàng phía dưới, việc ta cần làm là cộng hết vô theo hàng dọc. ![](https://hackmd.io/_uploads/BJYRZtd6h.png) + **Dòng 6:** thêm ký tự ‘0’ vào sau xâu (vòng lặp đầu tiên sẽ không được thêm), sau đó biến **tight** được tăng lên 1 nghĩa là những lần lặp sau đó sẽ thêm ký tự ‘0’ vào sau xâu tương ứng với giá trị của **tight**. + **Dòng 8:** biến **ans** sẽ lưu tổng của số được nhân (tổng của **ax** và **bx**), ban đầu ta cho **ax = “0”**, sau đó tính được **bx**, thì kết quả này sẽ được lưu lại. Sau đó, **dòng 9** ta sẽ gán lại giá trị **ans** cho **ax**. Ví dụ, ban đầu **ax = “0”**, **bx** tính được là **“123”**, thì **ans** sẽ là tổng của 2 xâu đó (**= “123”**). Xong gán lại kết quả đó cho **ax** (**= “123”**), rồi ta lại tính lần tiếp theo được **bx = “333”**, thì ta lại gán tổng của chúng cho **ans** (**= “456”**) và gán kết quả này cho **ax** (**= “456”**) và cứ theo thứ tự như thế sau vòng lặp cuối cùng ta sẽ có kết quả thu được vào biến **ans**. ```c=1 //author : hatakaze (a.k.a Tran Gia Huy) #include "bits/stdc++.h" using namespace std; typedef long long ll; string add_num(string a, string b) { ll carry = 0, sum; string ans; for(int i = 0; i < max(a.size(), b.size()); i++) { if(a.size() == b.size()) break; else if(a.size() < b.size()) { a = '0' + a; } else { b = '0' + b; } } ans = a; for(int i = a.size() - 1; i >= 0; i--) { sum = a[i] - '0' + b[i] - '0' + carry; carry = sum / 10; ans[i] = (sum % 10) + '0'; } if(carry == 1) ans = '1' + ans; return ans; } string small_mul(string a, int k) { ll carry = 0, sum; string ans = a; for(int i = a.size() - 1; i >= 0; i--) { sum = (a[i] - '0') * k + carry; carry = sum / 10; ans[i] = (sum % 10 + '0'); } if(carry != 0) { // Phải để là != vì có thể carry là một số lớn hơn 1 ans = char(carry + ‘0’) + ans; } while(ans.size() > 1 && ans[0] == '0') ans.erase(0, 1); return ans; } string big_mul(string a, string b) { string ans, ax = "0", bx; int tight = 0; for(int i = b.size() - 1; i >= 0; i--) { bx = small_mul(a, b[i] - '0'); bx.insert(bx.size(), tight, '0'); ++tight; ans = add_num(ax, bx); ax = ans; } return ans; } void solve() { string a, b; cin >> a >> b; cout << big_mul(a, b) << '\n'; } int main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); solve(); return 0; } ```