Xử Lý Tràn Số Trong C/C++

Xử Lý Tràn Số Trong C/C++

Trong quá trình học về C/C++ một số bạn có hỏi mình là tràn số là gì và cách khác phục nó như thế nào. Hôm nay, mình sẽ cùng các bạn xử lý tràn số trong C/C++ nhé.

1. Khái niệm và ví dụ tràn số

Tràn số hiểu đơn giản là khi bạn cố gắng gán giá trị cho một biến nào đó mà giá trị đó nằm ngoài phạm vi của kiểu dữ liệu biến đó.
Ví dụ như dưới đây:

#include<iostream>
using namespace std;
int main(){
	int a = 2147483648;
	cout<<a;
}

Kiểu int trong c++ sẽ nhận giá trị -2147483648 ... 2147483647.

Như hình trên thì chúng ta đã gán cho biến n một giá trị nằm ngoài kiểu int nên dẫn tới lỗi tràn số, biến n không thể lưu được giá trị đó, chương trình trên nếu chạy sẽ bị ra kết quả: -2147483648

2. Một số cách giải quyết tràn số

  1. Tràn số lúc tính toán.

    Ví dụ với đoạn code ở bên dưới.
    #include<iostream>
    using namespace std;
    int main(){
    	int a,b;
    	cin>>a>>b;
    	long long k = a*b;
    	cout<<k;
    }
    

    Khi ta nhập a=b=106 thì kết quả sẽ là -727379968 chứ không phải là 1012


    Nhiều bạn sẽ không hiểu là đoạn code của mình bị lỗi chỗ nào trong khi kết quả phép tính a*b=1012 hoàn toàn có thể lưu vào biến k có kiểu là long long.

    Thực ra là trước khi trả về giá trị để gán vào biến k thì máy tính phải tính phép tính a*b trước và kiểu trả về của phép tính  này là type = Max(type(a),type(b)) = Max(int,int) = int máy tính sẽ nhận 1012 là kiểu int trước khi lưu và biến k, mà 1012 không thể lưu vào int nên dẫn tới việc tràn số.

    Cách giải quyết đó là thay đổi kiểu dữ liệu của a hoặc b thành kiểu dữ liệu lớn hớn ví dụ:
    #include<iostream>
    using namespace std;
    int main(){
    	int a;
    	long long b;
    	cin>>a>>b;
    	long long k = a*b;
    	cout<<k;
    }​


    Lúc này phép tính a*b sẽ nhận kiểu dữ liệu là : type = Max(type(a),type(b)) = Max(int,long long) = long long, 
    và sẽ không bị tràn số nữa.

    Hoặc cách khác thường được nhiều coder dùng đó là ép kiểu cho biến trướ khi thực hiện phép tính:
    #include<iostream>
    using namespace std;
    int main(){
    	int a,b;
    	cin>>a>>b;
    	long long k = (long long)a*b;
    	cout<<k;
    }​


  2. Các bài toán module.
    Ví dụ cho bài toán sau: Tính tổng các số tự nhiên từ 1 đến n. Với n ≤ 1018.
    Bài toán này ta sẽ trả về giá trị là n*(n+1)/2;
    Vấn đề là nếu n = 1018 thì không có kiểu dữ liệu nào có thể lưu được giá trị của n*(n+1)/2 cả, nên thường những bài như này đề nó phải cho là: "Kết quả sẽ lấy phần dư cho 1000000007".

    Đến đây mình sẽ đưa cho các bạn 3 công thức thường dùng cho các bài toán modul:

    CT1: (a+b)%d = ((a%d)+(b%d))%d.

    CT2: (a-b)%d = ((a%d)-(b%d))%d.

    CT3: (a*b)%d = ((a%d)*(b%d))%d.

    Ta cần tính ở bài toán hiện tại là (n*(n+1)/2)%d;
    Nhưng ta nên không có công thức để áp dụng cho phép chia, nên ta phải rút gọn n*(n+1) cho 2 trước, cụ thể cách giải bài này như sau:

    #include<iostream>
    using namespace std;
    int main(){
    	int d = 1000000007;
    	long long n;
    	cin>>n;
    	long long k = n;
    	long long h = (n+1);
    	if (k%2==0) k/=2;
    	else h/=2;
    	// tinh ket qua cua (k*h)%d
    	long long kq = ((k%d)*(h%d))%d;
    	cout<<kq;
    }​

3. Kết luận

Là một coder nên cẩn thận trong việc chọn kiểu dữ liệu đặt cho biến, cũng như khi thực hiện phép tính, cần phải tính toán xem giới hạn tối thiểu và tối đa của biểu thức ta cần tính toán, nên biết cách ép kiểu biểu thức cho phù hợp với kiểu dữ liệu của biến nhận giá trị đó.