实数的正常性检验
由于实数计算可能出现异常情况,诸如超出函数范围、获得数字无穷大、丢失顺序等等,导致结果可能不包含数字。相反,结果可能包含特殊值,该特殊值实际上描述了问题的性质。所有这些特殊值具有一个通用名称:“非数字”(简称 NaN)。
我们在本书前面的章节中已接触了这种值。尤其是在输出到日志时(参见章节 数字转换为字符串以及相反转换),它们显示为文本标签(如 nan(ind)、+inf 等等)。另一个特征是,任何表达式的操作数中只要存在一个 NaN 值,整个表达式就会停止正确计算,开始给出 NaN 结果。唯一例外是表示正负无穷“非数字”:如果用其他数除以正负无穷,结果为零。然而有一个预期例外:如果我们将无穷除以无穷,我们又得到 NaN。
因此,重要的是让程序确定计算中出现 NaN 的时刻,并以特殊方式处理该情况:提示错误,替换某些可接受默认值,或者以其它参数重复计算(例如,减少迭代算法的精度/步骤)。
在 MQL5 中有 2 个函数可以分析实数的正常性:MathIsValidNumber 给出一个简单的回答:是 (true) 或否 (false),而 MathClassify 生成更详细的类别划分。
在物理层面,所有特殊值都通过位的特殊组合编码在数字中,这些位组合不用于表示普通数字。对于 double 和 float 类型,这些编码当然不同。我们来深入了解一下 double(因为它比 float 更常用)。
在 嵌套模板一章中,我们创建了 Converter 类,用于通过在一个联合体中组合两个不同的类型来切换视图。我们将该类用于研究 NaN 位设备。
出于方便,我们将把该类移动到一个单独的头文件 ConverterT.mqh。我们在测试脚本 MathInvalid.mq5 中连接该 mqh 文件,并为一系列类型 double/ulong 创建一个转换器实例(顺序不重要,因为该转换器能够双向工作)。
static Converter<ulong, double> NaNs; |
NaN 中的位组合经过标准化,因此我们取几个由常量 ulong 表示的常用值,看看内置函数如何应对。
// basic NaNs
|
正如所预期的那样,结果是相同的。
我们看看 MathIsValidNumber 和 MathClassify 函数的正式说明,然后继续测试。
bool MathIsValidNumber(double value)
该函数检查实数的正确性。参数可以是 double 或 float 类型。结果 true 表示数字正确,false 表示“非数字”(某种 NaN)。
ENUM_FP_CLASS MathClassify(double value)
该函数返回实数的类别(double 或 float 类型),其是 ENUM_FP_CLASS 枚举值之一:
- FP_NORMAL 是一个正规数。
- FP_SUBNORMAL 是一个小于以正规化形式可表示的最小数字的数字(例如,对于 double 类型,这些是小于 DBL_MIN, 2.2250738585072014e-308 的值);顺序(精度)丢失。
- FP_ZERO 为零(正或负)。
- FP_INFINITE 为无穷大(正或负)。
- FP_NAN 指所有其它类型的“非数字”(细分为“静默型”和“信号型”NaN 系列)。
MQL5 不提供警报型 NaN,警报型 NaN 用于异常机制中,允许截获并回应程序内的关键错误。在 MQL5 中没有此类机制,因此,例如,如果出现除零情况,MQL 程序会直接终止其工作(从图表卸载)。
可能有很多“静默型”NaN,你可以使用转换器构造,以区分和处理计算算法中的非标准状态。
我们在 MathInvalid.mq5 中执行某些计算,以直观展示不同类别的数字的获得方式。
// calculations with double
|
我们可以在相反方向使用转换器:通过 double 值获取其位表示,进而探测“非数字”:
PrintFormat("%I64X", NaNs[MathSqrt(-1.0)]); // FFF8000000000000
|
PrintFormat 函数类似于 StringFormat;唯一的不同是结果立即打印到日志,而不是打印到字符串。
最后,我们确保“非数字”始终不相等:
// NaN != NaN always true
|
要在 MQL5 中获取 NaN 或无穷大,有一种基于将字符串 "nan" 和 "inf" 转化为 double 的方法。
double nan = (double)"nan";
|