1,整数数据在内存中存储
在C语言中,整型类型(如int
、short
等)通常采用补码来表示有符号整数。这使得程序员能够使用标准的运算符(如+
、-
、*
等)执行整数运算,而无需考虑底层的位表示。
1.1 源码,反码,补码
这三种表示方法均有符号位和数值位两部分组成且对于正整数而言,原反补码均相同
反码与补码的运算规则:以int类型举例最高位是0代表该数字为正数,最高位是1代表该数字为负数;反码规则为符号位不变,其他位按位取反;补码是反码加一得到补码
数据在内存中存储时,均存储补码,以int类型举例
1的原码,反码,补码均为:0000 0000 0000 0000 0000 0000 0000 0001
-1原码:1000 0000 0000 0000 0000 0000 0000 0001
-1反码:1111 1111 1111 1111 1111 1111 1111 1110
-1补码=反码+1:1111 1111 1111 1111 1111 1111 1111 1111
则1+(-1)=1 0000 0000 0000 0000 0000 0000 0000 0000
此时发现数字变为33位,而int类型只有4个字节,32比特位,系统从低位向高位截取32个比特位,所得结果为32个0即为0;结果与事实相符
从上面我们得出补码的优点
-
相同的加法运算:使用补码,正数和负数的加法可以使用相同的加法器来实现。无需单独的减法器,从而简化了电路设计。
-
无符号表示兼容:补码同时支持有符号和无符号数的运算,因为在计算时负数能被转换为正数,简化了计算。
-
唯一性:补码对每个整数有唯一的表示,特别是负数,没有双重表示,这避免了某些额外的复杂性。例如,原码中0可以表示为正0和负0,而补码只用一个0表示。
对于补码的运算
1.加法:两个补码相加时,如果结果的最高位(溢出位)丢失,结果就是正确的补码表示。
2.减法:减法可以转化为加法。减去一个数等同于加上该数的补码。
3.乘法和除法:虽然这些操作比较复杂,现代计算机通常使用特定的硬件操作来处理乘法和除法,但原理上依然基于补码的表示。
1.2 整型有无符号的区别
对整型家族而言,存在有无符号的区别,其内在逻辑在于有符号整型的最高位为符号位,而无符号整型全为数值位,故两者表示范围也不同,以char类型和unsigned char类型为例子:
char(有符号)的表示范围以十进制表示为:-128--127(下面展示部分数字的补码表示)
1:0000 0001; 2:0000 0010 ... 127 :0111 1111; -1:1111 1111;-2:1111 1110 ...-127:1000 0001 ;-128:1000 0000
unsigned char表示范围以十进制表示为:0--256
2,浮点数在内存中存储
在C语言中,浮点数是以特定的格式存储的,通常遵循IEEE 754标准。这个标准定义了浮点数的表示方式,包括单精度(float)和双精度(double)等类型,浮点数表示范围在头文件<float.h>中
2.1浮点数的结构
浮点数由三部分组成
- 符号位 S(sign bit):1位,用于表示数值的正负。
- 指数部分 E(exponent):用于表示浮点数的大小。浮点数可以通过调整指数部分来表示非常大的或非常小的数。
- 尾数 M(mantissa or significand):也称为有效数字,表示实际的数值。
-
表示浮点数的公式
浮点数的表示可以通过以下公式计算其数值
对于双精度,公式中的偏移量会是1023,单精度的偏移量如图为127
2.2偏移量
- 偏移量是一种常数,用来调整指数的范围。它可以让我们在存储时只使用非负数。
- E为无符号整型。
-
在单精度浮点数中:
- 使用的偏移量是 127。也就是说,如果实际的指数是 -1,存储时会将其加上 127,存储为 126。
-
在双精度浮点数中:
- 使用的偏移量是 1023。同样的道理,实际的 -1 指数存储为 1022。
如果一个浮点数的实际指数是 -2:
- 在单精度中,存储的值 = -2 + 127 = 125
- 在双精度中,存储的值 = -2 + 1023 = 1021
2.3 浮点数存储格式
以单精度浮点数(flaot)为例,存储格式如下
-
符号位 (
S
):0 表示正数,1 表示负数。 -
指数部分 (
E
):采用偏移量表示法(bias)。对于单精度,偏移量为127,因此实际存储的指数需要加上127。 -
存储的指数 = 真实指数 + 127。
-
尾数部分 (
M
):- 尾数以1.XXXXXX的形式存储,即只有在数值为正常化时,将1隐含在尾数前。尾数的实际存储是一个隐含的二进制“1”后跟23位有效数字。例如,如果尾数位的二进制是
10000000000000000000000
,则实际的尾数为1.10000000000000000000000
(隐含的“1”加上尾数的内容)。
- 尾数以1.XXXXXX的形式存储,即只有在数值为正常化时,将1隐含在尾数前。尾数的实际存储是一个隐含的二进制“1”后跟23位有效数字。例如,如果尾数位的二进制是
我的个人理解为:M在存储中先将十进制数字转化为二进制数字,并以上面公式给出的方式表示出来,M部分只存小数点后面的部分,从左开始表示小数点后的数,表示完成后不够的位数向右补0,直到位数满足要求(在float类型中为23bit位)
例如十进制数5.5的二进制表示为101.1,即1.011*2的2次方 用公式表示时:S=0,E=2+127=129,M=011
即将其二进制存储为0(S) 1000 0001(E) 0110 0000 0000 0000 0000 000(M)
规范写为0100 0000 1011 0000 0000 0000 0000 0000
表示-16.75: 16.75的二进制表示为1000.11 即1.00011*2的3次方
S=1,E=3+127=130,M=00011
二进制表示为1 (S)1000 0010(E) 0001 1000 0000 0000 0000 000(M)
规范写为1100 0001 0000 1100 0000 0000 0000 0000
2.4精度与范围
-
精度:
- 单精度浮点数(float)有大约6-7位数字的精确度。
- 双精度浮点数(double)有大约15-16位数字的精确度。
-
范围:
- 单精度浮点数(float):约 3.4×10−383.4×10−38 到 3.4×10383.4×1038。
- 双精度浮点数(double):约 1.7×10−3081.7×10−308 到 1.7×103081.7×10308。
注意,二进制小数点后第一位为2的-1次方,第二位为2的-2次方.....
且许多十进制小数无法用二进制精确表示,这个时候就需要大家,“努力”表示接近的数字了
2.5 浮点数的特殊值
根据IEEE 754标准,浮点数还支持特殊的值。这些特殊值包括:
- 0:正零和负零(符号位不同)。
- 无穷大(Infinity):当数值超出浮点数可表示的范围时,结果为无穷大。表现为指数为全1,尾数为全0。
- NaN(Not a Number):表示未定义的数值操作,例如 0/0 的结果。表现为指数为全1,尾数非零。
2.6浮点数运算
C语言中的浮点数运算遵循标准的算术规则,支持常见运算符:
+
(加法)-
(减法)*
(乘法)/
(除法)
2.7注意事项
-
精度问题:由于浮点数的有限精度,某些计算结果可能不是精确的数字。例如,0.1在浮点数表示中可能无法精确表示,导致比较时不等式成立。
-
舍入误差:浮点数运算可能引入舍入错误。程序员需要注意对浮点数的比较,尽量避免直接将两个浮点数进行相等比较。
-
选择合适的类型:对于需要高精度计算的应用,建议使用双精度(double),以减少因精度不足带来的误差。
示例代码
#include <stdio.h>
int main() {
float a = 0.1f; // 单精度
double b = 0.1; // 双精度
printf("单精度浮点数: %.20f\n", a); // 输出 20 位小数
printf("双精度浮点数: %.20f\n", b); // 输出 20 位小数
// 测试浮点数比较
if (a != b) {
printf("a和b不同\n");
}
return 0;
}
可以复制代码验证,a与b的结果确实不同。
综上所述,整型和浮点型的存储方式不同,需要牢记两者区别,关键掌握如何计算二进制浮点数
int main()
{
int n = 9;
float* b = (float*)&n;
printf("n的值为:%d\n", n);
printf("b的值为:%f\n", *b);
*b = 9.0;
printf("n的值为:%d \n", n);
printf("*b值为:%.10f\n", *b);
return 0;
}
运行结果如图,第三行n的值并非空穴来风,用浮点数表示9.0并将其二进制序列以整型打印出来,就是这个结果
为了验证今日所学,我用了这个代码(非自己写),这个代码利用指针改变这块空间存储的数据类型实现了该功能,第一遍看没觉得巧妙,当自己去想办法验证时只
能得到这样的结果
或者这样。不知道n的整数值为什么两次不一样,为什么不全为0,且如果n为0.1,结果也为-1610612736。如果有佬看到这篇文章,还请教教我这个小萌新,一起学技术,共同进步。
我的第一篇博客,欢迎大家给出意见,不玻璃心,只求进步