Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1682323
  • 博文数量: 311
  • 博客积分: 7778
  • 博客等级: 少将
  • 技术积分: 4186
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-09 19:59
个人简介

蓝点工坊(http://www.bluedrum.cn) 创始人,App和嵌入式产品开发。同时也做相应培训和外包工作。 详细介绍 http://pan.baidu.com/s/1y2g88

文章存档

2012年(3)

2011年(115)

2010年(170)

2009年(23)

分类: C/C++

2010-05-21 16:40:21

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表示

 
 
  这种按原始的二进制表示称为原码。是无符号整数(即正整数主要表达方法),这样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]=  0    0000111  B

l       [-7]=   1    0000111  B

        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]=  0   0000111  B

l            [-7] =  1   1111000  B

        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]=   0   0000111  B

l       [-7]=   1   1111001  B

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

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

 

 

l       已知原码,求补码。

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

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

        故:[X]=11001100B[X]=11001011B

         

 

l       已知补码,求原码。

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

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

 

 
 
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表示按有符号输出即这个数的每一位都由01,10,本题问的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循环码.它们跟数字对应关系有
 
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'');
}
}
阅读(2679) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~