当Java将int转换为byte时出现奇怪的行为?

130
int i =132;

byte b =(byte)i; System.out.println(b);

令人费解。为什么输出是-124

12个回答

175
在Java中,一个int是32位。一个byte是8个bits
Java中的大多数原始类型都是有符号的,byteshortintlong使用二进制补码表示。( char类型是无符号的,而boolean没有概念)。
在这种数字方案中,最高位指定了数字的符号。如果需要更多位,则将最高位("MSB")复制到新的MSB即可。
所以如果你有一个byte25511111111,并且你想将它表示为一个int(32位),你只需将1向左复制24次。
现在,读取负二进制补码数的一种方法是从最低位开始,向左移动直到找到第一个1,然后反转之后的每一位。得到的数字是该数字的正版本。
例如:11111111变为00000001=-1。这是Java将显示的值。
您可能想要知道字节的无符号值。
可以使用掩码完成这项工作,该掩码删除除最低有效的8位以外的所有内容。(0xff)
所以:
byte signedByte = -1;
int unsignedByte = signedByte & (0xff);

System.out.println("Signed: " + signedByte + " Unsigned: " + unsignedByte);

将会打印出:"Signed: -1 Unsigned: 255"

这里到底发生了什么?

我们使用按位与运算来屏蔽所有多余的符号位(也就是最不重要的八个二进制位左边的那些1)。当一个int类型转换成byte类型时,Java会截取掉最左边的24个位。

1111111111111111111111111010101
&
0000000000000000000000001111111
=
0000000000000000000000001010101

由于现在的第32位是符号位而不是第8位(并且我们将符号位设为了0表示正数),Java会将字节中原来的8位作为一个正值读取。


1
干得好,Wayne!这是关于这个主题的最好解释。我只是在寻找数学规范化的原因,为什么在二进制补码表示中,符号位可以复制到右侧以添加位。想要理解它,只需要考虑如何获得一个数字的负数规则即可。也就是说:从右到左考虑所有位并将它们不变地写下来,直到第一个包含1的位。然后反转后续的位。如果我认为缺失的位是0,则很容易理解它们都变成了1。但我正在寻找更多“数学”解释。 - AgostinoX
这里发生的是 signedByte & (0xff),其中 0xff 是一个整数字面量,因此在执行位运算之前,signedByte 会被提升为整数。 - Kevin Wheeler
在你的例子中,那不是0xFF,而是0x7E! - JohnyTex

90

132在十进制(10进制)中,对应的二进制(2进制)表达式为1000_0100,而Java将int存储在32位中:

0000_0000_0000_0000_0000_0000_1000_0100

int转换为byte的算法是左截断;在System.out.println中使用的算法是二进制补码(如果最高位是1,则解释为负数:取反码(翻转比特)减去一。)因此System.out.println(int-to-byte( ))为:

  • 解释为(如果最高位为1)[负数(取反码(减一(左截断(0000_0000_0000_0000_0000_0000_1000_0100)))))
  • =解释为(如果最高位为1)[负数(取反码(减一(1000_0100))))]
  • =解释为(负数(取反码(1000_0011))))
  • =解释为(负数(0111_1100)))
  • =解释为(负数(124))
  • =解释为(-124)
  • =-124   完成!

7
非常好地讲解了。 - ZAJ
1
所以现在十进制的132在字节中是-124。那么反过来怎样操作呢? - Nilesh Deokar
@NileshDeokar,反转是通过 POLA 实现的,因为它们匹配(; cf JLS 5.1.2);输出与 sign-leftpad 相符(正数为 0,负数为 1)。 - Pacerier
什么是POLA?从intbyte的转换是有损转换(即信息丢失)。因此,无法将其转换回原始的int值。 - daparic

23

在Java中,byte是有符号的,它的范围是从-2^7到2^7-1,也就是-128到127。

因为132超过了127,所以你会得到132-256=-124。也就是说,会不断地添加或减去256(2^8)直到它在范围内。

如果想了解更多信息,可以阅读关于二进制补码的内容。


16

132超出了一个字节的范围,字节的范围是-128到127(Byte.MIN_VALUE到Byte.MAX_VALUE)。 相反,8位值的最高位被视为带符号的,表示在此情况下它是负数。因此,该数字为132-256 = -124。


6

