为什么 ((unsigned int)x) * y == ((unsigned int)(x * y) 总是为真?
why is ((unsigned int)x) * y == ((unsigned int)(x * y) always true?
我刚刚写了这些代码:
int x = -1;//x must be negative
unsigned int y = 1;//y must be positive
bool b;
for(; ; x--, y++){
b = ((unsigned int)x) * y == ((unsigned int)(x * y));
}
然后我发现b
总是正确的。在我看来,((unsigned int)x) * y 会溢出,但 ((unsigned int)(x * y)) 不会。我真的很难相信这是真的。这只是巧合还是有一定规律可循?
Is this just coincidence or is there any law behind this phenomenon?
不,这不是巧合,这可以用通常的算术转换执行的隐式转换来解释。
乘法运算符对其操作数执行 usual arithmetic conversions 以将它们变为通用类型,对于此子表达式:
(unsigned int)(x * y)
将导致 x
被转换为 unsigned int。这使得:
(unsigned int)(x * y)
相当于:
((unsigned int)x) * y
很多时候使用正确的警告标志可以帮助解决难题,使用 -Wall
和 gcc 会给出以下警告:
warning: self-comparison always evaluates to true [-Wtautological-compare]
b = ((unsigned int)x) * y == ((unsigned int)(x * y));
^
使用带有 clang 的 -Wconversion
标志会给出以下警告 (see it live):
warning: implicit conversion changes signedness: 'int' to 'unsigned int' [-Wsign-conversion]
b = ((unsigned int)x) * y == ((unsigned int)(x * y));
^ ~
作为参考,C++ 标准草案部分 5.6
[expr.mul] 说:
The usual arithmetic conversions are performed on the operands
and determine the type of the result.
5
部分涵盖了 通常的算术转换 说:
Otherwise, the integral promotions (4.5) shall be performed on both operands.61 Then the following
rules shall be applied to the promoted operands:
[...]
- Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the
rank of the type of the other operand, the operand with signed integer type shall be converted to
the type of the operand with unsigned integer type.
我们可以在 cppreference here 和 4.13
部分的 C++ 标准草案中找到等级 [conv.rank]:
The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type.
在 x * y
中,x
已作为通常算术转换的结果转换为 unsigned
。 §5/10:
即您的第一个表达式 (unsigned)(x * y)
等同于 (unsigned)((unsigned)x * y)
,后者又等同于 (unsigned)x * y
- 您的第二个表达式。
请注意,根据 §4.13/1.4,unsigned int
的排名等于 (signed
)int
的排名:
The rank of any unsigned integer type shall equal the rank of
the corresponding signed integer type.
这里有2个重点:
通常的算术转换
无符号整数运算不能溢出
引用自标准:
§5 Expressions [expr]
9 Many binary operators that expect operands of
arithmetic or enumeration type cause conversions and yield result
types in a similar way. The purpose is to yield a common type, which
is also the type of the result. This pattern is called the usual
arithmetic conversions, which are defined as follows:
[...]
— Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the
rank of the type of the other operand, the operand with signed integer type shall be converted to
the type of the operand with unsigned integer type.
因此(第 1 点),两个操作数的秩相同,有符号的将转换为无符号类型。因此 ((unsigned int)x) * y
和 ((unsigned int)(x * y))
的计算总是相同的。
现在让我们看看它们是否始终有效。
§3.9.1 Fundamental types [basic.fundamental]
4 Unsigned integers, declared unsigned, shall obey the laws of arithmetic modulo 2n where n is the number
of bits in the value representation of that particular size of integer.46
46) This implies that unsigned arithmetic does not overflow because a result that cannot be represented by the resulting
unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the
resulting unsigned integer type.
所以(第 2 点),((unsigned int)x) * y
将 不会 溢出(y
也不会溢出(尽管 x
会溢出,因为 for 循环继续,因此将发生未定义的行为)。
我刚刚写了这些代码:
int x = -1;//x must be negative
unsigned int y = 1;//y must be positive
bool b;
for(; ; x--, y++){
b = ((unsigned int)x) * y == ((unsigned int)(x * y));
}
然后我发现b
总是正确的。在我看来,((unsigned int)x) * y 会溢出,但 ((unsigned int)(x * y)) 不会。我真的很难相信这是真的。这只是巧合还是有一定规律可循?
Is this just coincidence or is there any law behind this phenomenon?
不,这不是巧合,这可以用通常的算术转换执行的隐式转换来解释。
乘法运算符对其操作数执行 usual arithmetic conversions 以将它们变为通用类型,对于此子表达式:
(unsigned int)(x * y)
将导致 x
被转换为 unsigned int。这使得:
(unsigned int)(x * y)
相当于:
((unsigned int)x) * y
很多时候使用正确的警告标志可以帮助解决难题,使用 -Wall
和 gcc 会给出以下警告:
warning: self-comparison always evaluates to true [-Wtautological-compare]
b = ((unsigned int)x) * y == ((unsigned int)(x * y));
^
使用带有 clang 的 -Wconversion
标志会给出以下警告 (see it live):
warning: implicit conversion changes signedness: 'int' to 'unsigned int' [-Wsign-conversion]
b = ((unsigned int)x) * y == ((unsigned int)(x * y));
^ ~
作为参考,C++ 标准草案部分 5.6
[expr.mul] 说:
The usual arithmetic conversions are performed on the operands and determine the type of the result.
5
部分涵盖了 通常的算术转换 说:
Otherwise, the integral promotions (4.5) shall be performed on both operands.61 Then the following rules shall be applied to the promoted operands:
[...]
- Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type.
我们可以在 cppreference here 和 4.13
部分的 C++ 标准草案中找到等级 [conv.rank]:
The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type.
在 x * y
中,x
已作为通常算术转换的结果转换为 unsigned
。 §5/10:
即您的第一个表达式 (unsigned)(x * y)
等同于 (unsigned)((unsigned)x * y)
,后者又等同于 (unsigned)x * y
- 您的第二个表达式。
请注意,根据 §4.13/1.4,unsigned int
的排名等于 (signed
)int
的排名:
The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type.
这里有2个重点:
通常的算术转换
无符号整数运算不能溢出
引用自标准:
§5 Expressions [expr]
9 Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:
[...]
— Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type.
因此(第 1 点),两个操作数的秩相同,有符号的将转换为无符号类型。因此 ((unsigned int)x) * y
和 ((unsigned int)(x * y))
的计算总是相同的。
现在让我们看看它们是否始终有效。
§3.9.1 Fundamental types [basic.fundamental]
4 Unsigned integers, declared unsigned, shall obey the laws of arithmetic modulo 2n where n is the number of bits in the value representation of that particular size of integer.46
46) This implies that unsigned arithmetic does not overflow because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting unsigned integer type.
所以(第 2 点),((unsigned int)x) * y
将 不会 溢出(y
也不会溢出(尽管 x
会溢出,因为 for 循环继续,因此将发生未定义的行为)。