【java_基础深入】java中short类型相加如何防止溢出

Java中的short的运算

  • 自运算后赋值
short s1 = 3;
short s2 = ++s1; // 编译正常
  • 预备知识 常量优化机制
short var = 10 + 20 // 编译通过

s1 + s2 存在 + 号运算符,Java的+号运算符计算过程,存在变量,自动保存为int的中间变量。

char c1 = 'a';
char c2 = c1 - 22; // 编译错误
				   // 原因:java运算符 看到运算符-号,以ASCII的值进行运算。char为两字节,int为四字节,无法进行自动转化
				   //		运算存在变量c1,无法使用常量优化机制。
int i = c1 - 22;   // 输出 65
  • 相加运算后赋值
short s1 = 666;
short s2 = 777;
// short s3 = s1 + s2; 编译错误,原因同【预备知识】
short s3 = (short)(s1 + s2);

计算机如何处理减法运算

  • 处理的单位
    计算机底层使用二进制码进行运算,bit为处理单位。以short为例,以两个字节也就是8位来存储

  • 为何要处理
    计算机想通过将加减两个运算,统一为加法预算以此来提高效率.。1-2 会被计算机处理成 1+(-2) 。

  • 预备知识【符号位】
    计算机为了区分数值的正负,在数据最高位前插入一个符号位。【0】为正 【1】为负
    short的取值范围为[-32768,32767],为215 次方的数量级,为什么不是216呢,因为有一位保留为符号位
    8 用short来存储时 的二进制是0000 0000 0000 1000,
    同理,-8 的二进制是 1000 0000 0000 1000

  • 预备知识【原码、反码、补码】

  • 【java】
    【规定】所有正数的原码、反码、补码相同
    【规定】负数的 反码 是原码符号位,每位按位取反的结果。补码是反码+1的结果
    8 的原码为0000 0000 0000 1000, 它的反码和补码也是0000 0000 0000 1000
    -8 的原码为1000 0000 0000 1000
    它的反码计算过程:
    1000 0000 0000 1000取反 => 1111 1111 1111 0111
    它的补码计算过程
    1111 1111 1111 0111 +1 => 1111 1111 1111 1000

  • 如何处理
    先求补码:
    8 - 8 => 8 + (-8) 并且统一使用补码计算 8的补码是1111 1111 1111 1000,
    然后按位相加
    __0000 0000 0000 1000
    __1111 1111 1111 1000
    ————————————
    1 0000 0000 0000 0000

    由于short只能存储两个字节,第17位舍去,最终的结果为0000 0000 0000 0000,结果为0;
    思考一个问题: 0000 0000 0000 0000 可以表示为+0, 1000 0000 0000 0000 也可以表示为-0 是否可以优化?
    而处理溢出的问题正是使用了 1000 0000 0000 0000 对溢出临界进行标识
    这么一来

(+0 和 -0)变成了 (+0 和 溢出临界),提高了存储效益。

1000 0000 0000 0000 为什么可以当且仅当得表示溢出临界点,以下将说明

(short)(s1 + s2)处理溢出的相关算法

溢出的边界

short的取值范围是[-215 - 1, 215] 即 [-32768, 32767]
short s = 32767;
short overflow = short (s + 1) 既可以模拟溢出
【java_基础深入】java中short类型相加如何防止溢出_第1张图片
32767的二进制为: 0111 1111 1111 1111 1的补码为 0000 0000 0000 0001
0111 1111 1111 1111
0000 0000 0000 0001
————————————
1000 0000 0000 0000 …标识为溢出边界

溢出边界的计算

上文提到 1000 0000 0000 0000 不是 -0 而是溢出边界。这个溢出边界在java里面打印出为-32768。

为什么 1000 0000 0000 0000 = -32768?
先把 -32768 转成有符号的二级制
1 1000 0000 0000 0000 17位
short类型只占16位,-32768 用16位截断后为 1000 0000 0000 0000
所以截断后的-32768与溢出边界相等。
回顾以下假设:

0000 0000 0000 0000 (带符号)… +0 唯一标识 0
1000 0000 0000 0000 (带符号)…-0 唯一标识 溢出边界

对溢出边界进行特殊读取为1 1000 0000 0000 0000并不破原有的运算逻辑。以下实验

short边界处理 -32768 - 1 为什么等于 32767

-32768 - 1 == -32768 + (-1)
-1 的补码为 1111 1111 1111 1111

__1000 0000 0000 0000 ...........16位
__1111 1111 1111 1111
——————————-——
 1 0111 1111 1111 1111 .........17位

高位截断 变为16位的 0111 1111 1111 1111 = 32767

-32768- 1变成了正数,咋一看很奇怪,其实正是制造溢出的逆运算。边界条件下并不破坏原有逻辑

正运算 32767 + 1 = -32768;
逆运算 -32768 - 1 = 32767;

这种运算有什么用?
做以下测试:
32767 * 2 == 65534;
32767 - 65534 = ?

short overflow = (short) (s + 1); // 制造溢出
 System.out.println("正数溢出后-2*32767的值为:" + (short)(overflow - 2 * Short.MAX_VALUE));
console: 正数溢出后-2*32767的值为:-32766

加上这种溢出处理的机制,(short)(s1 + s2)无论如何都可以正确编译,所有强制转换后的值都在short的取值范围内,并且Java(计算机)不用多余的空间去判断与处理这些溢出值。

结论

java中short类型借用1000 0000 0000 0000 设置为溢出边界,也进一步解释了,short 的取值范围为 [-215 - 1, 215]。这个区间正是多了二进制数1000 0000 0000 0000 可以表示一个数,而不是-0。又由于short类型运算后有截断的机制,1000 0000 0000 0000 参与所有运算依然能够保证运算结果正确。

你可能感兴趣的:(后端,基础,java,开发语言,后端)