我已经搜索了文档,但我找不到任何解释。
我有一个D程序:
import std.stdio;
void main () {
writeln(int.max);
int a = 2;
int b = 180;
writeln(a^^b);
}
它写道:
2147483647
0
我溢出了int
,但我没有得到垃圾或包装,而是0
.
如果我使用 real
或 double
,显然输出是正确的。
我写了一个程序来用 C 进行实验(不是说 C 是 D,而是 D 编译为本机代码,C 是可移植的汇编程序,所以它们应该是可比的):
#include <stdio.h>
#include <math.h>
#include <limits.h>
int main(void) {
double i = pow(2, 128);
int j = (int) i;
printf("int max: %dn", INT_MAX);
printf("print dbl as int: %dn", i);
printf("cast dbl -> int: %dn", j);
printf("double: %fn", i);
return 0;
}
它给出:
int max: 2147483647
print dbl as int: -1254064128
cast dbl -> int: -2147483648
double: 340282366920938463463374607431768211456.000000
第二行和第三行很少是同一件事两次,因为我相信这是未定义的行为,这就是重点。
我知道D想要成为一个更好的C,而做到这一点的一个方法是消除未定义的行为。
但是,如果D是一种系统编程语言(它甚至有内联asm
),为什么D拒绝包装溢出?
它确实包装了溢出。你只是碰巧尝试了碰巧包装为零的案例家族。试试3^^180
.我得到了-949019631。仅仅因为一个数字碰巧在屏幕上看起来很漂亮并不意味着它不是垃圾!
考虑 2^^n == 1 <<n。当你一遍又一遍地向左移动一个位时会发生什么?最终,右边的所有位都变为零!然后,当您将其截断以适应 64 位值时,您将全部为零。
但无论如何,让我详细介绍一下。首先,对你的C的批评:
// snip. note that i is type double
printf("print dbl as int: %dn", i);
这条线在两个层面上是错误的:它传递一个 64 位双精度,其中 printf 期望一个 32 位 int,并且它重新解释将这些位转换为 int,这与转换为 int 完全不同。
如果要在 D 中执行此操作,则需要使用联合或通过中间指针强制转换显式重新解释位。如果您愿意,您甚至可以切掉其他 32 位!
下一行使用适当的显式强制转换,编写正确,但仍然是未定义的行为,因为当 double 值太大而无法容纳时,将双精度转换为 int 既不是 C 也不是 D(也不是底层硬件)做出任何承诺的。
回到D。D 中的^^
运算符只是将表达式重写为 std.math.pow(a, b)
。 std.math.pow
对不同类型的实现有不同的实现。由于这两个参数在这里都是不可或缺的,因此它根本不进行浮点计算 - 它有一个纯粹的 int/long 实现,就像乘法一样工作。
所以你的 C 比较不太正确,因为在 C 中,你使用 double
并尝试转换,而在 D 中,它根本没有触及浮点数。整数乘法被定义为通过二进制的补码和截断来工作,这正是这里发生的事情。它溢出了,留下了位中的所有零。