在一种补码系统中,std::cout如何打印负零?

16

在一个ones-complement平台上,以下代码会打印什么?

#include <iostream>

int main() {
    int i = 1, j = -1;

    std::cout << i+j << std::endl;
    return 0;
}

我会怀疑它会打印出"0"而不是"-0",但我似乎找不到确凿的证据。

编辑:为了澄清,我想知道如何打印-0,有几个人建议,在实践中,补码的实现可能不会生成负零。

在这些情况下,以下内容被建议用于实际生成-0:

#include <iostream>

int main() {
    std::cout << ~0 << std::endl;
    return 0;
}
这个问题仍然存在:这将印出什么?

1
coliru 上打印 0。我非常确定这是每次都应该发生的事情。 - Bartek Banachewicz
2
我怀疑在任何系统上都不会打印出“-0”。 - nwp
1
可以给这个C标签吗?我假设C程序员更接近这个东西。 - Bathsheba
2
@BartekBanachewicz 我确定coliru不使用补码CPU;在二进制补码中,负零是不可能存在的。 - Mark Ransom
2
“operator<<” 是基于 “num_put” 规定的,而 “num_put” 又是基于 “printf” 规定的,而 “printf” 又是基于 “fprintf” 规定的。但我找不到任何关于 “fprintf” 的解释。 - aschepler
显示剩余7条评论
4个回答

5

实际上,将n加到-n应该得到一个负零。但是在实践中不会生成-0,因为1的补码加法使用了一种称为补数减法器的技术(第二个参数被补码并从第一个参数中减去)。

(获取有符号浮点零的惯用方法在这里不适用,因为您无法将整数除以零。)


2
我对这些并不了解,但是通过整数除法将-1除以某个较大正数(或者将1除以某个较大负数)可能会得到一个负零吗? - davmac
我对这个也很生疏 - 自从大学时代以来,我就没有再涉及过1的补码 - 但是据我所记,所有在1的补码上进行的算术运算都是这样实现的,你不会得到一个带符号的负零。 - Bathsheba
6
如果你知道你的系统采用补码模式,生成负零进行测试很容易:~0。虽然在这样的系统中自然获得负零可能有些困难,但我认为这并非不可能。这并没有完全回答所提出的问题。 - Mark Ransom

5
首先,需要澄清的是,使用位运算来创建负零然后使用其结果并不具有可移植性。话虽如此,在fprintf的文档中(因此也是std::basic_ostream::operator<<(int)的文档)没有指定int类型表示中的符号位是否对应于unsigned类型表示中的填充位或实际值位。
综上所述,这是未指定的行为。
#include <iostream>

int main() {
    std::cout << ~0 << std::endl;
    return 0;
}

2
使用位运算来创建整数负零肯定是可移植到其他的补码计算机。确实,在二进制补码计算机上它不起作用,因为负零在二进制补码中不存在。 - George
2
“确实可以移植到其他的补码计算机上:”这就是我所说的不可移植 ;)。 - YSC
3
这个问题只适用于反码计算机,因此在这里,就所有意图而言,我同意 George 的观点。 - Tyzoid
“~0”肯定是有效的代码,并且可以在两个平台上编译/运行良好。我知道在二进制补码机器上它会打印什么(“-1”)。我正在尝试了解在一位补码机器上的行为是什么。 - Tyzoid
@George @Tyzoid еӨ§е®¶йғҪи®Өдёә~0з»“жһ„еңЁдҝқжҢҒдәҢиҝӣеҲ¶иЎҘз ҒжҲ–еҸҚз Ғжһ¶жһ„зҡ„жғ…еҶөдёӢжҳҜеҸҜ移жӨҚзҡ„пјӣи®©жҲ‘们дёҚиҰҒеҲҶжӯ§е’Ңзә з»“дәҺжҺӘиҫһгҖӮеҰӮжһңдҪ еҜ№жҲ‘зҡ„第дёҖеҸҘиҜқж„ҹеҲ°дёҚиҲ’жңҚпјҢйӮЈе°ұдё“жіЁдәҺ第дәҢе’Ң第дёүеҸҘиҜқпјҢе®ғ们еёҰжқҘдәҶжҲ‘д»Ӣе…Ҙзҡ„йҷ„еҠ д»·еҖјгҖӮ - YSC
显示剩余3条评论

5

在查看glibc源代码时,我在vfprintf.c中发现了以下几行:

532       is_negative = signed_number < 0;                    \
533       number.word = is_negative ? (- signed_number) : signed_number;      \
534                                           \
535       goto LABEL (number);                            \
...
683       if (is_negative)                            \
684         outchar (L_('-'));                            \

看起来条件是 signed_number < 0,这对于 -0 将返回 false。
正如 @Ysc 提到的,文档中没有任何规定打印 -0 的规范,因此在补码平台上的不同 libc 实现可能会产生不同的结果。

0

如果我们从理论上看待补码的观点。由于零被定义为(+/-)0,如果我们有4位二进制值,那么零将是0000 (+0)和1111 (-0)两个二进制值。因此,如果一个操作(加法或减法)中涉及到零交叉操作,您总是需要进行校正。

例如,如果我们进行以下操作-2+6=4,结果将如下计算:

  1101 (-2)
+ 0110 (6)
------
 1100  (add carry)
======
  0011 (+3)  

从位运算中可以看出,结果是不正确的,仅是不完整的结果。在这种情况下,我们必须将值加上 +1 才能得到正确的结果。要确定是否需要添加 +1,我们必须查看进位结果。如果最左边的数字1100 是 ONE,则必须将其加上 +1 才能得到正确的结果。

如果我们看一下您的示例:

  0001 (+1)
+ 1110 (-1)
------
 0000  (add carry)
======
  1111 (-0)  

我们可以看到结果将会是-0,这将是最终结果,因为左侧加法进位位为0。


虽然我了解补码加法的机制(事实上,这就是我在原帖中引用(-0) = (-1) + 1的原因),但我的问题是它在C++标准/实际的补码机器中会输出什么。 - Tyzoid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接