原码
我们用第一个位表示符号(0为非负数,1为负数),剩下的位表示值。比如:
[+8] = [00001000]原
[-8] = [10001000]原
反码
我们用第一位表示符号(0为非负数,1为负数),剩下的位,非负数保持不变,负数按位求反。比如:
[+8] = [00001000]原 = [0000 1000]反
[-8] = [10001000]原 = [1111 0111]反
如果我们用原码或者补码来表示整数的二进制,有什么问题么?表面上看,似乎挺好的。不过仔细思考就会发现两个问题:
第一、0居然用两个编码(+0和-0)来表示了:
原码:[0000 0000]原 = [1000 0000]原
反码:[0000 0000]反 = [1111 1111]反
第二、计算机要理解符号位的存在,否则符号位参与运算,就会出现诡异的现象:
原码:
1 + (-1)
= [00000001]原 + [1000 0001]原
= [10000010]原
= -2
明显是不对的!
反码:
1 + (-1)
= [00000001]反 + [1111 1110]反
= [1111 1111]反
= -0
表现的好诡异!
为了解决这些问题,我们在计算机体系中引入了补码。
补码
我们用第一位表示符号(0为非负数,1为负数),剩下的位非负数保持不变,负数按位求反末位加一。
[+8] = [00001000]原 = [0000 1000]补
[-8] = [10001000]原 = [1111 1000]补
那我们再看看,把符号放进去做运算会有什么样的效果呢?
1 + (-1)
= [00000001]补 + [1111 1111]补
= [0000 0000]补
= 0
很明显,通过这样的方式,计算机进行运算的时候,就不用关心符号这个问题,而只需要按照统一的逢二进一的原则进行运算就可以了。
zigzag算法:
//负数右移后高位全变成1,再与左移一位后的值进行异或,就把高位那些无用的1全部变成0了,巧妙!
int int_to_zigzag(int n) {
return (n << 1) ^ (n >> 31);
}
//我们还原的代码就反过来写就可以了。不过这里要注意一点,就是右移的时候,需要用不带符号的移动,否则如
//果第一位数据位是1的话,就会补1
int zigzag_to_int(int n) {
return (((unsigned int)n) >> 1) ^ -(n & 1);
}
字节自表示方法
zigzag引入了一个方法,就是用字节自己表示自己
/*
(~0x7f)16=(11111111_11111111_11111111_10000000)补
他就是从倒数第八位开始,高位全为1的数。他的作用,就是看除开最后七位后,还有没有信息。
我们把zigzag值传递给这个函数,这个函数就将这个值从低位到高位切分成每7bits一组,如果高位还有有效信息,则给这7bits补上1个bit的1(0x80)。如此反复 直到全是前导0,便结束算法。
*/
int write_to_buffer(int z, byte* buf, int size)
{
int ret = 0;
for (int i = 0; i < size; i++)
{
if ((z & (~0x7f)) == 0) {
buf[i] = (byte)z;
ret = i + 1;
break;
}
else
{
buf[i] = (byte)((z & 0x7f) | 0x80);
z = ((unsigned int)z) >> 7;
}
}
return ret;
}
/*
整个过程就和压缩的时候是逆向的:对于每一个字节,先看最高一位是否有1(0x80)。如果有,就说明不是最后一个数据字节包,那取这个字节的最后七位进行拼装。否则,说明就是已经到了最后一个字节了,那直接拼装后,跳出循环,算法结束。最终得到4字节的整数。
*/
int read_from_buffer(byte* buf, int max_size)
{
int ret = 0;
int offset = 0;
for (int i = 0; i < max_size; i++, offset += 7)
{
byte n = buf[i];
if ((n & 0x80) != 0x80)
{
ret |= (n << offset);
break;
}
else
{
ret |= ((n & 0x7f) << offset);
}
}
return ret;
}
原文:https://blog.csdn.net/zgwangbo/article/details/51590186