江小南

V1

2022/12/31阅读:13主题:萌绿

【408篇】C语言笔记-第二十章(数据的机器级表示)

第一节:补码讲解及内存实战演练

1. 补码讲解

计算机的CPU无法做减法操作,只能做加法操作。CPU中有一个逻辑单元叫加法器。

计算机所做的减法,都是通过加法器将其变化成加法实现的

小端存储:X86架构都是使用的小端存储。小端存储是低字节在前,即低字节在低地址。高字节在后,即高字节在高地址,fb对于0xffffffffb是低字节,因此fb在内存中最前面。大端和小端相反。

负数在内存中以补码的形式存储。

问题:如何实现2-5?

实现2-5的方法是2+(-5),由于计算机只能存储0和1,5的二进制数为101,称为原码。计算机用补码表示-5,补码是对原码取反后加1的结果。

如上图:-5在内存中存储为0xfffffffb,对其加2后得0xfffffffd它就是k的值。当最高位为1时表示负数。我们在通过取反加1得到原码,也就是3,由于是负数,所以就是-3。

说明:补码得原码的时候先取反再加1先减1再取反效果一样。

注意:通过8位表示,-5的补码为1111 1011,-5的原码为10000 0101,符号位不动的,只有值的部分是5。

2. 反码

反码是一种在计算机中数的机器码表示。对于单个数值(二进制的0和1)而言,对其进行取反操作就是将0变成1,1变成0,正数的反码和原码一样,负数的反码就是在原码的基础上符号位保持不变,其他位取反。

第二节:整型不同类型解析-溢出解析

1. 整型不同类型解析

整型变量包括6中类型。有符号与无符号整型的最高位代表的意义不同。

不同整型变量表示的整型范围如表所示,超出范围会发生溢出现象,导致计算出错。

2. 溢出解析

有符号短整型可以表示的最大值为32767,当我们对其加1时,b的值会变成多少呢?实际运行打印得到的是-32768,为什么会这样呢?因为32767对应的十六进制数为0x7fff,加1后变为0x8000,其首位为1,因此变成了一个负数,去这个负数的原码后,就是其本身,值为32768,所以0x8000是最小的负数,即-32768,因此导致计算结果出错。

#include <stdio.h>

int main() {
    int i=10;
    short a=32767;
    short b=0;
    long c;
    b=a+1// 发生了溢出,解决溢出的办法是用更大的空间来存
    printf("b=%d\n",b);  // b并不是32767
    printf("---------------\n");
    unsigned int m=3;
    unsigned short n=0x8056// 无符号类型,最高位不认为是符号位
    unsigned long k=5;
    b=0x8056;
    printf("b=%d\n",b); // b是有符号类型,所以输出是负值
    printf("n=%u\n",n); // 无符号类型要用%u,用%d是不规范的
    return 0;
}
F:\Computer\Project\practice\20\20.4-overflow\cmake-build-debug\20_4_overflow.exe
b=-32768
---------------
b=-32682
n=32854

进程已结束,退出代码为 0

第三节:IEEE754标准解析

float型变量占用的内存空间为4字节,double型变量占用的内存空间为8字节。

与整型数据的存储方式不同,浮点型数据是按照指数形式存储的。系统把一个浮点型数据分成小数部分(用M表示)和指数部分(用E表示)并分别存放,指数部分采用规范化的指数形式,指数也分为正、负(符号位,用S表示)。

数符(即符号位)占1位,是0时代表正数,是1时代表负数。

4.5的IEEE-754浮点型变量存储标准

格式 SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM
二进制 0100 0000 1001 0000 0000 0000 0000 0000
十六进制 40 90 00 00

S:S是符号位,用来表示正、负,是1表示负数,是0表示正数。

E:E代表指数部分(指数部分的值规定只能是1到254,不能全是0或全是1),指数部分运算前都要减去127,因为还要表示负指数。这里的10000001转换为十进制数为129,129-127=2,即实际指数部分为2.

M:M代表小数部分,这里为0010 0000 0000 0000 0000 000,底数左边省略存储了一个1,使用的实际底数表示为1.00100000000000000000000。

上面表1可以变为如下表格:

S(符号位) E(阶码) M(尾数)
0 1000 0001 0010 0000 0000 0000 0000 000

可以看到,4.5的内存是0x40900000。

首先看f的小数部分,也就是表中M(灰色)的部分。这里为0010 0000 0000 0000 0000 000.总计23位。由于底数左边省略存储了一个1,所以实际底数部分表示为1.00100000000000000000000。

在看指数部分,计算机并不能直接计算10的幂次,f的指数部分为1000 0001,其十进制值为129,129-127=2,即实际指数部分为2,指数值为2,代表2的2次幂。因此将1.001左移2位即可,也就是100.1,然后转换成十进制数,整数部分为4,小数部分为2的-1次方,为0.5,因此十进制数为4.5。

1.456的内存为0x355eba3f。

首先看f1的小数部分,是表中Mhuise代表的部分,这里为011 1010 0101 1110 0011 0101,总计23位。底数左边省略存储了一个1,实际底数表示为1.011 1010 0101 1110 0011 0101。

指数部分:计算机不能直接计算10的幂次,f1的指数部分为0111 1111,其十进制为127,127-127=0,即实际指数部分为0指数,代表2的0次幂,为1。因为1.011 1010 0101 1110 0011 0101无需做移动。

1+0.25+0.125+0.0625+0.015625=1.453125。

第四节:浮点型精度丢失

浮点型变量分为单精度(float)型,双精度(double)型。

浮点型使用的是指数表示法,需要记忆数值范围。

表中的double类型是-1022到1023,是通过1-2046(不能全是0或全是1,全1是2047)减去1023,得到-1022到1023。

#include <stdio.h>

// 提醒:scanf读取double类型是,要用lf,如double d;scanf("%lf",&d);
int main() {
    float a=1.23456789e10;
    float b;
    b=a+20;  // 计算时精度丢失
    printf("b=%f\n",b); // %f既可以输出float,也可以输出double类型
    return 0;
}
F:\Computer\Project\practice\20\20.6-double\cmake-build-debug\20_6_double.exe
b=12345678848.000000

进程已结束,退出代码为 0

分析:对于程序中,我们赋给a的值为1.23456789e10,加20后,应该得到的值是1.234567892e10,但b输出的结果却是b=12345678848.000000,变得更小了。我们将这种现象称为精度丢失,因为float类型数据能够表示的有效数字为7位,最多只保证1.234567e10的正确性。要使结果正确,就需要把a和b均改为double类型,因为double可以表示的精度为15-16位

注意:对于强制类型转换,int转float可能造成精度丢失,因为int是有10位有效数字的,但是int强制转为double不会,float转为double也不会丢失精度。

分类:

后端

标签:

C++

作者介绍

江小南
V1