c语言整型数据编码方法,嵌入式C语言教程(9)---整数数字表示

Andrew Huang

这一节经常是把初学者搞得很糊涂一节。在C语言处理数字时,不同场合下,我归纳数字的格式要分三个层面:显示格式,编码格式和存储格式。学员包括很多有经验开发者,容易搞糊涂是,有时该用一种格式时引入另外一层面的东西就不知道怎么解释了。

我简单解释一下这三种格式。一种显示格式比较好理解,就是在C语言源代码看到的数字形式。前面已经讲过分为3个进制,10进制,8进制和16进制。用于数学运算的场合。(注意没有二进制表示,不同进制是为表达方便,可以互相转换)

数字编码格式,即数字的表示( Representation)。C语言直接支持只有一种,就是二进制位串(bit strings)来表示一个数字,换句通俗的话说,C语言的数字编码实际上二进制。而且我们知道,不同CPU下,一个类型对应位串的长度可能不一样。比如32位CPU下,一个int占四个字节(Byte),即用32位(bit)长的位串来表示它。

但在嵌入式编程中,我们可能还会碰到另外一个数字编码格式,称为BCD码。 这是一种特殊的格式,C语言并不直接支持,如果想处理BCD码数字,只能写函数来间接处理。但在硬件中很多数字表达会用到这种格式。我们在后面也会解释这种编码。

因为表示的格式与位高度相关,因此在位操作时,必须要考虑数字编码格式。

那么存储格式是指什么呢?这个是指数字经过编码后在内存的实际的排列格式,C语言实际上就是2进制位串在内存中如何排列。有人可能会问,难到大家不是一一对应吗? 实际还是真不是一一对应。在大部分CPU的是按BYTE反着排列的。关于存储格式我们会在后面"数字字节序"这一篇章里详细解释。

当你把一个数字当成一个字节空间时,就需要用存储格式的知误来解释了,比哪你把一个int当成4byte的数组时,你就会发现这种编码格式与存储格式不一致的问题。

1.二进制位串的格式

首先某个基本类型占用宽度是确定的.在32位CPU下,unsigned char /char占一个字节,unsigned int /int占四个字节,unsigned long /long也是占用四个字节.

因为数字编码是二进制表示的.(0,1),某一个类型的数就是内存中以二进制来表示.

我们正整数为例,以unsigned char型的数18为例.

18(10)=00010010(2)

在内存即用上述00010010表示

100521192229.png

这种按原始的二进制表示称为原码。是无符号整数(即正整数主要表达方法),这样unsigned char 能表示最大数字是 256(即的2的8次方),2个byte的unsigned short最大值是 65,536.4个byte的unsigned int 最大值是4,294,967,296.

2.负整数的表示

对于负整数的表示,即对于有符号数数据类型,如int,long,short,char等.最高位用于表示符号位.符号位上为0表正数,1表示负数.

2.1 原码表示法

因此,负数的表示,最容易想到最高位置1,剩下的用原码来表示。,这称为原码表示法.因为少一位,所以可表达的数值,比有符号位要少一半。

在数值前直接加一符号位的表示法

l[+7]原=00000111B

l[-7]原=10000111B

–0就有两个表达方法

l[+0]原=00000000B[-0]原=10000000B

–8位二进制原码的表示范围:-127~+127

这种方法直观易懂,又好理解。但是最大问题出在硬件加法器的设计上。为了硬件设计简单,在硬件加法器不管符号位,整体做位运算的。

l带符号位的原码进行乘除运算时结果正确,而在加减运算的时候就出现了问题.

–正确结果

–1 (10)- 1 (10) =1 (10) + ( -1 )(10) =0 (10)

–用原码运算的结果

–(00000001)原+ (10000001)原= (10000010)原= ( -2 )显然不正确.

2.2 反码表示法

l为解决原码的运算错误,人们提出了反码表示法

–正数:正数的反码与原码相同。

–负数:负数的反码,符号位为“1”,数值部分按位取反。

–反码表示例子

l[+7]反=00000111B

l[-7]反=11111000B

–0也有两种反码表示

l[+0]反=00000000B

l[- 0]反=11111111B

–8位二进制反码的表示范围:-127~+127

反码表示法的缺点

l反码运算例子1

–1(10) -1(10)=1(10) + (-1)(10)=( 0 )(10)

–(00000001)反+ (11111110)反=(11111111)反=( -0 )有问题.

l反码运算例子2

–1(10) -2(10) =1(10) + ( -2 ) (10) =(-1) (10)

–(00000001)反+ (11111101)反=(11111110)反=( -1 )正确

问题出现在(+0)和(-0)上,在人们的计算概念中零是没有正负之分的.

2。3 补码表示法l为了解决上述问题,人们最终采用补码表示法

–正数:正数的补码和原码相同。

–负数的补码则是符号位为“1”,数值部分按位取反后再在末位(最低位)加1。也就是“反码+1”。

–例子

l[+7]补=00000111B

l[-7]补=11111001B

–在补码中用(-128)代替了(-0),所以补码的表示范围为:(-128~0~127)共256个.

l(-128)没有相对应的原码和反码, (-128) = (10000000)

100521194254.png

l已知原码,求补码。

–问:已知某数X的原码为10110100B,试求X的补码和反码。

–解:由[X]原=10110100B知,X为负数。求其反码时,符号位不变,数值部分按位求反;求其补码时,再在其反码的末位加1。

–故:[X]补=11001100B,[X]反=11001011B。

100521194435.png

l已知补码,求原码。

–问:已知某数X的补码11101110B,试求其原码。

–解:由[X]补=11101110B知,X为负数。求其原码表示时,符号位不变,数值部分按位求反,再在末位加1。

