补码——Java类型byte强转short发现的坑

做音频开发的同学一般会和byte数组打交道比较多,因为PCM原始数据一般都是byte数组来表示,如果音频的位深是16bit,那就会是连续两个byte元素表示一个音频幅值。如果需要对幅值进行计算,那就要先将两个byte还原回short值(16位数)再进行计算。

我的byte数组是小端存储,于是我想当然的认为short值计算的Java代码应该是:

//2个byte转化为1个short
//将高字节填充到short的高8位,低字节填充到short的低8位
short data = bytes[0] + (bytes[1] << 8);
复制代码

运行结果发现,在两个byte都是正数的情况下无问题,任一个是负数或者两个负数的情况下,会出现高8位数据减去了1的情况。比如:

0xb1 + (0x04 << 8) 会得到值 0x03b1,而不是 0x04b1
复制代码

原因

补码

学计算机组成原理的时候,相信大家都对“补码”一词有概念。这个问题正是补码引起的。

CPU里只有加法器(ALU),没有减法器,因为可以用补码将减法变为加法。

原码和补码的关系:

正数: 补码和原码一致<br>
负数: 原码的符号位不变,其他位取反加1就是补码
如:
-1的原码(8bit) : 1000 0001 
-1的补码(8bit) : 1111 1111 (即0xff)
所以,做个最简答的减法 1 - 1 
得:1 - 1 = 1 + (-1) = 0x01 + 0xff = 0
可见,利用补码和溢出的方式,可以很巧妙的将减法转化为加法
复制代码

强转

强转会发生什么,运行如下代码就会知道。

byte b = -1;            //8位的-1:0xff
short s = (short) b;    //16位的-1: 0xffff
复制代码

所以,负数的byte强转为short时,高八位会全补上1,以保证强转后数据的值不发生变化,但是如果遇上两个byte来表示short的情况,就引发了背景中出现的问题

0xb1 + (0x04 << 8) = 0xffb1 + 0x0400 = 0x03b1
复制代码

解决方式

相信大家也见过很多博客中这样子的代码

byte b = -1;
short s = (short) b & 0x00ff;
复制代码

这样可以保证补的高8位一定是0,而不是1。我们再用这个方法来算一下:

(0xb1 & 0x00ff) + (0x04 << 8) = 0x00b1 + 0x0400 = 0x04b1
复制代码

这样就完美将值进行了还原

原文 

https://juejin.im/post/5e81a6eaf265da48094d8256

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 补码——Java类型byte强转short发现的坑

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址