• Java中整数基础知识


    原文链接 Java中整数基础知识

    最近做了一道题,非常有意思,题本身很简单,但涉及到整数的最大值以及最小值,当写测试用例的时候,却犯了一个错误,发现最小整数并不是0xFFFFFFFF,我们来仔细看一下。

    整数基础

    Java中,整数都是有符号的,最高位是符号位,0表示正数,1表示负数。有四种,byte,short,int和long。

    • byte 8位,-2^7 ~ 2^7 - 1,-128 ~ 127, 0x80 ~ 0x7F
    • short 16位,-2^15 ~ 2^15 - 1,-32768 ~ 32767, 0x8000 ~ 0x7FFF
    • int 32位,-2^31 ~ 2^31 -1,-2147483648 ~ 2147483647, 0x8000000 ~ 0x7FFFFFFF
    System.out.println(String.format("Max byte %d, 0x%X, half 0x%X", Byte.MAX_VALUE, Byte.MAX_VALUE, Byte.MAX_VALUE/2));
    System.out.println(String.format("Min byte %d, 0x%X, half 0x%X", Byte.MIN_VALUE, Byte.MIN_VALUE, (byte)(Byte.MIN_VALUE/2)));
    System.out.println(String.format("Max short %d, 0x%X, half 0x%X", Short.MAX_VALUE, Short.MAX_VALUE, Short.MAX_VALUE/2));
    System.out.println(String.format("Min short %d, 0x%X, half 0x%X", Short.MIN_VALUE, Short.MIN_VALUE, (short) (Short.MIN_VALUE/2)));
    System.out.println(String.format("Max Int %d, 0x%X, half 0x%X", Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE/2));
    System.out.println(String.format("Min Int %d, 0x%X, half 0x%X", Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE/2));
            
    // Outputs
    //Max byte 127, 0x7F, half 0x3F
    //Min byte -128, 0x80, half 0xC0
    //Max short 32767, 0x7FFF, half 0x3FFF
    //Min short -32768, 0x8000, half 0xC000
    //Max Int 2147483647, 0x7FFFFFFF, half 0x3FFFFFFF
    //Min Int -2147483648, 0x80000000, half 0xC0000000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    细节和原理

    值得注意的是,16进制的数值与直觉预期并不一样,特别是负数。正数是一致的,比如int,一共是32位,最高位是符号,所以真正数值部分是31位,那么最大的int就是0x7FFFFFF。

    但负数,也即是最小的int,却与直觉完全不一样。按照直觉,负数最高位是1,那最小的int应该是0xFFFFFFFF啊,为何确是0x80000000呢?原因就是整数的编码方式并不是直接的二进制形式的,是以补码的形式,也就是说在内部实现中,用二进制表示一个整数的时候,是以二进制补码形式(转成二进制后还要求其补码,才是真实的二进制和16进制形式)。

    简单来说,补码是一种二进制编码形式,正数的补码就是它的本身,而负数的补码是其取反后加1,可以参考百科上的定义

    负数的转换:原码取反加1,即是补码,补码再转补码即得到原码(也可以补码减1再取反即是原码),符号位在转换过程中一直不变。

    **注意:**补码的严谨说法是2的补码(Twi’s Complement),并不称作二进制补码。补码是为了能用加法的方式来计算减法。因此原码转补码时,取反加1,补码求原码时再求补码,避免用减法(虽然减1后再取反也能求得原码,但需要用到减法)。

    非10进制字面常量是补码形式

    在日常代码中,为了方便,通常都用16进制来写一些整数常量,这里就特别要注意了。16进制的字面常量,不会再进行补码转换,会当成补码直接使用。

    System.out.println(String.format("Literal %d, 0x%X", 0xffffffff, 0xffffffff));
    //Literal -1, 0xFFFFFFFF
    
    • 1
    • 2

    所以,你写的0xFFFFFFFF是补码形式,它的原码是减1再取反,(32个1)减1,最低位变成0,前面31个1,再取反,就只剩下最后一位是1和最高位的符号位,因此是-1,注意符号位是不变的,在转换过程中。

    而最小的整数是-2^31,原码 形式应该是0x80000000,先取反变成了0xFFFFFFFF,再加1,符号位最高位不变的情况下,其余全变成了0,所以是0x80000000。

    最小整数

    开篇时说了,当时错误的认为0xFFFFFFFF是最小的整数,这里犯的第一个严重错误是,误把二进制的补码当成了原码,代码中的16进制(二进制)都是补码形式的,它的原码是0x80000001即-1。这个错误是比较明显的。

    但另外的问题就是,假如都是二进制原码的情况下,为啥最小的整数是0x80000000而不是0xFFFFFFFFF。这是理解上的误区,整数的定义是,最高位是符号位,所以常规认知是全是1的情况是最大的数,加上符号不就变成最小的了么?这是以10进制思维,也就是二进制转换成为10进制后的想法。计算机只认识二进制,在最高位是1(负数)的情况下,哪个数最小?当然0x80000000最小啊,它除了符号位全是0,肯定 小于0xFFFFFFFF,因此从二进制的角度来理解,0x80000000是最小的整数。

    而0xFFFFFFFF(原码)则是第2小的负整数,最高位是符号位,其余31位全是1,它的补码是0x80000001:

    System.out.println(String.format("Literal %d 0x%X", 0x80000001, 0x80000001));
    //Literal -2147483647 0x80000001
    
    • 1
    • 2

    计算机中是以二进制补码来存储整数的,所以要从计算机的角度来理解比较,就是要用二进制的补码来比较两个数的大小。

    再次强调,我们写的源码当中的二进制(无论是字面常量,还是打印输出)都是补码形式,计算机看到的也是补码,比较也是补码,只有当转换成为10进制时,才会还原为原码并进行10进制转换

    由此得出,(注意,程序员眼睛看到的16进制全是补码形式):

    • 0x80000000是最小的负数,原码为0x80000000,-2^31
    • 0x80000001,第2小的负数(最小的0x80000000再加上1),原码为0xFFFFFFFF,-(2^31-1)
    • 0xFFFFFFFF,是-1,原码为0x80000001。它是最大的负数(-1是最大的负数)。0xFFFFFFFF(全是1)肯定 最大啊,最高位是1,是负数,所以是最大的负数。

    一些有意思的值

    Integer.MAX_VALUE + 1 = Integer.MIN_VALUE

    按理说应该溢出了,但如果以16进制去计算,就是这样的结果:0x7FFFFFFF + 1 = 0x80000000

    System.out.println(String.format("Max in %d (0x%X) + 1 = %d (0x%X)", Integer.MAX_VALUE, Integer.MAX_VALUE, (Integer.MAX_VALUE+1), (Integer.MAX_VALUE+1)));
    // Max in 2147483647 (0x7FFFFFFF) + 1 = -2147483648 (0x80000000)
    
    • 1
    • 2

    Integer.MIN_VALUE - 1 = Integer.MAX_VALUE

    System.out.println(String.format("Min in %d (0x%X) - 1 = %d (0x%X)", Integer.MIN_VALUE, Integer.MIN_VALUE, (Integer.MIN_VALUE-1), (Integer.MIN_VALUE-1)));
    //Min in -2147483648 (0x80000000) - 1 = 2147483647 (0x7FFFFFFF)
    
    • 1
    • 2

    参考资料

    原创不易,打赏点赞在看收藏分享 总要有一个吧

  • 相关阅读:
    spring boot logback.xml文件配置,info、error隔离
    【编程之路】面试必刷TOP101:动态规划(78-82,Python实现)
    【分布式】深度学习中分布式训练的现状及未来
    Stable Diffusion 系统教程 | 强大的ControlNet 控制网
    《A DECODER-ONLY FOUNDATION MODEL FOR TIME-SERIES FORECASTING》阅读总结
    SpringBoot开发实用篇复习1
    这么分页,小心有坑
    怎样在CSDN插入代码块 怎么变色?
    Codeforces Global Round 21 C D E
    手动实现Transformer
  • 原文地址:https://blog.csdn.net/hitlion2008/article/details/133999289