100521194441.png

3.常见补码的求值

3.1 0取反的值是多少?

printf("%d",~0);

0取反.即全1.是一个负数.将其当成补码,减去1后再取反,得到1.上述打印值是-1

3.2 -1 取反的值是多少?

printf("%d",~(-1));

-1的补码是 全1.取反后是0.因此其取反是输出是0

3.3 问:printf(“%d”,~2)输出?

–~是表示位取反,%d表示按有符号输出即这个数的每一位都由0变1,1变0,本题问的2取反后,新的有符号数的值是什么?

l解:2是正数,采用原码表示10B,%

4.BCD码

4.1 BCD码定义

BCD全称Binary-Coded Decimal‎.与二进制位串不同,这种编码方式.是把每一个十进制数不考虑进位关系,每一个数字都折分成一个4位的二进制数.这种转换关系非常简单实用.

为什么用4位呢,因为0-9的10个数字必须要4位二进制位足够表达.但是4位二进制能表示16个数字.因此在BCD还因为如何从16个数字选择10个数字,还细分多种表示方法.

4位二进制数码有16种组合,原则上可任选其中的10种作为代码,分别代表十进制中的0,1,2,3,4,5,6,7,8,9 这十个数字。如何选择10个数字,BCD细分为多种编码, 最常用的BCD码称为8421码.即用就是使用"0"至"9"这十个数值的二进码来表示.8.4.2.1 分别是4位二进数的位取值.

即有如下对应关系.

0=0000

1=0001

2=0010

3=0011

4=0100

5=0101

6=0110

7=0111

8=1000

9=1001

常见BCD编码有 除了8421.还有5421码 2421码 余3码 余3循环码.它们跟数字对应关系有

100522205338.jpg

4.2 BCD码与十进制转换关系

BCD码与十进制数的转换.关系直观,相互转换也很简单.直接将对位数字换成对应BCD编码即可.

3 2 1(10) = 0011 0010 0001 (bcd)

BCD还能表示小数,它一般用指定位宽来规定小数.以小数位为2位来算.

75.4= 0111  0101.0100 0000

85 = 1000 0101.0000 0000

这种称为定点小数,既可保存数值的精确度,又可免却使电脑作浮点运算时所耗费的时间。缺点就是不能表示过大或过小的数字.

注意一个同一个unsigned char 中00011000,当把它视为二进制数时,其值为24;但作为2位BCD码时, 其值为18。 完全看是如何理解的.

4.3 压缩BCD码和非压缩BCD码

一个ASCII码占8bit,其中'0'~'9'的值低4位正好跟8421码相等。有,时为了处理方法,还有一种用8bit表示BCD数字,其高4位总为0.这种编码称为非压缩BCD码(Unpacked BCD),而用4bit表示的BCD称为压缩BCD吗(packed BCD)

Decimal

Binary

BCD

Unpacked

Packed

0

0000 0000

0000 0000

0000 0000

1

0000 0001

0000 0001

0000 0001

2

0000 0010

0000 0010

0000 0010

3

0000 0011

0000 0011

0000 0011

4

0000 0100

0000 0100

0000 0100

5

0000 0101

0000 0101

0000 0101

6

0000 0110

0000 0110

0000 0110

7

0000 0111

0000 0111

0000 0111

8

0000 1000

0000 1000

0000 1000

9

0000 1001

0000 1001

0000 1001

10

0000 1010

0000 0001 0000 0000

0001 0000

11

0000 1011

0000 0001 0000 0001

0001 0001

12

0000 1100

0000 0001 0000 0010

0001 0010

13

0000 1101

0000 0001 0000 0011

0001 0011

14

0000 1110

0000 0001 0000 0100

0001 0100

15

0000 1111

0000 0001 0000 0101

0001 0101

16

0001 0000

0000 0001 0000 0110

0001 0110

17

0001 0001

0000 0001 0000 0111

0001 0111

18

0001 0010

0000 0001 0000 1000

0001 1000

19

0001 0011

0000 0001 0000 1001

0001 1001

20

0001 0100

0000 0010 0000 0000

0010 0000

4.4 BCD码运算

由于编码是将每个十进制数用一组4位二进制数来表示,因此,若将这种BCD码直接交计算机去运算,由于计算机总是把数当作二进制数来运算,所以结果可能会出错。例:用BCD码求38+49。

解决的办法是对二进制加法运算的结果采用"加6修正,这种修正称为BCD调整。即将二进制加法运算的结果修正为BCD码加法运算的结果,两个两位BCD数相加时,对二进制加法运算结果采用修正规则进行修正。

修正规则:

(1)如果任何两个对应位BCD数相加的结果向高一位无进位,若得到的结果小于或等于9,则该不需修正;若得到的结果大于9且小于16时,该位进行加6修正。

(2)如果任何两个对应位BCD数相加的结果向高一位有进位时(即结果大于或等于16),该位进行加6修正.

(3)低位修正结果使高位大于9时,高位进行加6修正。

下面通过例题验证上述规则的正确性。

用BCD码求35+21 BCD码求25+37 用BCD码求38+49 用BCD码求42+95

用BCD码求91+83 用BCD码求94+7 用BCD码求76+45

关于BCD码,参见如下文章了解更多。

练习

1.printf("%d",(unsigned int)~2); 输出结果

2.写一个ASCII数字串转换成非压缩BCD码的转换算法

void BcdToAscii (char *ascii_buf, const BYTE *bcd_buf, int len)

{

int i;

char ch;

for (i=0; i{

if (i & 1) ch = *(bcd_buf++) & 0x0f;

else ch = *bcd_buf >> 4;

ascii_buf[i] = ch + ((ch > 9)? ''A''-10 : ''0'');

}

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值