补码架构上的负零行为?

Behaviour of negative zero on a one's complement architecture?

在补码架构上考虑以下代码:

int zero = 0;
int negzero = -0;
std::cout<<(negzero < zero)<<std::endl;
std::cout<<(negzero <= zero)<<std::endl;
std::cout<<(negzero == zero)<<std::endl;
std::cout<<(~negzero)<<(~zero)<<std::endl;
std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;

首先,一个人的补码架构(甚至区分负零)是相当罕见的,这是有原因的。加二进制补码基本上比加一个补码更容易。

您发布的代码似乎没有未定义的行为,甚至没有实现定义的行为,它可能不会导致负零(或者它不应该与正常零区分开来)。

负零不应该那么容易产生(如果你设法做到这一点,它充其量是实现定义的行为)。如果它是补码架构,它们将由 ~0(按位反转)而不是 -0.

生成

C++标准对基本类型的实际表示和行为要求比较模糊(也就是说规范只处理数字的实际含义)。这意味着您基本上无法将数字的内部表示与其实际值相关联。因此,即使您这样做是正确的并使用 ~0(或任何适合实施的方式)标准似乎仍然不会打扰表示,因为负零的值仍然为零。

#define zero (0)
#define negzero (~0)
std::cout<<(negzero < zero)<<std::endl;
std::cout<<(negzero <= zero)<<std::endl;
std::cout<<(negzero == zero)<<std::endl;
std::cout<<(~negzero)<<(~zero)<<std::endl;
std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;

前三行应产生相同的输出,就好像 negzero 的定义与 zero 相同。第三行应该输出两个零(因为标准要求 0 呈现为 0 没有符号)。最后两个应该输出一个。

在 C 标准中可以找到一些 提示(关于如何产生负零)实际上提到了负零,但我认为没有提到关于他们应该比较小于正常零。 C 标准表明负零可能无法在对象中存储(这就是我在上面的示例中避免使用它的原因)。

根据 C 和 C++ 的相关方式,可以合理地认为负零在 C++ 中的生成方式与在 C 中的生成方式相同,并且标准似乎允许这样做。虽然 C++ 标准允许其他方式(通过未定义的行为),但似乎没有其他方式可以通过已定义的行为使用。所以可以肯定的是,如果 C++ 实现能够以合理的方式产生负零,那么它与类似的 C 实现是一样的。

根据我对标准的解读:

§3.9.1/p3 基本类型中的 C++ 标准 [basic.fundamental] 实际上是 C 标准中的球:

The signed and unsigned integer types shall satisfy the constraints given in the C standard, section 5.2.4.2.1.

现在,如果我们转到 ISO/IEC 9899:2011 部分 5.2.4.2.1,它作为对 §6.2.6.2/p2 整数类型 [=36= 的前向引用](强调我的):

If the sign bit is zero, it shall not affect the resulting value. If the sign bit is one, the value shall be modified in one of the following ways:

  • the corresponding value with sign bit 0 is negated (sign and magnitude);

  • the sign bit has the value −(2^M) (two’s complement);

  • the sign bit has the value −(2^M − 1) (ones’ complement).

Which of these applies is implementation-defined, as is whether the value with sign bit 1 and all value bits zero (for the first two), or with sign bit and all value bits 1 (for ones’ complement), is a trap representation or a normal value. In the case of sign and magnitude and ones’ complement, if this representation is a normal value it is called a negative zero.

因此,负零的存在是实现定义的。

如果我们继续第 3 段:

If the implementation supports negative zeros, they shall be generated only by:

  • the &, |, ^, ~, <<, and >> operators with operands that produce such a value;

  • the +, -, *, /, and % operators where one operand is a negative zero and the result is zero;

  • compound assignment operators based on the above cases.

It is unspecified whether these cases actually generate a negative zero or a normal zero, and whether a negative zero becomes a normal zero when stored in an object.

因此,不确定您显示的相关案例是否会生成负零。

现在继续第 4 段:

If the implementation does not support negative zeros, the behavior of the &, |, ^, ~, <<, and >> operators with operands that would produce such a value is undefined.

因此,相关操作是否导致未定义行为,取决于实现是否支持负零。

首先你的第一个前提是错误的:

int negzero = -0;

应该在任何一致的架构上产生一个正常的零。

@101010 的回答中给出了参考:

3.9.1 基本类型 [basic.fundamental] §3:

... The signed and unsigned integer types shall satisfy the constraints given in the C standard, section 5.2.4.2.1.

稍后在 C 参考中: 5.2.4.2.1 整数类型的大小

... Forward references: representations of types (6.2.6)

和(还是C): 6.2.6 类型表示 / 6.2.6.2 整数类型 § 3

If the implementation supports negative zeros, they shall be generated only by:

  • the &, |, ^, ~, <<, and >> operators with arguments that produce such a value;

  • the +, -, *, /, and % operators where one argument is a negative zero and the result is zero;

  • compound assignment operators based on the above cases.

所以negzero = -0不是这样的结构,不应产生负数0。

对于以下几行,我将假设负数 0 是在支持它的实现中按位产生的

C++ 标准根本没有提到负零,C 标准只是说它们的存在是依赖于实现的。我找不到任何段落明确说明负零是否应该等于关系或相等运算符的正常零。

所以我将在 C 参考中引用:6.5.8 关系运算符 §6

Each of the operators < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to) shall yield 1 if the specified relation is true and 0 if it is false.92) The result has type int.

以及 C++ 5.9 中的关系运算符 [expr.rel] §5

If both operands (after conversions) are of arithmetic or enumeration type, each of the operators shall yield true if the specified relationship is true and false if it is false.

我对标准的解释是,实现可能允许整数值 0 的替代表示(负零),但它仍然是值 0 的表示,它应该在任何算术表达式中相应地执行,因为 C 6.2.6.2 整数类型§ 3 说:

negative zeros[...] shall be generated only by [...] the +, -, *, /, and % operators where one argument is a negative zero and the result is zero

这意味着如果结果不是 0,则负 0 应作为正常零执行。

所以这两行至少是完美定义的并且应该产生 1:

std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;

此行明确定义为依赖于实现:

std::cout<<(~negzero)<<(~zero)<<std::endl;

因为一个实现可能有填充位。如果没有填充位,在一个补码架构上 ~zero negzero,所以 ~negzero 应该产生一个 0 但我如果负零应显示为 0-0,则无法在标准中找到。负数 浮点数 0 应显示为负号,但对于 整数 负值似乎没有任何明确说明。

对于涉及关系和相等运算符的最后 3 行,标准中没有明确说明,所以我会说它是实现定义的

TL/DR:

依赖于实现:

std::cout<<(negzero < zero)<<std::endl;
std::cout<<(negzero <= zero)<<std::endl;
std::cout<<(negzero == zero)<<std::endl;
std::cout<<(~negzero)<<(~zero)<<std::endl;

完美定义,应该产生 1:

std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;