计算机基础

整数的表示

2.2.1 整型数据类型

32位机器上C语言的整型数据类型的取值范围:

C数据类型最小值最大值
char-128127
unsigned char0255
short-3276832767
unsigned short065535
int-21474836482147483647
unsigned int04294967295
long-21474836482147483647
unsigned long04294967295
long long92233720368547758089223372036854775807
unsigned long long018446744073709551615

64位机器上C语言的整型数据类型的取值范围:

C数据类型最小值最大值
char-128127
unsigned char0255
short-3276832767
unsigned short065535
int-21474836482147483647
unsigned int04294967295
long-92233720368547758089223372036854775807
unsigned long018446744073709551615
long long92233720368547758089223372036854775807
unsigned long long018446744073709551615

C语言标准要求的整型数据类型的取值范围:

C数据类型最小值最大值
char-127127
unsigned char0255
short-3276732767
unsigned short065535
int-3276732767
unsigned int065535
long-21474836472147483647
unsigned long04294967295
long long92233720368547758079223372036854775807
unsigned long long018446744073709551615

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;
}

关于作者

程序员,软件工程师,java, golang, rust, c, python,vue, Springboot, mybatis, mysql,elasticsearch, docker, maven, gcc, linux, ubuntu, centos, axum,llm, paddlepaddle, onlyoffice,minio,银河麒麟,中科方德,rpm