整数的表示
2.2.1 整型数据类型
32位机器上C语言的整型数据类型的取值范围:
C数据类型 | 最小值 | 最大值 |
---|---|---|
char | -128 | 127 |
unsigned char | 0 | 255 |
short | -32768 | 32767 |
unsigned short | 0 | 65535 |
int | -2147483648 | 2147483647 |
unsigned int | 0 | 4294967295 |
long | -2147483648 | 2147483647 |
unsigned long | 0 | 4294967295 |
long long | 9223372036854775808 | 9223372036854775807 |
unsigned long long | 0 | 18446744073709551615 |
64位机器上C语言的整型数据类型的取值范围:
C数据类型 | 最小值 | 最大值 |
---|---|---|
char | -128 | 127 |
unsigned char | 0 | 255 |
short | -32768 | 32767 |
unsigned short | 0 | 65535 |
int | -2147483648 | 2147483647 |
unsigned int | 0 | 4294967295 |
long | -9223372036854775808 | 9223372036854775807 |
unsigned long | 0 | 18446744073709551615 |
long long | 9223372036854775808 | 9223372036854775807 |
unsigned long long | 0 | 18446744073709551615 |
C语言标准要求的整型数据类型的取值范围:
C数据类型 | 最小值 | 最大值 |
---|---|---|
char | -127 | 127 |
unsigned char | 0 | 255 |
short | -32767 | 32767 |
unsigned short | 0 | 65535 |
int | -32767 | 32767 |
unsigned int | 0 | 65535 |
long | -2147483647 | 2147483647 |
unsigned long | 0 | 4294967295 |
long long | 9223372036854775807 | 9223372036854775807 |
unsigned long long | 0 | 18446744073709551615 |
C语言和C++都支持有符号(默认)和无符号数,java只支持有符号数。
2.2.2 无符号数的编码
无符号数的二进制表示有一个很重要的属性,就是每个介于0-2^n-1之间的数都有位移一个n位的值编码。 例如:十进制的11作为无符号数,只有一个4位的表示,就是:[1011]。对于每个长度为w的位向量,都有 一个唯一的值与之对应;反过来,0-2^w-1之间的每个整数都有一个唯一的长度为w的位向量二进制表示与之对应。
[1111] = 1*2^3 + 1*2^2 + 1*2^1 + 1*2^0 = 15
2.2.3 补码编码
对于许多应用,我们还希望表示负数。最常见的有符号数的计算机表示就是补码。
最高有效位称为符号位,它的权重为-2^(w-1),是无符号表示中权重的负数。 符号位被设置为1时,表示值为负数,而当设置为0时,值为非负数。
[0001] = -0*2^3 + 0*2^2 + 0*2^1 + 1*2^0 = 1;
[0101] = -0*2^3 + 1*2^2 + 0*2^1 + 1*2^0 = 5;
[1011] = -1*2^3 + 0*2^2 + 1*2^1 + 1*2^0 = -5;
[1111] = -1*2^3 + 1*2^2 + 1*2^1 + 1*2^0 = -1;
取值范围: 最小值[10000…..] = -2^(w-1) 最大值[011111….] = 2^(w-1) - 1
有符号数的其他表示方法:
-
反码: 除了最高有效位的权是-2^(w-1) -1 而不是-2^(w-1),它和补码是一样的。
-
原码:最高有效位是符号位,用来确定剩下的应该区正权还是负权。
这两种表示方法都有一个奇怪的属性,那就是0有两种不同的编码方式。 原码中[000…0]解释为+0,[1000…0]解释为-0; 反码中[000…0]解释为+0,[1111…1]解释为-0;
正数的原码,补码,反码都相同,都等于它本身; 负数的补码是:符号位为1,其余各位求反,末位加1; 反码是:符号位为1,其余各位求反,但末位不加1。
2.2.4 有符号数和无符号数之间的转换
/**
*
*字节序列的二进制表示(小端序操作系统)
*@start 第一个字节的地址
*@len 字节序列的长度
*/
void show_small_endpoint_bin(char * start, int len){
char c = (char)0;
unsigned int n = 0x80;
char * p = start;
for(int i = len-1; i >= 0; i--){ // 如 int 占4个字节,因为是小端序,所以倒过来输出
c = p[i];
n = 0x80; // 1000 0000 = 0x80;
for(int j = 0; j < 8; j++){ // 1个字节8个位
if (c & n){
printf("1");
}else {
printf("0");
}
if(j == 3){
printf("-");
}
n >>= 1;
}
printf(" ");
}
printf("\n");
}
int main(){
short v = -12345; // 二进制位:1100-1111 1100-0111
show_small_endpoint_bin((char*)&v, sizeof(short));
unsigned short uv = (unsigned short)v;
printf("%d, %u\n", v, uv); //-12345, 53191=1*2^15 + 1*2^14 + 1*2^11+1*2^10+2^9+2^8 +2^7+2^6+2^2+2^1+1
return 0;
}
对于大多数C语言的实现而言,处理同样字长的有符号数和无符号数之间的相互转换的一般规则是: 数值可能会变,但位模式不会变,只是改变了解释这些位的方式。
2.2.5 C语言中的有符号数与无符号数
C语言支持所有整型数据类型的有符号和无符号运算。尽管C语言标准没有指定有符号数要采用某种表示, 但几乎所有的机器都使用补码。通常,大多数数字都认为是有符号的,要创建一个无符号常量,必须加上 后缀字符‘U’或者‘u’。例如:12345U或者 0x1A2Bu。
C语言允许无符号数和有符号数之间的转换。 显示转换:
int tx, ty;
unsigned ux, uy;
tx = (int)ux;
uy = (unsigned)ty;
隐式转换:
int tx, ty;
unsigned ux, uy;
tx = ux;
uy = ty;
用printf输出时,%d, %u, %x 表示有符号十进制,无符号十进制,十六进制。
当执行一个运算时,如果它的一个运算数是有符号的而另一个是无符号的,那么C语言会隐式地 将有符号参数强制类型转换成无符号数,并假设这两个数都是非负的,来执行这个运算。
2.2.6 拓展一个数字的位表示
将一个无符号数转换为一个更大的数据类型,我们只需要简单地在表示的开头添加0,这种运算称为零拓展。
将一个补码数字抓换为一个更大的数据类型可以执行符号拓展,规则是在表示中添加最高有效位的值副本。
#include <stdio.h>
/**
*
*字节序列的二进制表示(小端序操作系统)
*@start 第一个字节的地址
*@len 字节序列的长度
*/
void show_small_endpoint_bin(char * start, int len){
char c = (char)0;
unsigned int n = 0x80;
char * p = start;
for(int i = len-1; i >= 0; i--){ // 如 int 占4个字节,因为是小端序,所以倒过来输出
c = p[i];
n = 0x80; // 1000 0000 = 0x80;
for(int j = 0; j < 8; j++){ // 1个字节8个位
if (c & n){
printf("1");
}else {
printf("0");
}
if(j == 3){
printf("-");
}
n >>= 1;
}
printf(" ");
}
printf("\n");
}
int main(){
short v = -12345; // 二进制位: 1100-1111 1100-0111
show_small_endpoint_bin((char*)&v, sizeof(short));
int iv = (int)v; // 二进制位:1111-1111 1111-1111 1100-1111 1100-0111
show_small_endpoint_bin((char*)&iv, sizeof(int));
printf("%d, %d\n", v, iv);
return 0;
}
2.2.7 截断数字
#include <stdio.h>
/**
*
*字节序列的二进制表示(小端序操作系统)
*@start 第一个字节的地址
*@len 字节序列的长度
*/
void show_small_endpoint_bin(char * start, int len){
char c = (char)0;
unsigned int n = 0x80;
char * p = start;
for(int i = len-1; i >= 0; i--){ // 如 int 占4个字节,因为是小端序,所以倒过来输出
c = p[i];
n = 0x80; // 1000 0000 = 0x80;
for(int j = 0; j < 8; j++){ // 1个字节8个位
if (c & n){
printf("1");
}else {
printf("0");
}
if(j == 3){
printf("-");
}
n >>= 1;
}
printf(" ");
}
printf("\n");
}
int main(){
int i = 12345678; // 0000-0000 1011-1100 0110-0001 0100-1110
show_small_endpoint_bin((char*)&i, sizeof(int));
short s = (short)i; // 0110-0001 0100-1110
show_small_endpoint_bin((char*)&s, sizeof(short));
printf("%d, %d\n", i, s); //12345678, 24910
i = -34535; // 1111-1111 1111-1111 0111-1001 0001-1001
show_small_endpoint_bin((char*)&i, sizeof(int));
s = (short)i; // 0111-1001 0001-1001
show_small_endpoint_bin((char*)&s, sizeof(short));
printf("%d, %d\n", i, s); // -34535, 31001
return 0;
}