这里提供一种非常机械的方法,没有那些令人分心的理论:

  1. 将数字转换为二进制表示(可以使用计算器)
  2. 只复制最右边的8位(LSB),舍弃其余内容。
  3. 对于步骤#2的结果,如果最左边的位是0,则使用计算器将数字转换为十进制。这就是你的答案。
  4. 否则(如果最左边的位是1),你的答案是负数。保留所有最右边的0和第一个非零位不变。然后将其余部分反转,即用0替换1,用1替换0。再使用计算器将其转换为十进制,并附加一个负号表示该值为负数。

这种更实用的方法符合上面许多理论性答案的要求。所以,那些仍在读那些声称使用模数的Java书籍的人,这明显是错误的,因为我概述的4个步骤绝对不是模操作。


Java的书籍都在说什么关于使用“模数”?我在46年里从来没有看到过任何一本计算机科学书籍提到过,更不用说任何一本Java书籍了。什么是“模数”?Java中没有模数运算符,只有余数运算符。 - user207421
更努力地使用grep。http://iiti.ac.in/people/~tanimad/JavaTheCompleteReference.pdf 第59页。 - daparic
在这里更努力地查找第63页[https://github.com/ontiyonke/book-1/blob/master/%5BJAVA%5D%5BThe%20Complete%20Reference%20Java%20Ninth%20Edition%5D.pdf],使用grep命令。 - daparic

4

二进制补码:

enter image description here


在Java中,byte(N=8)和int(N=32)均采用了上述二进制补码表示。

由于上述公式,对于byte而言,a7是负数,但对于int而言则是正数。

coef:   a7    a6  a5  a4  a3  a2  a1  a0
Binary: 1     0   0   0   0   1   0   0
----------------------------------------------
int:    128 + 0 + 0 + 0 + 0 + 4 + 0 + 0 =  132
byte:  -128 + 0 + 0 + 0 + 0 + 4 + 0 + 0 = -124

2
一个快速模拟它工作方式的算法如下:
public int toByte(int number) {
    int tmp = number & 0xff
    return (tmp & 0x80) == 0 ? tmp : tmp - 256;
}

这是怎么工作的?看一下daixtr的答案。他在答案中描述了一个精确算法的实现,具体如下:
public static int toByte(int number) {
    int tmp = number & 0xff;
    if ((tmp & 0x80) == 0x80) {
        int bit = 1;
        int mask = 0;
        for(;;) {
            mask |= bit;
            if ((tmp & bit) == 0) {
                bit <<=1;
                continue;
            }
            int left = tmp & (~mask);
            int right = tmp & mask;
            left = ~left;
            left &= (~mask);
            tmp = left | right;
            tmp = -(tmp & 0xff);
            break;
        }
    }
    return tmp;
}

2

在很多书籍中,你会看到将整型转换为字节的解释被视为模除操作。然而,这并不严格正确,如下所示: 实际发生的是将整数值的二进制形式中最高的24位被丢弃,导致剩余的最左边的一位可能被设置为1,表示该数字为负数,从而造成混淆。

public class castingsample{

public static void main(String args[]){

    int i;
    byte y;
    i = 1024;
    for(i = 1024; i > 0; i-- ){

      y = (byte)i;
      System.out.print(i + " mod 128 = " + i%128 + " also ");
      System.out.println(i + " cast to byte " + " = " + y);

    }

}

}

2
我在46年的时间里从未在任何书籍中看到过这个。 - user207421

1
如果您想从数学角度理解这个问题,就需要知道以下内容:
基本上,-128到127之间的数字将按照它们的十进制值进行编写,而超过该范围的数字将采用(您的数字-256)的方式进行编写。
例如,对于数字132,答案将是 132 - 256 = -124 即
数字加上你的答案 256 + (-124) 是 132
下面是另一个例子。
double a = 295.04;
int b = 300;
byte c = (byte) a;
byte d = (byte) b; System.out.println(c + " " + d);

输出将为39 44

(295 - 256) (300 - 256)

注意:它不会考虑小数点后面的数字。


0
从概念上讲,对您的数字重复减去256,直到它在-128到+127的范围内。因此,在您的情况下,您从132开始,最终以一步达到-124。
从计算上讲,这相当于从原始数字中提取8个最低有效位。(请注意这8位中的最高有效位成为符号位。)
请注意,在其他语言中,此行为未定义(例如C和C ++)。

明确地说,您得到的结果与重复执行减法的结果相同。实际上,JVM并不是以这种方式运行的。(这样会非常低效!) - Stephen C
确实。我希望我的第二段涵盖了JVM实际如何执行此操作的内容。但是我稍微修改了一下我的措辞。 - Bathsheba
1
是的。将“基本上”改为“概念上”会有很大的区别! - Stephen C

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