作者: Sam (甄峰) sam_code@hotmail.com
0.
之前对窄数据类型向宽数据类型转换转换时,符号扩展,零扩展稍有了解,具体什么时候用符号扩展,什么地方用零扩展,并不清楚。所以常用按位与(&)
按位或(|) 等操作来保证扩展部分与编程思路的统一。
这次在使用iBeacon协议时,信号强度的dbm值保存在一个字节中。有时作为原码,有时又要求补码。为了验证,常要printf出来。
这下就要求把零扩展和符号扩展搞清楚。
1. 何时使用零扩展,何时使用符号扩展:
当窄数据类型为:有符号数据类型时,扩展为宽数据类型时,使用符号扩展。
当窄数据类型为:无符号数据类型时,扩展为宽数据类型时,使用零扩展。
例1: 有符号数扩展(符号扩展)
int8_t rssi = 0xBE;
int rssi_4byte = 0;
rssi_4byte = rssi;
解析:
rssi为8位有符号数,则使用符号扩展。
0xBE: 10111110B.
最高位为1。所以使用1扩展到其它3个byte.
rssi_4byte: 11111111 11111111 11111111 10111110B = - 0x42 =
-66.
例2:无符号数扩展(零扩展)
uint8_t rssi = 0xBE;
0xBE=10111110B
rssi_4byte: 00000000 00000000
00000000 10111110B = 0xBE = 190
例3:rssi实际使用中遇到的问题:
1个字节保存RSSI:0x9F.
它的含义其实是:1001 1111
最高位:1表示负值。 其实表示:-0x1F. = -31
如果这样处理:
int8_t rssi = 0x9F;
printf("RSSI: [%d] 0x%x\n", rssi, rssi);
此处,因为打印的是%d. 所以会临时扩展为4byte.
又因为是int8_t, 是有符号。所以会符号扩展。
rssi会被扩展为:FFFFFF9F.
所以打印结果是: RSSI [-97],
0xFFFFFF9F
这显然与希望的结果不同。
如果这样处理:
uint8_t rssi= 0x9F;
printf("RSSI: [%d] 0x%x\n", rssi, rssi);
此处,因为打印的是%d. 所以会临时扩展为4byte.
又因为是uint8_t, 是无符号。所以会零扩展。
rssi会被扩展为:0000009F.
打印结果为:RSSI
[159] 0x9F.
这显然与希望的结果不同。
仔细分析下来: 0x9F 其实是 RSSI的原码。那如何得到原始值就很清晰了。
最高位是符号位,其它位是绝对值。
int8_t rssi =
0x9F;
if(rssi > 0)
{
printf("RSSI: [%d]
[0x%x]", (int)rssi, (int)rssi);
}
else
{
printf("RSSI: [-%d] [-0x%x]", (rssi&0x7F), (rssi&0x7F));
}
附1:
原码:
如果机器字长为n,那么一个数的原码就是用一个n位的二进制数,其中最高位为符号位:正数为0,负数为1。剩下的n-1位表示概数的绝对值。
例如: X=+101011 , [X]原= 00101011
X=-101011
, [X]原= 10101011
位数不够的用0补全。
PS:正数的原、反、补码都一样:0的原码跟反码都有两个,因为这里0被分为+0和-0。
反码:
知道了什么是原码,那反码就更是张飞吃豆芽——小菜一碟了。知道了原码,那么你只需要具备区分0跟1的能力就可以轻松求出反码,为什么呢?因为反码就是在原码的基础上,符号位不变其他位按位取反(就是0变1,1变0)就可以了。
例如:X=-101011 ,
[X]原= 10101011 ,[X]反=11010100
补码:
补码也非常的简单就是在反码的基础上按照正常的加法运算加1。
例如:X=-101011 ,
[X]原= 10101011 ,[X]反=11010100,[X]补=11010101
PS:0的补码是唯一的,如果机器字长为8那么[0]补=00000000。
移码:
移码最简单了,不管正负数,只要将其补码的符号位取反即可。
例如:X=-101011 , [X]原= 10101011 ,[X]反=11010100,[X]补=11010101,[X]移=01010101