Chuyển đổi kiểu số học
Trong các biểu thức tính toán và so sánh số học, các giá trị của các kiểu khác nhau thường được sử dụng làm toán hạng. Để xử lý chúng một cách chính xác, cần phải đưa các kiểu về một "mẫu số chung" nhất định. Trình biên dịch cố gắng thực hiện điều này mà không cần sự can thiệp của lập trình viên trừ khi lập trình viên đã chỉ định các quy tắc chuyển đổi rõ ràng (xem Chuyển đổi kiểu rõ ràng). Trong trường hợp này, trình biên dịch, bất cứ khi nào có thể, cố gắng duy trì độ chính xác tối đa khi nói đến các số. Đặc biệt, nó tạo ra sự gia tăng về dung lượng của các số nguyên và sự chuyển đổi từ số nguyên sang số thực (nếu chúng có liên quan).
Mở rộng số nguyên ngụ ý việc chuyển đổi bool
, char
, unsigned char
, short
, unsigned short
thành int
(hoặc unsigned int
nếu int
không đủ lớn để lưu trữ các số cụ thể). Các giá trị lớn có thể được chuyển đổi thành long
và unsigned long
.
Nếu kiểu biến không thể lưu trữ kết quả của kiểu đã thu được khi biểu thức được đánh giá, trình biên dịch sẽ đưa ra cảnh báo:
double d = 1.0;
int x = 1.0 / 10; // truncation of constant value
int y = d / 10; // possible loss of data due to type conversion
2
3
Biểu thức để khởi tạo các biến x
và y
chứa số thực 1.0, do đó các toán hạng khác (hằng số 10 trong trường hợp này) được chuyển đổi thành double
và kết quả của phép chia cũng sẽ có kiểu double
. Tuy nhiên, kiểu của các biến là int
và do đó, một phép chuyển đổi ngầm định sang nó diễn ra.
Phép tính 1.0 / 10
được thực hiện bởi trình biên dịch trong quá trình biên dịch và do đó nó nhận được một hằng số kiểu double (0.1)
. Tất nhiên, trong thực tế, không có khả năng hằng số khởi tạo sẽ vượt quá kích thước của biến nhận. Do đó, cảnh báo của trình biên dịch "cắt bớt giá trị hằng số" có thể được coi là kỳ lạ. Nó chỉ cho thấy vấn đề theo cách đơn giản nhất.
Tuy nhiên, do kết quả của các phép tính dựa trên biến, mất dữ liệu tương tự cũng có thể xảy ra. Cảnh báo trình biên dịch thứ hai mà chúng ta thấy ở đây ("có thể mất dữ liệu do chuyển đổi kiểu") xảy ra thường xuyên hơn nhiều. Hơn nữa, mất dữ liệu không chỉ có thể xảy ra khi chuyển đổi từ kiểu thực sang kiểu số nguyên mà còn ngược lại.
double f = LONG_MAX; // truncation of constant value
long m1 = 1000000000;
f = m1 * m1; // possible loss of data due to type conversion
2
3
Như chúng ta đã biết, kiểu double
không thể biểu diễn chính xác các số nguyên lớn (mặc dù phạm vi giá trị hợp lệ của nó lớn hơn nhiều so với kiểu long
).
Một cảnh báo khác mà chúng ta có thể gặp phải do không khớp kiểu: "tràn hằng số tích phân".
long m1 = 1000000000;
long m2 = m1 * m1; // ok: m2 = 1000000000000000000
long m3 = 1000000000 * 1000000000; // integral constant overflow
// m3 = -1486618624
2
3
4
Hằng số nguyên trong MQL5 có kiểu int
, do đó phép nhân triệu với triệu được thực hiện có tính đến phạm vi của kiểu này, bằng INT_MAX
(2147483647
). Giá trị 10000000000000000000
gây tràn số và m3 lấy phần dư sau khi chia giá trị này cho phạm vi (thông tin chi tiết hơn về điều này trong thanh bên dưới).
Thực tế là biến nhận m3
có kiểu long
không có nghĩa là các giá trị trong biểu thức phải được chuyển đổi sang kiểu này trước. Điều này chỉ xảy ra tại thời điểm gán. Để phép nhân được thực hiện theo các quy tắc của long
, bạn cần phải chỉ định kiểu long
trực tiếp trong chính biểu thức. Điều này có thể được thực hiện bằng cách chuyển đổi rõ ràng hoặc bằng cách sử dụng các biến. Đặc biệt, việc thu được cùng một tích bằng cách sử dụng biến m1
có kiểu long
(chẳng hạn như m1 * m1
) sẽ dẫn đến kết quả đúng trong m2
.
INFO
Signed and unsigned integers (Số nguyên có dấu và không dấu)
Các chương trình không phải lúc nào cũng được viết hoàn hảo, với khả năng bảo vệ khỏi mọi lỗi có thể xảy ra. Do đó, đôi khi xảy ra trường hợp số nguyên thu được trong quá trình tính toán không khớp với biến của kiểu số nguyên đã chọn. Sau đó, nó nhận được phần dư của phép chia giá trị này cho giá trị tối đa (M
) có thể được viết bằng số byte tương ứng (kích thước kiểu), cộng với 1. Vì vậy, đối với các kiểu số nguyên có kích thước từ 1 đến 4 byte, M + 1
lần lượt là 256
, 65536
, 4294967296
và 18446744073709551616
.
Nhưng có một sắc thái đối với các kiểu có dấu. Như chúng ta đã biết, đối với các số có dấu, tổng phạm vi giá trị được chia gần như bằng nhau giữa các vùng dương và âm. Do đó, giá trị "dư" mới có thể trong 50% trường hợp vượt quá giới hạn dương hoặc âm. Trong trường hợp này, số chuyển thành "ngược lại": nó đổi dấu và kết thúc ở khoảng cách M so với giá trị ban đầu.
Điều quan trọng là phải hiểu rằng phép biến đổi này chỉ xảy ra do cách diễn giải khác nhau về trạng thái bit trong biểu diễn bên trong và bản thân trạng thái là giống nhau đối với số có dấu và không dấu.
Chúng ta hãy giải thích điều này bằng một ví dụ cho các kiểu số nguyên nhỏ nhất: char
và uchar
.
Vì unsigned char
có thể lưu trữ các giá trị từ 0 đến 255, 256
ánh xạ thành 0
, -1
ánh xạ thành 255
, 300
ánh xạ thành 44
, v.v. Nếu chúng ta thử viết 300 vào một signed char
thông thường, chúng ta cũng nhận được 44, vì 44 nằm trong phạm vi từ 0 đến 127 (phạm vi dương của char
). Tuy nhiên, nếu bạn đặt các biến char
và uchar
thành 3000, thì hình ảnh sẽ khác. Phần dư của 3000 chia cho 256 là 184. Nó kết thúc bằng uchar
không đổi. Tuy nhiên, đối với char
, cùng một tổ hợp bit sẽ cho kết quả là -72
. Có thể dễ dàng kiểm tra rằng 184
và -72
khác 256
.
Trong ví dụ sau, bạn có thể dễ dàng phát hiện ra vấn đề nhờ cảnh báo của trình biên dịch.
char c = 3000; // truncation of constant value
Print(c); // -72
uchar uc = 3000; // truncation of constant value
Print(uc); // 184
2
3
4
Tuy nhiên, nếu bạn nhận được một số quá lớn trong quá trình tính toán, sẽ không có cảnh báo nào.
char c55 = 55;
char sm = c55 * c55; // ok!
Print(sm); // 3025 -> -47
uchar um = c55 * c55; // ok!
Print(um); // 3025 -> 209
2
3
4
5
Hiệu ứng tương tự có thể xảy ra khi các số nguyên có dấu và không dấu có cùng kích thước được sử dụng trong cùng một biểu thức vì toán hạng có dấu được chuyển đổi thành không dấu. Ví dụ:
uint u = 11;
int i = -49;
Print(i + i); // -98
Print(u + i); // 4294967258 = 4294967296 - 38
2
3
4
Khi hai số nguyên âm cộng lại, chúng ta sẽ có được kết quả mong đợi. Biểu thức thứ hai ánh xạ tổng của -38
thành số không dấu "đối diện" 4294967258
.
Không nên trộn lẫn các kiểu có dấu và không dấu trong cùng một biểu thức vì những vấn đề tiềm ẩn sau đây.
Ngoài ra, nếu chúng ta trừ một số nào đó khỏi một số nguyên không dấu, chúng ta cần đảm bảo rằng kết quả không phải là số âm. Nếu không, nó sẽ được chuyển đổi thành một số dương và có thể làm sai lệch ý tưởng của thuật toán, đặc biệt là ý tưởng của vòng lặp while
kiểm tra biến cho điều kiện "lớn hơn hoặc bằng không": vì các số không dấu luôn không âm, chúng ta có thể dễ dàng gặp phải một vòng lặp vô hạn, tức là chương trình bị treo.