Kiểm tra tính bình thường của số thực
Do các phép tính với số thực có thể gặp phải các tình huống bất thường, chẳng hạn như vượt quá phạm vi của hàm, nhận được vô cực toán học, mất thứ tự, và các trường hợp khác, kết quả có thể không chứa một số. Thay vào đó, nó có thể chứa một giá trị đặc biệt thực sự mô tả bản chất của vấn đề. Tất cả các giá trị đặc biệt như vậy có tên chung là "không phải số" (Not A Number, NaN).
Chúng ta đã gặp chúng trong các phần trước của sách. Đặc biệt, khi xuất ra nhật ký (xem phần Chuyển đổi số thành chuỗi và ngược lại), chúng được hiển thị dưới dạng nhãn văn bản (ví dụ: nan(ind)
, +inf
và các loại khác). Một đặc điểm khác là chỉ cần một giá trị NaN duy nhất trong số các toán hạng của bất kỳ biểu thức nào cũng đủ để toàn bộ biểu thức ngừng được đánh giá chính xác và bắt đầu cho ra kết quả NaN. Ngoại lệ duy nhất là các "không phải số" biểu thị vô cực dương/âm: nếu bạn chia một thứ gì đó cho chúng, bạn sẽ nhận được số không. Tuy nhiên, có một ngoại lệ dự kiến ở đây: nếu chúng ta chia vô cực cho vô cực, chúng ta lại nhận được NaN.
Do đó, điều quan trọng đối với các chương trình là xác định thời điểm xuất hiện NaN trong các phép tính và xử lý tình huống theo cách đặc biệt: báo hiệu lỗi, thay thế bằng một giá trị mặc định chấp nhận được, hoặc lặp lại phép tính với các tham số khác (ví dụ, giảm độ chính xác/bước của thuật toán lặp).
Có 2 hàm trong MQL5 cho phép phân tích tính bình thường của một số thực: MathIsValidNumber
đưa ra câu trả lời đơn giản: có (true
) hay không (false
), và MathClassify
cung cấp phân loại chi tiết hơn.
Ở mức vật lý, tất cả các giá trị đặc biệt được mã hóa trong một số với tổ hợp bit đặc biệt không được sử dụng để biểu diễn các số thông thường. Đối với các kiểu double
và float
, các mã hóa này tất nhiên là khác nhau. Hãy xem xét double
phía sau hậu trường (vì nó được sử dụng nhiều hơn float
).
Trong chương Mẫu lồng nhau, chúng ta đã tạo một lớp Converter
để chuyển đổi cách nhìn bằng cách kết hợp hai kiểu khác nhau trong một union. Hãy sử dụng lớp này để nghiên cứu thiết bị bit của NaN.
Để thuận tiện, chúng ta sẽ chuyển lớp này sang một tệp tiêu đề riêng ConverterT.mqh
. Hãy kết nối tệp mqh này trong script thử nghiệm MathInvalid.mq5
và tạo một instance của bộ chuyển đổi cho cặp kiểu double/ulong
(thứ tự không quan trọng vì bộ chuyển đổi có thể hoạt động theo cả hai hướng).
static Converter<ulong, double> NaNs;
Tổ hợp bit trong NaN được chuẩn hóa, vì vậy hãy lấy một vài giá trị thường dùng được biểu diễn bằng các hằng số ulong
và xem các hàm tích hợp phản ứng với chúng như thế nào.
// basic NaNs
#define NAN_INF_PLUS 0x7FF0000000000000
#define NAN_INF_MINUS 0xFFF0000000000000
#define NAN_QUIET 0x7FF8000000000000
#define NAN_IND_MINUS 0xFFF8000000000000
// custom NaN examples
#define NAN_QUIET_1 0x7FF8000000000001
#define NAN_QUIET_2 0x7FF8000000000002
static double pinf = NaNs[NAN_INF_PLUS]; // +infinity
static double ninf = NaNs[NAN_INF_MINUS]; // -infinity
static double qnan = NaNs[NAN_QUIET]; // quiet NaN
static double nind = NaNs[NAN_IND_MINUS]; // -nan(ind)
void OnStart()
{
PRT(MathIsValidNumber(pinf)); // false
PRT(EnumToString(MathClassify(pinf))); // FP_INFINITE
PRT(MathIsValidNumber(nind)); // false
PRT(EnumToString(MathClassify(nind))); // FP_NAN
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Như kỳ vọng, kết quả là giống nhau.
Hãy xem mô tả chính thức của các hàm MathIsValidNumber
và MathClassify
rồi tiếp tục với các thử nghiệm.
bool MathIsValidNumber(double value)
Hàm này kiểm tra tính đúng đắn của một số thực. Tham số có thể thuộc kiểu double
hoặc float
. Kết quả true
có nghĩa là số đúng, và false
có nghĩa là "không phải số" (một trong những biến thể của NaN).
ENUM_FP_CLASS MathClassify(double value)
Hàm này trả về danh mục của một số thực (kiểu double
hoặc float
), là một trong các giá trị của enum ENUM_FP_CLASS
:
FP_NORMAL
là số bình thường.FP_SUBNORMAL
là số nhỏ hơn số tối thiểu có thể biểu diễn ở dạng chuẩn hóa (ví dụ, đối với kiểudouble
, đây là các giá trị nhỏ hơnDBL_MIN
, 2.2250738585072014e-308); mất thứ tự (độ chính xác).FP_ZERO
là số không (dương hoặc âm).FP_INFINITE
là vô cực (dương hoặc âm).FP_NAN
là tất cả các loại "không phải số" khác (được chia thành các họ "im lặng" và "tín hiệu" NaN).
MQL5 không cung cấp NaN cảnh báo, được sử dụng trong cơ chế ngoại lệ và cho phép bắt và phản hồi các lỗi nghiêm trọng trong chương trình. Không có cơ chế như vậy trong MQL5, vì vậy, ví dụ, trong trường hợp chia cho không, chương trình MQL chỉ đơn giản là kết thúc công việc (gỡ khỏi biểu đồ).
Có thể có nhiều NaN "im lặng", và bạn có thể xây dựng chúng bằng bộ chuyển đổi để phân biệt và xử lý các trạng thái không chuẩn trong các thuật toán tính toán của bạn.
Hãy thực hiện một số phép tính trong MathInvalid.mq5
để hình dung cách các số thuộc các danh mục khác nhau có thể được nhận:
// calculations with double
PRT(MathIsValidNumber(0)); // true
PRT(EnumToString(MathClassify(0))); // FP_ZERO
PRT(MathIsValidNumber(M_PI)); // true
PRT(EnumToString(MathClassify(M_PI))); // FP_NORMAL
PRT(DBL_MIN / 10); // 2.225073858507203e-309
PRT(MathIsValidNumber(DBL_MIN / 10)); // true
PRT(EnumToString(MathClassify(DBL_MIN / 10))); // FP_SUBNORMAL
PRT(MathSqrt(-1.0)); // -nan(ind)
PRT(MathIsValidNumber(MathSqrt(-1.0))); // false
PRT(EnumToString(MathClassify(MathSqrt(-1.0))));// FP_NAN
PRT(MathLog(0)); // -inf
PRT(MathIsValidNumber(MathLog(0))); // false
PRT(EnumToString(MathClassify(MathLog(0)))); // FP_INFINITE
// calculations with float
PRT(1.0f / FLT_MIN / FLT_MIN); // inf
PRT(MathIsValidNumber(1.0f / FLT_MIN / FLT_MIN));// false
PRT(EnumToString(MathClassify(1.0f / FLT_MIN / FLT_MIN))); // FP_INFINITE
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Chúng ta có thể sử dụng bộ chuyển đổi theo hướng ngược lại: để lấy biểu diễn bit của nó bằng giá trị double
, và do đó phát hiện "không phải số":
PrintFormat("%I64X", NaNs[MathSqrt(-1.0)]); // FFF8000000000000
PRT(NaNs[MathSqrt(-1.0)] == NAN_IND_MINUS); // true, nind
2
Hàm PrintFormat
tương tự như StringFormat; điểm khác biệt duy nhất là kết quả được in trực tiếp vào nhật ký, không phải vào một chuỗi.
Cuối cùng, hãy đảm bảo rằng "không phải số" luôn không bằng nhau:
// NaN != NaN always true
PRT(MathSqrt(-1.0) != MathSqrt(-1.0)); // true
PRT(MathSqrt(-1.0) == MathSqrt(-1.0)); // false
2
3
Để nhận NaN hoặc vô cực trong MQL5, có một phương pháp dựa trên việc ép kiểu các chuỗi "nan" và "inf" sang double
:
double nan = (double)"nan";
double infinity = (double)"inf";
2