补码编码、有符号无符号转化、字符扩展与截断

1. 无符号数的编码

无符号数的编码其实就是我们平时认知的二进制表示形式,比如0b00001101=123+122+120=13

其一般定义为:

对于向量 x=[xw1,xw2,...,x0]:

其转化为无符号变量后的值为

B2Uw(x)=i=0w1xi2i

就是基本的数学进制转化表示方法,比较简单。

2. 补码编码

2.1补码(two's complement)

我们不仅仅需要表示无符号的值,有些时候我们也要表示有符号的值,而计算机最常见的表示有符号数的方式为补码形式,补码的表示方式中,把最高位解释成负权,在第1节无符号数的编码时,可以看出向量x的各位都是按照正权来解释的。

那么我们下面给出补码的定义:

对向量x=[xw1,xw2,...,x0]:

其表示的有符号值为:

B2Tw(x)=xw12w1+i=0w2xi2i

可以看出除了最高位是按照负权解释外,其余和无符号数的编码相同。

  • 对于一个字节的有符号数补码 0xFF = -1;

那么计算机为什么使用补码呢?下面介绍下源码和反码

2.2 原码和反码

原码(Sign-Magnitude):最高位是符号位,用来确定剩下的位是使用正权还是负权,其表示的值为:

B2Sw(x)=1xw1i=0w2xi2i

  • 对于一个字节的有符号数补码 0xFF = -127;

反码(Ones' Complement):除最高有效位的权是(2w11) 而不是(2w1)外,其他各位的解释方式和补码是一样的。

B2Ow(x)=xw1(2w11)+i=0w2xi2i

从公式可以看出,相同的位向量x 使用反码解释得到的值-1就得到了对应的使用补码解释得到的值。

从公式同样可以看出,当最高位是0时,反码和原码解释得到的值是一样的,

  • 0b0000 0000 ---> 原码表示0,反码也是表示0.

但当最高位是1时,

  • 0b1000 0000 ---> 原码表示0,反码表示-127

也就是说当最高位时1时,补码解释0b1000 0000 == 原码解释 0b1111 1111 (-127),此时可以看出原码解释到反码解释的转化,只需把位图 x 最高位保持不变,其余各位取反。

细心的朋友可能会发现,对于0b1000 0000 用原码解释得到值0,对于0b0000 0000用原码解释得到的值也是0,这就是原码表示的缺点,就是对于零有两个位图 x 来表示。

那么反码是否也有这个问题呢,答案是肯定的0b1111 1111 和0b0000 0000 在反码解释中都是0.

2.3 补码和反码的来源

补码的名字为two's complement,反码的名字为ones' complement. 注意这两名字的撇号的位置是不一样的。这归结与补码和反码的来源。

术语补码来源于这样一种情况,即对于非负数x,我们用 2wx 计算-x的w位表示。

术语反码来源于这样一种情况,即对于非负数x, 我们用[1111..1]-x来表示-x。

可见看原英文名更为贴切。

其实对于正数,用原码、反码或补码编码,其最终位向量 x 是一样的。只有负数时,才有区别。而0是原码和反码都有两种表示方式,而补码对所有数都只有一种表示方式。

3.有符号无符号转化

注意一下所讲的有符号全部是用补码表示的。

有符号与无符号的相互转化其实也比较简单,就是位图 x 保持不变,变化的只是对位图 x 的解释。当用符号时,就用补码解释,当为无符号时,就用无符号数的编码来解释。虽然位图 x 是不变的,但是解释出来的值却存在某种关系(此关系不是很重要,自己推一下就行了,本质还是位图 x 不变)。

3.1 无符号-->有符号

对满足 0<=u<=UMaxwu 有:

U2Tw(u)={u, u<=TMaxwu2w,u>TMaxw

举例(以一个字节为例):

0b1111 1111 当是无符号表示时,它的值为255, 当是有符号值表示时,它的值是-1. (1=25528)

推导过程如下:

一个位图 x 用无符号表示时的值:

(1)B2Uw(x)=i=0w1xi2i

一个位图 x 用有符号表示时的值:

(2)B2Tw(x)=xw12w1+i=0w2xi2i1

(1)(2)得到差值为xw12w1+xw12w1=xw12w

  • xw1为0时,差值为0,也就是当最高位为0时,无符号表示的值和有符号补码表示的值相等。
  • xw1 为1时,差值为 2w,所以,有符号值(2) = 无符号值(1) - 2w

3.2 有符号-->无符号

对满足 TMinw<=x<=TMaxwx 有:

T2Uw(u)={x+2w,x<0x,x>=0

举例(以一个字节为例):

0b1111 1111 当是有符号表示时,它的值为-1, 当是无符号值表示时,它的值是255. (255=1+28)

推导过程与3.1中类似。

有符号和无符号转化时的坑:

当一个运算数是有符号的,另一个是无符号的,那么c语言会隐式的把有符号的数转为无符号的数,再进行计算,也就是说变成了两个非负数的运算。当是算术运算时,是没有影响的。但是对于逻辑运算就存在很大的坑。

printf("%d\n", -1 > 2U); // 1  
printf("%d\n", -1 > 0U); // 1   

输出的结果都是true,这显然不满足我们的预期。

4.字符扩展

所谓字符扩展就是,比如当一个1字节的数,转化为2字节的数时,在高位补充数据,那么高位应该填补0还是1呢?

我们先说结论:

  • 无符号数,0扩展,即高位补0
  • 有符号数,符号位扩展,即高位补扩展前高位的值。

我们举个例子:

#include<stdio.h>
void printByte(void* c, int n){
    for(int i = 0; i < n; ++i){
         printf("%x ", *((unsigned char*)c+i));
    }
    printf("\n");
}
int main(){
    unsigned char a = 0x12;            //0b00010010
    short b = (unsigned short)a;
    printf("%u\n", b);  		// 18, 16+2 = 18
    printByte(&b, sizeof(b));           // 12 00, (小端)也即使0x0012
    
    signed char a1 = 0x87; 		// 0b10000111
    short b1 = (short)a1;
    printf("%d\n", b1);    		// -121
    printByte(&b1, sizeof(b1));         // 87 ff, (小端)也即使0xff87
      
    //注意,当a1位unsigned char时,情况就发生了变化。
    unsigned char a2 = 0x87; 	        // 0b10000111
    short b2 = (short)a2;  	        // 在此时转化,推测是unsigned char ->unsigned short -> short
    printf("%d\n", b2);     	        // 135
    printByte(&b2, sizeof(b2));	        // 87 00, (小端)也即使0xff87
    
    return 0;
}

5.字符截断

与字符扩展相反,字符截断是,比如当一个2字节的数,转化为1字节的数时,应截断去掉高位数据,那么仅仅去掉高位的数据就行了么,不需要额外的其他操作了吗? 答案是肯定的,只需要把高位去掉,保留低位就行

  • 对于无符号数据,这是显然的。
  • 对于有符号数据,同样如此。
int main(){
    unsigned short a = 0x1288; 	        //0b0001 0010 1000 1000
    unsigned char b = (unsigned char)a;
    printf("%u\n", b);  		// 136, 16+2 = 18
    printByte(&b, sizeof(b));	        // 88, 也即使0x88
    
    short a1 = 0x1288; 			//0b0001 0010 1000 1000
    char b1 = (char)a1;
    printf("%d\n", b1);    		// -120
    printByte(&b1, sizeof(b1));         // 88, 也即使0x88
      
    unsigned short a2 = 0x1288;         //0b0001 0010 1000 1000
    char b2 = (char)a2;  		
    printf("%d\n", b2);     	        // -120
    printByte(&b2, sizeof(b2));         // 88, 也即使0x88
    
    return 0;
}

可以看出,字符截断,其实就是直接了当的,把高位的数据扔掉,只保留低位的数据就行了。

posted @   yangbofun  阅读(2699)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示