Java中正数 负数位运算与解析

Java中正数 负数位运算与解析

先说下出现的背景

最近和一位资深嵌入式开发的大佬一起开发一个一栈式(设备-应用项目), 大佬精通底层汇编,负责解析设备侧的数据,
考虑了数据量他改造了数据库的各个字段排开主键和时间,算是每个字段都死扣了,这样可以使cpu计算更快,硬盘耗费更低

CREATE TABLE `gk_report_gps` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `device_id` bigint(20) NOT NULL COMMENT '设备内部ID',
  `battery_volt` smallint(2) NOT NULL COMMENT '电压',
  `battery_power` tinyint(1) NOT NULL COMMENT '电量100%',
  `state` tinyint(1) NOT NULL COMMENT '充电、运动状态',
  `latitude` int(4) NOT NULL COMMENT '纬度0.00001',
  `longitude` int(4) NOT NULL COMMENT '经度0.00001',
  `altitude` smallint(2) NOT NULL COMMENT '高度0.1m',
  `speed` tinyint(1) NOT NULL COMMENT '速度',
  `gps_flag` tinyint(1) NOT NULL COMMENT 'debug',
  `satellite_cal1` tinyint(1) NOT NULL COMMENT 'debug',
  `satellite_cal2` tinyint(1) NOT NULL COMMENT 'debug',
  `satellite_view1` tinyint(1) NOT NULL COMMENT 'debug',
  `satellite_view2` tinyint(1) NOT NULL COMMENT 'debug',
  `pdop` smallint(2) NOT NULL COMMENT 'debug',
  `hdop` smallint(2) NOT NULL COMMENT 'debug',
  `vdop` smallint(2) NOT NULL COMMENT 'debug',
  `utc` char(4) NOT NULL COMMENT 'utc时间',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9573 DEFAULT CHARSET=utf8 COMMENT='gk'

然而入库的时候出现了问题

Caused by: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Out of range value for column ‘altitude’ at row 1

设备数据到应用侧是Base64格式,我依据标准Base64解析成byte数组,再参照数据库类型(数据按各字节转换10进制)解析入库
altitude在应用侧就是2个字节数据,而数据库对应的smallint也是2字节,但是却抛异常了

数据分析原因

debug下先下解析的数据
在这里插入图片描述


而我这边采用的是无符号解析

new BigInteger(1, data).toString(10);

解析altitude为 60765 但是数据库中smallint是带sign的范围为 -2^15 (-32,768) 到 2^15 - 1 (32,767) 显然是存不下这数据量
怎么办呢?
要么把数据库smallint改成4字节int,要么就存入负数,但是大佬直接否定了前者,那就在应用当带符号二进制解析


负数在java中是以补码方式存储的(正数补码是其本身, 负数补码先去反码再末尾+1)

按照当时byte[]{-19, 93}

  • 源码[10010011, 01011101]
  • 补码[11101101, 01011101]

java中二进制化整形采用位运算:&, |

  • &: 按位与(同为1时为1,否则为0)
  • |: 按位或(同为0时为0,否则为1)

而altitude 是一个2字节数据那么拼凑可以这样:

(byte[0] << 8) | byte[1]

但那事实是不行,前者(byte[0] << 8) 左移8为结果为int,负数的话其高位会全部补1也就是说在内存中是: 1111 1111 1111 1111 1110 1101 0000 0000
那么如果byte[1]也是负数, 必然其高位结果会受影响,所以按位与应该要截断高位是这样

(byte[0] << 8) | (byte[1] & 0xFF)

0xFF十六进制 为1111 1111,按位与上一个8为的byte其结果是其本身,但是是这样么? 0xFFint型补齐: 0000 0000 0000 0000 1111 1111 1111 1111
& 0xFF会直接截取一个8位的正数

  • byte数组内存存在形式:[11101101, 01011101]
  • 按位或执行完结果: 1111 1111 1111 1111 1110 1101 0101 1101
  • 结果(源码显示): 1111 1111 1111 1111 1001 0010 1010 0011
  • 结果(十进制显示): -4771

将这-4771存入数据库中

其读取到java内存顺序:

  • 源码显示: 1001 0010 1010 0011
  • java中负数以补码存入: 1111 1111 1111 1111 1110 1101 0101 1101
  • 无符号十进制显示(&FFFF): 60765

这样讲解析后的带符号数还原了原始数据


小结: 计算机组成原理还是需要好好学习的!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值