Java中整数在位级别上是如何表示的?

92

我正试图理解Java如何在内部存储整数。我知道所有的Java原始整数都是带符号的,(除了short?)。这意味着一个字节中可用的位数比数字少一个。

我的问题是,所有整数(正数和负数)是否都以二进制补码形式存储,还是只有负数以二进制补码形式存储?

我看到规范中写着x位二进制补码数。但我经常感到困惑。

例如:

  int x = 15; // Stored as binary as is?  00000000 00000000 00000000 00001111?
  int y = -22; // Stored as two complemented value? 11111111 11111111 11111111 11101010

编辑

明确一下,x = 15

   In binary as is: `00000000 00000000 00000000 00001111'
  Two's complement: `11111111 11111111 11111111 11110001`

因此,如果您的答案是 所有 数字都以二进制补码形式存储,则:

  int x = 15; // 11111111 11111111 11111111 11110001
  int y = -22 // 11111111 11111111 11111111 11101010

这里的混淆在于标志说,两个数字都是负数。也许我误读/误解了它?

编辑 不确定我的问题是否令人困惑。被迫分开提问:

我的问题确切地说:正数是以二进制形式存储的吗?而负数则以二进制补码形式存储吗?

有些人说所有数字都以二进制补码形式存储,而另一个答案则表示只有负数以二进制补码形式存储。


顺便说一下,char 是唯一的无符号整数值。 - undefined
11个回答

119

首先,让我们总结一下Java原始数据类型:

byte:字节数据类型是8位有符号二进制补码整数

short:短整型数据类型是16位有符号二进制补码整数

int:整型数据类型是32位有符号二进制补码整数

long:长整型数据类型是64位有符号二进制补码整数

float:浮点型数据类型是单精度的32位IEEE 754浮点数

double:双精度数据类型是双精度的64位IEEE 754浮点数

boolean:布尔数据类型表示一位信息

char:字符数据类型是一个16位Unicode字符

来源

二进制补码

"好的例子来自于维基百科,它通过注意到256 = 255 + 1以及(255 - x)是x的补码来实现对二进制补码的关系

0000 0111=7的二进制补码是1111 1001=-7

它的工作方式是MSB(最高有效位)接收负值,因此在上面的情况下

-7 = 1001= -8 + 0+ 0+ 1

正整数通常存储为简单的二进制数字(1是1,10是2,11是3等等)。

负整数则以其绝对值的二进制补码形式存储。使用这种表示法时,正数的二进制补码就是负数。

来源

由于我得到了一些点赞,所以我决定添加更多信息。

更详细的回答:

在其他方法中,有四种主要方法来表示二进制中的正数和负数,它们分别是:

  1. 带符号大小
  2. 取反码
  3. 二进制补码
  4. 偏置

1. 带符号大小

使用最高有效位表示符号,其余位用于表示绝对值。其中0表示正数1表示负数,例如:

1011 = -3
0011 = +3

这种表示方法更简单。然而,您不能像加十进制数一样加二进制数,使其在硬件层面更难以实现。此外,这种方法使用两个二进制模式来表示0:负零(1000)正零(0000)

2. 反码

在这种表示法中,我们翻转给定数字的所有位以找出其补码。例如:

010 = 2, so -2 = 101 (inverting all bits).

这种表示法的问题在于,仍然存在两种比特模式来表示0,负零(1111)正零(0000)

3. 补码

在此表示法中,要找到一个数的负数,我们需要反转所有比特位,然后再加上一位。加上一位解决了具有两种比特模式表示0的问题。在这种表示法中,只有一个模式表示0 (0000)

例如,我们想使用4个比特位找到4(十进制)的二进制负数表示。首先,将4转换为二进制:

4 = 0100

然后我们取反所有位

0100 -> 1011

最后,我们添加了一位(bit)。

1011 + 1 = 1100.

如果我们使用带有4位的二进制补码表示,那么1100相当于十进制中的-4。

一种更快的找到补码的方法是通过修复第一个值为1的比特位,并反转剩余的比特位。在上面的例子中,它会像这样:

0100 -> 1100
^^ 
||-(fixing this value)
|--(inverting this one)

二进制的补码表示法,除了只有一个0的表示之外,还可以用同样的方式将两个不同符号的偶数相加。但是,需要检查溢出情况。

4. 偏置(Bias)

这种表示法用于在IEEE 754浮点数规范中表示指数。它的优点是,所有位为零的二进制值表示最小值。而所有位都为1的二进制值表示最大值。正如名称所示,值(正或负)以具有偏差的二进制形式编码,使用n位(通常为2^(n-1)或2^(n-1)-1)的偏置。

因此,如果我们使用8位,则十进制值1在使用2^(n-1)的偏置下用二进制表示为:

+1 + bias = +1 + 2^(8-1) = 1 + 128 = 129
converting to binary
1000 0001

67

Java中的整数类型为32位,并且始终带有符号。这意味着最高有效位(MSB)作为符号位。由int表示的整数仅是位的加权和。 权重分配如下:

Bit#    Weight
31      -2^31
30       2^30
29       2^29
...      ...
2        2^2
1        2^1
0        2^0

请注意,最高有效位的权重是负数(实际上是最大可能的负数),因此当该位开启时,整个数字(加权总和)将变为负数。

让我们用4位数字来模拟它:

Binary    Weighted sum            Integer value
0000       0 + 0 + 0 + 0           0
0001       0 + 0 + 0 + 2^0         1
0010       0 + 0 + 2^1 + 0         2
0011       0 + 0 + 2^1 + 2^0       3
0100       0 + 2^2 + 0 + 0         4
0101       0 + 2^2 + 0 + 2^0       5
0110       0 + 2^2 + 2^1 + 0       6
0111       0 + 2^2 + 2^1 + 2^0     7 -> the most positive value
1000      -2^3 + 0 + 0 + 0        -8 -> the most negative value
1001      -2^3 + 0 + 0 + 2^0      -7
1010      -2^3 + 0 + 2^1 + 0      -6
1011      -2^3 + 0 + 2^1 + 2^0    -5
1100      -2^3 + 2^2 + 0 + 0      -4
1101      -2^3 + 2^2 + 0 + 2^0    -3
1110      -2^3 + 2^2 + 2^1 + 0    -2
1111      -2^3 + 2^2 + 2^1 + 2^0  -1
因此,二进制补码不是表示负整数的专用方案,我们可以说整数的二进制表示总是相同的,只是取反最高位的权重。而该位决定了整数的符号。
在C语言中,有一个关键字unsigned(Java中不可用),可用于声明unsigned int x; 对于无符号整数,MSB的权重为正(2^31),而不是负数。在这种情况下,unsigned int的范围是0到2^32-1,而int的范围是-2^31到2^31-1。
从另一个角度来看,如果将x的二进制补码视为~x+1(而不是NOT x plus one),则解释如下:
对于任何x,~x只是x的按位反转,因此无论x在哪里都有一个1位,在~x那里将有一个0位(反之亦然)。因此,如果将它们加起来,加法中不会有进位,并且总和将仅是每个位都为1的整数。
对于32位整数:
x + ~x = 1111 1111 1111 1111 1111 1111 1111 1111
x + ~x + 1 =   1111 1111 1111 1111 1111 1111 1111 1111 + 1
           = 1 0000 0000 0000 0000 0000 0000 0000 0000

最左边的1位将被简单地丢弃,因为它不适合32位(整数溢出)。因此,

x + ~x + 1 = 0
-x = ~x + 1

所以您可以看到,负数x可以由~x + 1表示,我们称之为x的补码。


我的问题是:正数是以二进制形式存储的,而负数则以二补码形式存储吗? - Kevin Rave
是的,负数在计算机中被表示为其正值的二进制补码。 - Sufian Latif
@0605002:如果有的话,你能否提供这个答案的参考资料?虽然我知道这些概念,但从未以那种方式思考过。最简单、最准确的答案。 - Abhishek Singh
四年的大学生活,我从未理解过二进制补码。这个答案教会了我更多。很遗憾,全世界都以如此晦涩难懂的方式教授这样简单的东西。 - Prashant Pandey
你在这里使用的方案已经是二进制补码了,不是吗? - KevinZhou

11

我已经运行了以下程序以了解它

public class Negative {
    public static void main(String[] args) {
        int i =10;
        int j = -10;

        System.out.println(Integer.toBinaryString(i));
        System.out.println(Integer.toBinaryString(j));
    }
}

输出是

1010
11111111111111111111111111110110

从输出结果来看,它似乎一直在使用二进制补码。


1
十的补码是 11111111 11111111 11111111 11110110。你的打印输出二进制形式为 1010 的数字。所以只有负数才会以补码形式存储? - Kevin Rave
请查看维基百科上的文章en.wikipedia.org/wiki/…,您提供的15的2补数是错误的。 - Dungeon Hunter
如果最高位(MSB)的第一位是1,那么它将是负数。 - Dungeon Hunter
是的,十的二进制补码为 11111111 11111111 11111111 11110110,表示的是 -10。 - Dungeon Hunter
正数将以二进制形式存储,留下符号位为2的补码。 - Dungeon Hunter

5
Oracle提供了一些与Java数据类型有关的文档Datatypes,您可能会觉得很有趣。具体来说:
int: int数据类型是32位有符号二进制补码整数类型。它最小值为-2,147,483,648,最大值为2,147,483,647(含)。
顺便说一下,short也存储为二进制补码。

4
正数按原样存储/检索。
e.g) For +ve number 10; byte representation will be like 0-000 0010 
                                               (0 - MSB will represent that it is +ve).
So while retrieving based on MSB; it says it is +ve, 
so the value will be taken as it is. 

但负数将会以二进制补码形式存储(除了最高位),并将最高位设为1。

例如) 存储-10时,则:

  0-000 0010  -> (1's complement) -> 0-111 1101 
              -> (2's complement) 0-111 1101 + 1 -> 0-111 1110
  Now MSB will be set to one, since it is negative no -> 1-111 1110

在检索时发现 MSB 被设为 1,因此这是一个负数。除了 MSB 外,还会执行 2 的补数。

  1-111 1110  --> 1-000 0001 + 1 --> 1-000 0010
  Since MSB representing this is negative 10 --> hence  -10 will be retrived.

类型转换

需要注意的是,当你将int/short类型转换为byte类型时,只有最后一个字节以及最后一个字节的MSB(最高有效位)会被考虑在内。

以"-130"这个short类型为例,它可能会被存储如下:

(MSB)1-(2's complement of)130(1000 0010) --> 1-111 1111 0111 1110

现在字节转换会取最后一个字节,该字节为0111 1110。(0-MSB) 由于MSB表示它是一个正值,所以将按原样处理。 这是126。(+ve)。
再举一个例子,“130”短整型数可能存储如下:
  0-000 000 1000 0010     (MSB = 0)

现在字节强制转换取了最后一个字节,即1000 0010。(1=MSB) 由于MSB表示它是负值,将执行2的补码并返回负数。因此,在这种情况下,将返回-126。

 1-000 0010  -> (1's complement) -> 1-111 1101 
             -> (2's complement) 1-111 1101 + 1 -> 1-111 1110 -> (-)111 1110
               = -126

(int)(char)(byte) -1 和 (int)(short)(byte) -1 的区别。
(byte)-1       -> 0-000 0001 (2's Comp) -> 0-111 1111 (add sign) -> 1-111 1111
(char)(byte)-1 -> 1-111 1111 1111 1111  (sign bit is carry forwarded on left) 

同样地
(short)(byte)-1-> 1-111 1111 1111 1111  (sign bit is carry forwarded on left) 

但是,
(int)(char)(byte)-1 -> 0-0000000 00000000 11111111 11111111  = 65535
since char is unsigned; MSB won't be carry forwarded. 

并且

(int)(Short)(byte)-1 -> 1-1111111 11111111 11111111 11111111 = -1
since short is signed; MSB is be carry forwarded. 

参考资料

为什么使用二进制补码表示负数?

什么是“二进制补码”?


1
https://dev59.com/nWgu5IYBdhLWcg3wWl4q#23801667 - Kanagavelu Sugumar

3
根据这份文档,Java中的所有整数都是带符号的,并以二进制补码格式进行存储。不确定其可靠性。

在二进制补码格式中,正数被表示为一个直接的二进制数。 - Shashi
可能Java语言规范4.2.原始类型和值是一个更加可靠的来源... 除了char之外,所有的整数都是有符号的。 - undefined

3

最高有效位(第32位)表示该数字是正数还是负数。如果它是0,意味着数字是正数,并且存储在其实际的二进制表示中。但如果是1,则表示该数字为负数,并以二进制补码形式存储。因此,在从二进制表示中恢复整数值时,当我们将-2 ^ 32赋予第32位时,我们得到实际答案。


2
感谢dreamcrashhttps://dev59.com/82Yr5IYBdhLWcg3wvsvR#13422442上的回答;在维基百科页面上,他们提供了一个例子,帮助我理解如何找到正数的负对应项的二进制表示。
例如,使用1个字节(= 2个半字节 = 8位),十进制数5表示为:
0000 01012
最高有效位为0,因此该模式表示非负值。要将其转换为二进制补码中的-5,需要反转位;0变为1,1变为0:
1111 1010
此时,数字是十进制值-5的一补数。要获得二进制补码,需要将结果加1,得到:
1111 1011
结果是一个带符号的二进制数,以二进制补码形式表示十进制值-5。最高有效位为1,因此所表示的值为负数。

2

正数直接以二进制形式存储,负数需要使用二进制补码表示。

例如:

15: 00000000 00000000 00000000 00001111
-15: 11111111 11111111 11111111 11110001

这里的区别在于符号位。


1
对于正整数,2'补码的值与最高有效位为0相同(例如+14的2'补码为01110)
对于负整数,我们只计算2'补码值(-14=10001+1=10010)
因此,最终答案是这两个值(正和负)都以2'补码形式存储。

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