为什么溢出会导致0?
Why does overflow result in 0?
我已经搜索了文档,但找不到任何内容来解释这一点。
我有一个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: %d\n", INT_MAX);
printf("print dbl as int: %d\n", i);
printf("cast dbl -> int: %d\n", j);
printf("double: %f\n", 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: %d\n", i);
这一行在两个层面上是错误的:它传递了一个 64 位双精度值,而 printf 期望一个 32 位 int,并且它重新解释将这些位转换为 int,这与转换为 int 完全不同。
如果您想在 D 中执行此操作,则需要使用联合或通过中间指针强制转换来显式地重新解释这些位。如果你愿意,你甚至可以切掉其他 32 位!
下一行使用了正确的显式转换,写得正确,但仍然是未定义的行为,因为当 double 太大而不适合时将 double 转换为 int 不是 C 和 D(也不是底层硬件)造成的关于任何承诺。
然后回到 D。D 中的 ^^
运算符只是将表达式重写为 std.math.pow(a, b)
。 std.math.pow
对不同的类型有不同的实现。由于两个参数在这里都是整数,它根本不进行浮点计算 - 它有一个纯粹的 int/long 实现,就像乘法一样。
所以你的 C 比较不太正确,因为在 C 中,你使用 double
并尝试转换,而在 D 中,它根本没有触及浮点数。整数乘法被定义为通过二进制的补码和截断来工作,这正是这里发生的事情。它溢出了,留下的位全部为零。
我已经搜索了文档,但找不到任何内容来解释这一点。
我有一个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: %d\n", INT_MAX);
printf("print dbl as int: %d\n", i);
printf("cast dbl -> int: %d\n", j);
printf("double: %f\n", 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: %d\n", i);
这一行在两个层面上是错误的:它传递了一个 64 位双精度值,而 printf 期望一个 32 位 int,并且它重新解释将这些位转换为 int,这与转换为 int 完全不同。
如果您想在 D 中执行此操作,则需要使用联合或通过中间指针强制转换来显式地重新解释这些位。如果你愿意,你甚至可以切掉其他 32 位!
下一行使用了正确的显式转换,写得正确,但仍然是未定义的行为,因为当 double 太大而不适合时将 double 转换为 int 不是 C 和 D(也不是底层硬件)造成的关于任何承诺。
然后回到 D。D 中的 ^^
运算符只是将表达式重写为 std.math.pow(a, b)
。 std.math.pow
对不同的类型有不同的实现。由于两个参数在这里都是整数,它根本不进行浮点计算 - 它有一个纯粹的 int/long 实现,就像乘法一样。
所以你的 C 比较不太正确,因为在 C 中,你使用 double
并尝试转换,而在 D 中,它根本没有触及浮点数。整数乘法被定义为通过二进制的补码和截断来工作,这正是这里发生的事情。它溢出了,留下的位全部为零。