Lỗi kì lạ - bị mất 1 đơn vị khi chuyển từ double sang int trên C

Mình có đoạn code sau, thực hiện bài toán trừ 2 số double, xong chuyển sang kiểu int

0.026600 - 0.001000 = 0.025600 * 10000000 = 256000.000000
(int) 256000.000000 => kết quả mìh mong muốn là 256000
nhưng nó lại ra 255999

Điều kì lạ là lỗi này xảy ra một cách rất ngẫu nhiên, mình xem đi xem lại nhưng không hiểu sao lại bị vậy.
Đây là đoạn code của mình

 int main() {
	double math;
	double input =  0.02660;
	double i =  0.00100, imax = 0.00201; //lap 100 gia tri
	int numzero = 10000000;
	
	int input_int = 0;
	int line = 0;
		
	for(i; i < imax; i = i + 0.00001) {
		math = (input - i) * numzero; //giam gia tri input va bo dau thap phan
		input_int = (int) math;
	
		//printf("Line %d: %lf - %lf = %lf (convert =>) %d\n", line, input, i, math, input_int);
		printf("Line %d: %lf => %d\n", line, math, input_int);
		line++;
	}
}

Kết quả mình chạy


có số nó convert đúg, nhưng có số lại bị sai
mình xem đi xem lại nhưng không hiểu chuyện gì đang diễn ra?
Mong các bạn giúp đỡ, mình cảm ơn.

Đó là sai số của kiểu float/double thôi. Điều này không tránh được.

4 Likes

là sao bạn. phần tính toán giá trị lúc in ra đúg mà, nó chỉ bị sai khi chuyển sang kiểu int

Cái giá trị double bạn in ra mà hình thực ra nó không chính xác như vậy mà nó đã được làm tròn lên.
Ví dụ nhìn thấy 100.0000 nhưng thực ra giá trị thực của nó có thể là
100.0000000000000000000001…
hoặc
99.9999999999999999999999…
Khi cast thì nó cứ theo giá trị thực mà nó cắt thôi.
0.999999999999999999999 … thì vẫn là 0 khi cast.

6 Likes

tại sao lại như vậy? rõ ràng phần toán mìh code đúg mà, nó phải tính ra kết quả đúg, chứ sao lại 99.999999, chưa kể là có số thì bị còn có số thì lại không bị gì

Cũng tương tự như 1/3 = 0.3333… nhưng là 0.13 :smiley:
hay 1/5 = 0.3333…16.

4 Likes

Sai số này do cách máy tính lưu số double.
image

Một số double được lưu dưới dạng mã nhị phân gồm 64 bit như hình trên,
Và được biểu diễn dưới dạng
image.

e là số được lưu trong phần exponent,
và tổng b_i được lưu trong phần fraction.

nên con số double thực tế và doule được lưu có thể sai lệch tương đối nhỏ nhưng lại ảnh hưởng lớn đối với các hàm round, floor hay ceil
Thông thường mình chọn offset là 1 số cực nhỏ để làm tròn,

offset = 10e-16;
floor(x) = floor(x + offset);
ceil(x) = ceil(x - offset); 
4 Likes

Số liệu trên máy tính được lưu trữ bằng nhị phân,
Tức là 0 hoặc 1 và do vậy nó không thể tạo ra tất cả các giá trị số double, float chính xác mà chỉ có thể tạo ra số có giá trị gần đúng.

Giống như vật chất cấu tạo từ nguyên tử nên nó sẽ không thể có cái vật gì lẻ nửa nguyên tử.

Khi chuyển đổi thì thiếu 1 phần tỷ của 1 đều tính là 0.
Mặc dù bạn tính ra đều bằng 10 nhưng thực tế máy tính không thể tạo ra số 10 chính xác. Nó có thể là 9.9999999999999999 hoặc 10.000000000001.
9.999 thì sẽ làm tròn thành 9 vì phần dư chưa đủ 1.
Còn 10.00001 thì vẫn ra được 10.
Vậy nên so sánh double hoặc float không sử dụng phép so sánh bằng (==).

Cũng đơn giản mà nhỉ.

4 Likes

vậy là do cách lưu trữ trên bộ nhớ máy tính, có cách nào khắc phục hay giải quyết bài toán trên không bạn

Cách thì có nhiều nhưng đơn giản nhất là trước khi chuyển đổi hãy cộng thêm một giá trị nhỏ vào số cần chuyển đổi. Coi như bù sai số cho những số dạng x.9999999.
Ví dụ cộng thêm 0.00001

Hoặc có thể kiểm tra sai số trước và sau chuyển đổi. Nếu nó lớn hơn bao nhiêu đó (ví dụ 0.999) thì sẽ cộng thêm 1 vào kết quả sau chuyển đổi.

4 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?