我尝试乘以三个数字,但得到一个奇怪的结果。为什么我会得到如此不同的结果?
unsigned int a = 7;
unsigned int b = 8;
double d1 = -2 * a * b;
double d2 = -2 * (double) a * (double) b;
double d3 = -2 * ( a * b );
// outputs:
// d1 = 4294967184.000000
// d2 = -112.000000
// d3 = 4294967184.000000
答案 0 :(得分:9)
在第一个示例中,数字-2
将转换为unsigned int。乘法结果为-112,当表示为无符号时为2 ^ 32 - 112 = 4294967184.然后,此结果最终转换为double
以进行赋值。
在第二个示例中,所有数学都在双精度数上完成,从而得到正确的结果。如果您这样做,您将获得相同的结果:
double d3 = -2.0 * a * b
因为-2.0
是double
字面值。
答案 1 :(得分:3)
双签署。这意味着第一位(最高有效位,也就是符号位)确定该数字是正数还是负数。
unsigned int无法处理负值,因为它使用第一位(最高位)来扩展它可以表示的“正”数字的范围。所以在
double d1 = -2 * a * b;
执行时,你的机器将整个(-2 * a * b)置于无符号的int结构(如a和b)中,它产生以下二进制1111 1111 1111 1111 1111 1111 1001 0000(因为它是两者的补码) 112的是0000 0000 0000 0000 0000 0000 0111 0000)。但这里的问题是它是unsigned int因此它被视为一个非常大的正整数(即4294967184),因为它不会将前1作为符号位。
然后你把它放在一个双,这就是你打印.00000的原因。
另一个例子是有效的,因为你将a转换为double而b转换为double,所以当-2乘以double时,你的计算机将把它放在双重结构中,因此,将考虑符号位。
double d3 = -2 * (double) (a * b)
也会奏效。
要了解签名和未签名,请检查this
答案 2 :(得分:1)
double d1 = -2 * a * b;
右侧的所有内容都是整数类型,因此右侧将被计算为整数类型。 a
和b
是无符号的,因此指示结果的特定类型。那-2
怎么样?它被转换为unsigned int
。使用2s补码算法将负整数转换为无符号整数。 -2
成为一个非常大的正无符号整数。
double d2 = -2 * (double) a * (double) b;
现在右侧是混合整数和浮点数,因此右侧将被计算为浮点类型。那-2
怎么样?它被转换为双倍。现在转换非常简单:转换为-2
的{{1}}变为double
。
答案 3 :(得分:1)
在C和C ++中,内置运算符始终应用于两个相同类型的变量。如果两个变量中的一个(或两个)变量最初不同(或太小),则一组非常精确的规则会指导促销。
在这种精确的情况下,-2
默认为signed int
类型(int
的同义词),而a
和b
的类型为{{1} }}。在这种情况下,规则规定unsigned int
应该提升为-2
,并且因为在您的系统上,您可能有32位unsigned int
和2补码表示,这最终成为int
(4 294 967 294)。然后将此数字乘以2**32 - 2
,结果取模a
(4 294 967 282),然后2**32
,再次模数b
(4 294 967 184)。
这真是一个奇怪的系统,并导致了无数的错误。例如,溢出本身导致今年6月30日的Linux漏洞导致全世界许多计算机被绞死。我听说它也破坏了几个Java系统。