2.6.2.算术类型转换

在算术计算和比较表达式中,经常用不同类型的值作为操作数。为正确处理这些值,必须让这些类型达到某种“共同标准”。除非程序员指定了显式转换规则,否则编译器会尝试在没有程序员干预的情况下完成此操作(参见 显式类型转换)。在这种情况下,编译器会尽可能保持数字的最大精度。特别是,它产生了整数的表示范围扩大以及从整数到实数的转换(如涉及的话)。

整数扩展意味着从 boolcharunsigned charshortunsigned short 隐式转换为 int(或转换为 unsigned int,如果 int 不足以存储特定数字)。大值可以转换为 longunsigned long

如果变量的类型无法存储计算表达式所获得类型的结果,编译器将发出警告:

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

初始化 xy 变量的表达式包含实数 1.0,因此其他操作数(本例中为常数 10)被转换为 double,除法的结果也将是 double 类型。然而变量的类型是 int,因此它会被隐式转换。

计算 1.0/10 是由编译器在编译期间完成的,因此它得到一个 double 类型的常量 (0.1)。当然,在实践中,初始化常量不太可能超过接收变量的大小。因此,编译器警告“常量值截断”可以被认为是异常的。这只是复杂问题的简单化表示。

但是,基于变量的计算也会导致类似的数据丢失。我们在这里看到的第二个编译器警告(“类型转换可能会导致数据丢失”)出现得更为频繁。此外,不仅在从实数类型转换到整数类型时可能会丢失数据,反向转换时也可能会丢失数据。

double f = LONG_MAX// truncation of constant value
long m1 = 1000000000;
f = m1 * m1;         // possible loss of data due to type conversion

我们知道,double 类型不能准确表示大整数(虽然它的有效值范围比 long 大得多)。

由于类型不匹配,我们可能遇到另一个警告:“整数常量溢出”。

long m1 = 1000000000;
long m2 = m1 * m1;                 // ok: m2 = 1000000000000000000
long m3 = 1000000000 * 1000000000// integral constant overflow
                                   // m3 = -1486618624

MQL5 中的整数常量为 int 型,因此在执行百万乘百万的运算时会考虑该类型的范围,这个范围是 INT_MAX (2147483647)。值 1000000000000000000 导致溢出,m3 为该值除以范围后的得出的余数(更多相关信息见下方侧栏)。

接收变量 m3long 类型,但一事实并不意味着表达式中的值必须事先转换成该类型。转换只在赋值的时候进行。为了根据长整型的规则执行乘法,您需要以某种方式在表达式中直接指定 long 类型。这可以通过显式转换或使用变量来完成。特别是,使用 long 类型的变量 m1 获得相同的乘积(如 m1 * m1)会得到 m2 的正确结果。

有符号和无符号整数
 
程序编写并不总是完美无缺,也无法防止所有可能的失败情况。因此,有时计算过程中获得的整数不适用于所选整数类型的变量。那么取这个值除以可写入相应字节数(类型大小)的最大值 (M) 的余数,再加上 1。因此,对于大小为 1 到 4 字节的整数类型,M + 1 分别为 256、65536、4294967296 和 18446744073709551616。
 
但是对于有符号类型有一个细微的差别。我们知道,有符号数的值范围在正负区域之间大致均等地分布。因此,新的“余数”值可能在 50% 的情况下超过正数或负数限制。在这种情况下,数字变为了相反数:数字的符号改变,最终与原数相差 M。
 
关键要理解,这种转换本质上由于对内部表示中的位状态的不同解释而发生的,并且状态本身对于有符号和无符号数是相同的。
 
我们来用一个最小整数类型的例子来说明:charuchar
 
由于unsigned char可以存储从 0 到 255 的值,256 映射到 0,-1 映射到 255,300 映射到 44,以此类推。如果我们尝试将 300 写入一个常规的有符号 char,也会得到 44,因为 44 在 0 到 127 的范围内(char 的正值范围)。但是,如果将 charuchar 变量设置为 3000,结果会有所不同。3000 除以 256 的余数是 184。这个余数在 uchar 的情况下保持不变。但如果是 char,相同的位组合会得到结果 -72。很容易发现 184 和 -72 相差 256。

在下面的例子中,多亏了编译器警告,我们轻松地发现了问题。

char c = 3000;      // truncation of constant value
Print(c);           // -72
uchar uc = 3000;    // truncation of constant value
Print(uc);          // 184

但如果在计算过程中得到一个特别大的数字,则不会发出警告。

char c55 = 55;
char sm = c55 * c55;  // ok! 
Print(sm);            // 3025 -> -47
uchar um = c55 * c55// ok!
Print(um);            // 3025 -> 209

当在同一个表达式中使用相同大小的有符号和无符号整数时,也会产生类似的效果,因为有符号操作数被转换为无符号操作数。例如:

uint u = 11;
int i = -49;
Print(i + i); // -98
Print(u + i); // 4294967258 = 4294967296 - 38

两个负整数相加,我们得到预期的结果。第二个表达式将 -38 的和映射到“相反的”无符号数 4294967258。

由于这些潜在的问题,不建议在同一个表达式中混合使用有符号和无符号类型。

除此之外,如果我们从一个无符号整数中减去某个数,则需要确保结果不会是负数。否则,它将被转换为正数,并且会曲解算法的意思,特别是 while 循环的意思, 该循环检查变量的“大于等于零”条件:由于无符号数始终是非负的,我们很容易得到一个无限循环,即程序挂起。