二进制负数:
原码就是原来的表示方法
反码是除符号位(最高位)外取反
补码=反码+1
1个字节它不管怎么样还是只能表示256个数,因为有符号所以我们就把它表示成范围:-128-127。它在计算机中是怎么储存的呢?可以这样理解,用最高位表示符号位,如果是0表示正数,如果是1表示负数,剩下的7位用来储存数的绝对值的话,能表示27个数的绝对值,再考虑正负两种情况,27*2还是256个数。首先定义0在计算机中储存为00000000,对于正数我们依然可以像无符号数那样换算,从00000001到01111111依次表示1到127。那么这些数对应的二进制码就是这些数的原码。到这里很多人就会想,那负数是不是从10000001到11111111依次表示-1到-127,那你发现没有,如果这样的话那么一共就只有255个数了,因为10000000的情况没有考虑在内。实际上,10000000在计算机中表示最小的负整数,就是这里的-128,而且实际上并不是从10000001到11111111依次表示-1到-127,而是刚好相反的,从10000001到11111111依次表示-127到-1。负整数在计算机中是以补码形式储存的,补码是怎么样表示的呢,这里还要引入另一个概念——反码,所谓反码就是把负数的原码(负数的原码和和它的绝对值所对应的原码相同,简单的说就是绝对值相同的数原码相同)各个位按位取反,是1就换成0,是0就换成1,如-1的原码是00000001,和1的原码相同,那么-1的反码就是11111110,而补码就是在反码的基础上加1,即-1的补码是11111110+1=11111111,因此我们可以算出-1在计算机中是按11111111储存的。总结一下,计算机储存有符号的整数时,是用该整数的补码进行储存的,0的原码、补码都是0,正数的原码、补码可以特殊理解为相同,负数的补码是它的反码加1。下面再多举几个例子,来帮助大家理解!
十进制 → 二进制 (怎么算?要是不知道看计算机基础的书去)
47 → 101111
有符号的整数 原码 反码 补码
47 00101111 00101111 00101111(正数补码和原码、反码相同,不能从字面理解)
-47 10101111 11010000 11010001(负数补码是在反码上加1)
///////////////////////////
c语言数据类型长度
C语言中,只是能够明确sizeof(short)<sizeof(long),sizeof(short)≤sizeof(int)≤sizeof(long)而已,至于的int具体是16位还是32位的,取决于平台和语言实现(编译器)。< span="">在VC++(x86)等32位环境中,int和long都表示32位有符号整数,范围是一样的。
//////////////////////////
代码示例:
static get_utili(constchar*p){int util;…while(isspace((int)*p))//跳过空格++p;util=(int)*p++;…}
现象&后果:
当传入的参数p指向的内容为0x9A、0XAB等内容(最高位为1)时,得到的int型变量util的值将会出错,因为char会进行符号扩展,使得0x9A(十进制的154)变成了-102。会造成程序运行时的数据处理错误。
Bug分析:
char符号扩展是与编译器相关的,但在x86平台上,对于任何主流的编译平台,char总是进行符号扩展的。上述代码在将char型的*p赋给int型变量util的时候,需要先进行char型到unsigned char型的转换,以避免按照char的最高位进行符号扩展。
上述出错代码的符号扩展过程如下:
因为要扩展的短数据类型为有符号数的-- char x=10011100b(即0x9A)
因而在int y=(int)x时--进行符号扩展,即短数据类型的符号位填充到长数据类型的高字节位(比短数据类型多出的那一部分),则y的值为11111111 10011100b(变成了十进制的-102);
但是,将要扩展的短数据类型变成无符号数后--unsigned char x=10011100b(即0x9A)
在 int y=(int)x时--进行扩展的时候是以零扩展,即用零来填充长数据类型的高字节位,则y的值应为00000000 10011100b(十进制的154)。
正确代码:
util=(int)*p++;改成util=(int)(unsigned char)*p++
Bug定位:
该bug是在code review的过程中发现的。
char符号扩展的问题,如果在测试时没有构造相应的case,就会很难被发现。面对这类问题,细致的code review是必不可少的,不管是通过code review直接发现问题还是通过review来丰富相应case的构造,code review都应该是一个不可缺少的环节。
关于符号扩展
一、短数据类型扩展为长数据类型
1、要扩展的短数据类型为有符号数的
进行符号扩展,即短数据类型的符号位填充到长数据类型的高字节位(即比短数据类型多出的那一部分),保证扩展后的数值大小不变
如1:char x=10001001b; short y=x; 则y的值应为11111111 10001001b;
2:char x=00001001b; short y=x; 则y的值应为00000000 00001001b;
2、要扩展的短数据类型为无符号数的
进行零扩展,即用零来填充长数据类型的高字节位
如1:unsigned char x=10001001b; short y=x; 则y的值应为00000000 10001001b;
2:unsigned char x=00001001b; short y=x; 则y的值应为00000000 00001001b;
二、长数据类型缩减为短数据类型
如果长数据类型的高字节全为1或全为0,则会直接截取低字节赋给短数据类型;如果长数据类型的高字节不全为1或不全为0,则转会就会发生错误。
三、同一长度的数据类型中有符号数与无符号数的相互转化
直接将内存中的数据赋给要转化的类型,数值大小则会发生变化。另短类型扩展为长类型时,但短类型与长类型分属有符号数与无符号数时,则先按规则一进行类型的扩展,再按本规则直接将内存中的数值原封不动的赋给对方。
附:有符号数的转换
从 |
到 |
方法 |
char |
short |
符号位扩展 |
char |
long |
符号位扩展 |
char |
unsigned char |
最高位失去符号位意义,变为数据位 |
char |
unsigned short |
符号位扩展到short;然后从short转到 unsigned short |
char |
unsigned long |
符号位扩展到long; 然后从long 转到unsigned long |
char |
float |
符号位扩展到long; 然后从long 转到float |
char |
double |
符号位扩展到long; 然后从long 转到double |
char |
long double |
符号位扩展到long; 然后从long 转到long double |
short |
char |
保留低位字节 |
short |
long |
符号位扩展 |
short |
unsigned char |
保留低位字节 |
short |
unsigned short |
最高位失去符号位意义,变为数据位 |
short |
unsigned long |
符号位扩展到long; 然后从long转到unsigned double |
short |
float |
符号位扩展到long; 然后从long 转到float |
short |
double |
符号位扩展到long; 然后从long 转到double |
short |
long double |
符号位扩展到long; 然后从long 转到double |
long |
char |
保留低位字节 |
long |
short |
保留低位字节 |
long |
unsigned char |
保留低位字节 |
long |
unsigned short |
保留低位字节 |
long |
unsigned long |
最高位失去符号位意义,变为数据位 |
long |
Float |
使用单精度浮点数表示。可能丢失精度。 |
long |
double |
使用双精度浮点数表示。可能丢失精度。 |
long |
long double |
使用双精度浮点数表示。可能丢失精度。 |
无符号数的转换
从 |
到 |
方法 |
unsigned char |
char |
最高位作为符号位 |
unsigned char |
short |
0扩展 |
unsigned char |
long |
0扩展 |
unsigned char |
unsigned short |
0扩展 |
unsigned char |
unsigned long |
0扩展 |
unsigned char |
float |
转换到long; 再从 long 转换到float |
unsigned char |
double |
转换到long; 再从 long 转换到double |
unsigned char |
long double |
转换到long; 再从 long 转换到double |
unsigned short |
char |
保留低位字节 |
unsigned short |
short |
最高位作为符号位 |
unsigned short |
long |
0扩展 |
unsigned short |
unsigned char |
保留低位字节 |
unsigned short |
unsigned long |
0扩展 |
unsigned short |
float |
转换到long; 再从 long 转换到float |
unsigned short |
double |
转换到long; 再从 long 转换到double |
unsigned short |
long double |
转换到long; 再从 long 转换到double |
unsigned long |
char |
保留低位字节 |
unsigned long |
short |
保留低位字节 |
unsigned long |
long |
最高位作为符号位 |
unsigned long |
unsigned char |
保留低位字节 |
unsigned long |
unsigned short |
保留低位字节 |
unsigned long |
float |
转换到long; 再从 long 转换到float |
unsigned long |
double |
Convert directly to double |
unsigned long |
long double |
转换到long; 再从 long 转换到double |
---------------------------------------------------------
符号扩展,零扩展,以及缩减
数现代高级程序设计语言允许程序员使用包含不同大小的整数对象的表达式。那么,当一个表达式的两个操作数大小不同的时候,会发生什么呢?有些语言会报错,而其他的语言则会自动将操作数转换成一个统一的格式。这种转换是有代价的,因此,如果你不希望编译器在你不知情的情况下自动加入各种转换到你原本非常完美的代码中,就需要掌握编译器是如何处理这些表达式的。
进制补码系统中,同一个负数在不同大小的表示法中的表示是不同的。你不能在一个包含16位数的表达式中随意地使用8位有符号数,转换是必需的。这种转换,以及其逆操作(将16位数转换为8位)就是符号扩展(sign extension)与缩减(contraction)操作。
-64为例,其8位的二进制补码表示是$C0,而等效的16位二进制补码表示则是$FFC0。很显然,其位模式不一样。再看看数+64,其8位和16位表示分别是$40与$0040。一个很显然的事实就是,扩展负数的大小与扩展非负数的大小是完全不同的。
个数从某个位数符号扩展到一个更大的位数很简单,只需要将符号位复制到新格式新增的高端各位即可,例如,为了将一个8位的数符号扩展到16位,只需将8位数的第7位复制到16位数的第8 .. 15位即可。而将一个16位数符号扩展到一个双字,只需要将第15位复制到双字的第16 .. 31位即可。
理不同长度有符号数的时候,必须使用符号扩展。例如,在将一个字节量与一个字量相加的时候,在相加之前必须将字节量符号扩展到16位。其他运算可能又会需要符号扩展到32位。
表2-5 符号扩展举例
8位 |
16位 |
32位 |
二进制补码表示 |
$80 |
$FF80 |
$FFFF_FF80 |
11_1111_1111_1111_1111_1111_1000_0000 |
$28 |
$0028 |
$0000_0028 |
00_0000_0000_0000_0000_0000_0010_1000 |
$9A |
$FF9A |
$FFFF_FF9A |
11_1111_1111_1111_1111_1111_1001_1010 |
$7F |
$007F |
$0000_007F |
00_0000_0000_0000_0000_0000_0111_1111 |
n/a |
$1020 |
$0000_1020 |
00_0000_0000_0000_0001_0000_0010_0000 |
n/a |
$8086 |
$FFFF_8086 |
11_1111_1111_1111_1000_0000_1000_0110 |
处理无符号二进制数的时候,可以使用零扩展(zero extension)来将小位数的无符号数扩展到大位数的无符号数。零扩展非常简单——只需要用零来填充大位数操作数的高端各个字节即可。例如,为了将8位数$82零扩展到16位,只需要在高端字节中插入零,即得到$0082。
表2-6 零扩展举例
8位 |
16位 |
32位 |
二进制补码表示 |
$80 |
$0080 |
$0000_0080 |
00_0000_0000_0000_0000_0000_1000_0000 |
$28 |
$0028 |
$0000_0028 |
00_0000_0000_0000_0000_0000_0010_1000 |
$9A |
$009A |
$0000_009A |
00_0000_0000_0000_0000_0000_1001_1010 |
$7F |
$007F |
$0000_007F |
00_0000_0000_0000_0000_0000_0111_1111 |
n/a |
$1020 |
$0000_1020 |
00_0000_0000_0000_0001_0000_0010_0000 |
n/a |
$8086 |
$0000_8086 |
00_0000_0000_0000_1000_0000_1000_0110 |
大多数高级语言编译器会自动处理符号扩展与零扩展,以下C语言的例子说明了它们是如何工作的:
signed char sbyte; // C语言中的字符类型是一个字节
short int sword; // C语言中的短整型一般是16位
long int sdword; // C语言中的长整型一般是32位
. . .
sword = sbyte; //自动将8位值符号扩展到16位
sdword = sbyte; //自动将8位值符号扩展到32位
sdword = sword; //自动将16位值符号扩展到32位
语言(例如Ada)在从小数据类型转换到大数据类型时需要显式转换(explicit cast)。查一下所用语言的参考手册就知道这种显式转换是不是必需的了。要求提供显式转换的语言的优点在于编译器永远不会在程序员不知情的情况下做任何事情。如果你没有提供必要的转换,编译器会给出一个诊断消息,让你知道程序还需要改进。
符号扩展和零扩展,有一点需要明确的是,它们是需要付出代价的。将一个小整型赋值给一个大整型可能会比在同样大小的整型变量间传输数据需要更多的机器指令(执行时间更长)。因此,在一个数学表达式或者一条赋值语句中混合使用不同大小的变量要小心。
符号缩减,即将一个某位数转换为值相同但位数变小的数,比较麻烦。符号扩展永远不会失败,使用符号扩展,一个m位有符号数永远可以转换为一个n位数(这里n>m)。不幸的是,在m的情况下,一个n位数不是总能转换为m位数。例如,-448的16位十六进制表示是$FE40,而这个数的大小对于8位来说太大了,我们无法将其符号缩减到8位。
将一个数值正确地符号缩减,必须要检查需要丢弃的高端字节。首先,这些高端字节必须是全零或者$FF,如果它们包含其他值,我们就无法对这个数进行符号缩减。其次,最终结果的最高位必须与被丢弃的所有位一致。以下就是一些从16位数转换到8位数的例子:
$FF80 (11_1111_1000_0000) 可以被符号缩减为 $80 (00_0000).
$0040 (00_0000_0100_0000) 可以被符号缩减为 $40 (00_0000).
$FE40 (11_1110_0100_0000) 不能被符号缩减为8 位
$0100 (00_0001_0000_0000) 不能被符号缩减为8 位
级语言里使用缩减有点困难,有些语言,譬如说C语言,会直接将表达式的低端部分存储到比较小的变量中,并将高端部分丢弃(在最好的情况下,C编译器可能会在编译过程中给出一个警告,提示可能会出现的精度损失)。你可以采取措施来让编译器停止抱怨,但是它仍然不会检查数值的有效性。以下是C语言中符号缩减的典型代码:
signed char sbyte; // C语言中的字符类型是一个字节
short int sword; // C语言中的短整型一般是16位
long int sdword; // C语言中的长整型一般是32位
. . .
sbyte = (signed char) sword;
sbyte = (signed char) sdword;
sword = (short int) sdword;
语言中,唯一安全的解决方案就是在将表达式的结果值存储到一个小变量中之前,将该结果值与某个上下边界值进行比较。不幸的是,如果需要经常做这种操作,代码会变得比较笨拙。以下就是加上这些检查之后的转换代码:
if( sword >= 128 && sword <= 127 )
{
sbyte = (signed char) sword;
}
else
{
// 报告错误
}
// 另一种方案,使用断言:
assert( sword >= 128 && sword <= 127 )
sbyte = (signed char) sword;
assert( sdword >= 32768 && sdword <= 32767 )
sword = (short int) sdword;
易见,这让代码变得丑陋。在C/C++中,你可能会倾向于将它们编写为宏(#define)或者函数,以提高代码的可读性。
有些高级语言(例如Pascal和Delphi/Kylix)会自动进行符号缩减,还会检查结果来确保它适用于目标操作4。这些语言在越界违例发生的时候会产生某种类型的异常(或者停止程序的运行)。当然了,如果你想加入纠错代码,要么就需要写点异常处理代码,要么就使用前面C语言例子中使用的if语句序列。
引用:
http://testing.etao.com/experience_list/66
http://apps.hi.baidu.com/share/detail/40431986
http://blog.sina.com.cn/s/blog_6adcb3530101cmsd.html