本周知识要点:
2.1. C语言中对整数的存储机制了解
2.2. 进制转换(二进制转十进制,十进制转二进制)
2.3. 应用C语言中存储机制 做一个bit算法,这对某些计算将会特别快
1、整数的一些基础知识要点:(参考C语言中整数的存储形式)
C语言中整形按大小可以细分int、short、long,又可以按符号分有符号和无符号等,又因C标准并未明确规定不同数据类型的标准大小,而交由编译器规定。故在不同系统、不同编译器下大小不同,从存储来说,最大的不同即在于有无正负号,为了描述方便,便于理解,我们统一按照最常见的四字节为例(2字节和8字节也通用),按有符号整数和无符号整数
1.1 无符号整数(unsigned int)
储时,它们(unsigned int)的全部位数(比如int四个字节,将占8*4=32个bit位)都用来表示数据,即00000000 00000000 00000000 00000000 ~ 11111111 11111111 11111111 11111111 之间,而在内存中,通常用8个十六进制数表示,即四个二进制数一组,十六进制表示为0x00000000 ~ 0xFFFFFFFF。最大表示范围十进制为0~4294967295。
1.2 有符号整数(int)
有符号数与无符号数的区别在于,最高一位也就是左边第一位是用来表示符号的,而右边31位表示数据。
左边第一位用1表示负数,用0表示正数
右边31位数据即为0000000 00000000 00000000 00000000 ~ 1111111 11111111 11111111 11111111
对应十六进制数为0x00000000 ~ 0xFFFFFFFF ,其中正数部分为:0x00000000 ~ 0x7FFFFFFF,负数部分为:0x80000000 ~0xFFFFFFFF,其中0x00000000为0,0x80000000为最小负数。
对应十进制数则为-2147483648 ~ 2147483647
1.3 十进制在计算机二进制存储规则(参考 int类型在内存中的存储方式)
① 占用的比特位数量
在32位操作系统下,两者都是占用4个字节,每个字节有8个比特位,因此有32个0-1的二进制位数。两者的不同在于,int类型有正负号(±)的存在,需要比unsigned int类型多消耗一个位数。
② 符号的表示方法
在所有被int类型占用的比特位中,左起第一个位就是符号位。int类型的符号位上,0表示正数,1表示负数。在32位操作系统下,其余后面31位是数值位。
③ 数字0的表示方法
按照上面提到的符号,我们有了两种0的表示方法,即“+0”和“-0”。
实际上,在32位系统下int类型中,我们计算机已经强行规定了这种情况,数字0采用“+0”的表示方法,即00000000 00000000 00000000;而“-0”这个特殊的数字被定义为了-2^31。
因此我们看到32位系统下int类型的取值范围中,负数部分比正数部分多了一个数字,正数的最大取值是2^31-1,而负数的最小取值是-2^31。正数部分之所以要减去1,是因为被数字0占用了,而负数部分不需要用来表示0,因此原本的“-0”就用来表示-2^31这个数字。
(所以 这个能很好的解决+0 和-0 的区别,而只唯一剩下一个0)
而且,还有一个规定,当十进制对应的二进制全是1时, 即11111111 11111111 11111111 (最大的负整数,实际是有符号的-1)这时候再+1就会变成000000000 00000000 00000000(最小的非负整数),而01111111 11111111 11111111(最大的非负整数)+1,就会变成10000000 00000000 00000000(最小的负整数)这样就会形成一个“整数圈”,使得任意的加减法都会在这个圈子里运算。
例子:-1+1 = 0,以及,2147483647+1 = -2147483648。
为什么会出现这种情况,原因主要还是便于人类理解加减法,更加详细的请看下面。
1.4 原码、反码和补码的概念
① 原码的概念
原码:是计算机中一种对数字的二进制定点表示方法。原码表示法在数值前面前面有一位符号位(即最高位为符号位),正数该位为0,负数该位为1(0有两种表示:+0和-0),其余位表示数值的大小。
实际上:(以八位为例)
十进制的 1 对应二进制就是 00000001
十进制的 -1 是否就是对应二进制的 10000001呢?答案是否定的,因为10000001 如果+1 就变成10000010,就变成-2了,不符合人类对加减法的认知,所以不应该这样编码。
② 反码的概念
反码表示法的规定:“正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。”
即:
对于正数和“+0”而言,其原码本身就是反码,例如 8位二进制“+1”,其原码与反码都是00000001;
对于负数和“-0”而言,符号位与原码中一样,保持不变,其余位数逐位取反,1换成0,0换成1,例如 “-1”,其8位二进制原码是1000 0001,其反码是1111 1110;
但是 这里仍然有一个问题,就是-0 和+0 是需要分别存储的,但实际上没有意义,还要浪费空间来存储,所以反码逐渐被人类抛弃直接使用,而是改进成补码。
③ 补码的概念
补码正是基于反码的“-0”问题诞生的,可以解决这个问题。
补码的计算方法是:
正数和+0的补码是其本身,
负数则先计算其反码,然后反码加上1,得到补码。(这里还是有反码的概念在里面)
补码换算为原码的过程中,如果补码是正数或者+0的补码,则其原码就是补码本身;如果补码是负数或者-0的补码,则其原码的计算方法是,先将补码减掉1,得到反码,再将反码取反,得到原码。
由于补码1000 0000具有特殊性,计算机在编写底层算法时,将其规定为该取值范围中的最小数-128,其值与(-1)+(-127)的计算结果正好符合。
补充一点,8位二进制补码1000 0000没有对应的反码和原码,其他位数的二进制补码与此类似。
综上所述,在C语言中整数的计算实际上 是对应二进制的补码在计算,还有诸多规则。
2、关于进制转换的一些概念
通常是十六进制(Hex).、十进制(Dec)、八进制(Oct)和二进制(Bin),其中十六进制 = 4位二进制展开成1位进制,八进制 = 3位二进制展开成1位八进制。所以接下来仅讨论二进制 和 十进制的关系即可。
2.1 二进制数转十进制数:
二进制的一个整数或小数转十进制,可以通过在二进制中对 该数各位按十进制带权相加得到。
如:
2.2 十进制整数转二进制:
十进制的一个整数转二进制,可以通过在十进制中对这个整数除以2取余数得到。如4 = 1*4 + 0*2 + 0*1 = 100
2.3 十进制分数\小数 转二进制分数(一般都会变成分数)
十进制的分数转二进制,可以在十进制中做除法得到小数,再转二进制;或把分子分母转二进制,在二进制中做除法
3、 C语言的bit算法代码展示
包括有 ①取符号函数 、②取二进制最高位的1、③取二进制最低位的1、④二进制数前有多少个0、⑤二进制数有多少个1、⑥数字顺序颠倒
#include<stdio.h>
int sign(int i)
{
// 补充说明,在C语言中如果是有符号整型 位运算右移 会保留符号
// 如果是 无符号整形,则不会保留符号
// 所以如果要整体移动进制 则需要用到无符号形式
// -1代表负数,1代表正数
unsigned a = -i;
return (i>>31)|(a>>31);
}
int reserve_highest_1(int i)
{
unsigned a = i;
a |= (a >> 1);
a |= (a >> 2);
a |= (a >> 4);
a |= (a >> 8);
a |= (a >> 16);
a -= (a >> 1);
return a;
}
int reserve_lowest_1(int i)
{
return i&-i;
}
int numbenr_of_leading_0(int i)
{
int n = 0;
unsigned a = i;
if (a >> 16 == 0) { n += 16; a <<= 16; }
if (a >> 24 == 0) { n += 8; a <<= 8; }
if (a >> 28 == 0) { n += 4; a <<= 4; }
if (a >> 30 == 0) { n += 2; a <<= 2; }
if (a >> 31 == 0) { n += 1; a <<= 1; }
return n;
}
int number_of_1(int i)
{
unsigned a = i;
a = ((a&0xAAAAAAAA) >> 1) + (a&0x55555555);
a = ((a&0xCCCCCCCC) >> 2) + (a&0x33333333);
a = ((a&0xF0F0F0F0) >> 4) + (a&0x0F0F0F0F);
a = ((a&0xFF00FF00) >> 8) + (a&0x00FF00FF);
a = ((a&0xFFFF0000) >> 16) + (a&0x0000FFFF);
return a;
}
int reverse_order(int a)
{
unsigned i = a;
i = ((i&0xAAAAAAAA)>>1) | ((i&0x55555555)<<1);
i = ((i&0xCCCCCCCC)>>2) | ((i&0x33333333)<<2);
i = ((i&0xF0F0F0F0)>>4) | ((i&0x0F0F0F0F)<<4);
i = ((i&0xFF00FF00)>>8) | ((i&0x00FF00FF)<<8);
i = ((i&0xFFFF0000)>>16) | ((i&0x0000FFFF)<<16);
return i;
}
int main()
{
}