计算机基础 | 整数浮点数存储逻辑+大小端字节序

0 阅读1分钟

平时你是否遇见过下面这种情况?

                                                        此图源自比特就业

       同样是 “9”,为什么 int 和 float 互相转换会得到完全离谱的结果?答案藏在计算机的内存存储规则里 —— 今天就从底层拆解整数、浮点数的存储逻辑,再用代码实战验证大小端,让你彻底搞懂这些底层原理!

一、前言

位(bit):计算机最小的存储单位。 

字节(Byte):1 字节 = 8 位,是数据处理的基本单元(文件大小、内存占用都按字节算);

二进制高低位:最左侧是高位,最右侧是低位(比如 1100 0010,左边的 1 是高位,右边的 0 是低位)。

十进制转二进制:

  • 整数部分:将十进制整数反复除以 2,记录每次的余数,直到商为 0,最后将余数倒序排列就是二进制结果。 
  • 小数部分:将十进制小数反复乘以 2,记录每次的整数部分(0 或 1),直到小数部分为 0(或达到精度要求),最后将整数部分正序排列就是二进制小数。
  •  eg:十进制:5.5 对应的二进制:101.1

二、整数在内存中的存储

整数分有符号(int/char)和无符号(unsigned int),核心差异是 “是否有符号位”,而负数的存储是关键 —— 内存中存的是补码

(一)有符号的整数:

三种表示方式均有符号位和数值位两部分。最高位为符号位,符号位上0表示正,1表示负。其余为数值位。

正整数:原码、补码、反码都相同。

负整数:

  • 原码:将数值按照正负数形式转换为二进制得到的就是原码。 
  • 反码:原码符号位不变,剩余位依次按位取反即可得到。 
  • 补码:反码加一即可得到补码
  • eg:-5 在 8 位 char 类型中的存储形式

(二)无符号整数:

与有符号整数的区别:没有符号位,所有位数都用来表示数值,原码 = 反码 = 补码。

(三)范围说明

char:

无符号:位数为8,所有位均表示数值 

             最大取值:1111 1111 = 255 

             最小取值:0000 0000 = 0 

有符号:位数为8,最高位表示符号,其余位表示数值 

             最大取值:0111 1111 = 127 

             最小取值:1000 0000 = -128 

思考为什么8位有符号是-128~127?

补码规则下,0 只有一种表示(0000 0000),而原码 1000 0000 本应表示 “-0”(无意义),因此被重新定义为 -128,相当于多挤出一个数值位,范围从 -127 扩展到 -128。

int:

无符号:位数为32,所有位均表示数值 

             最大取值:2^32 −1=4294967295 

             最小取值:0000 0000 …… 0000 = 0 

有符号:位数为32,最高位表示符号,其余位表示数值 

             最大取值:2^31−1=2147483647 

             最小取值:−2^31 =−2147483648

三、浮点数在内存中的存储

(一)浮点数存储的核心标准:IEEE 754

核心公式

所有符合IEEE 754 标准的浮点数,最终值都按以下公式进行计算:

  • S:符号位(0/1)
  • M:尾数的二进制小数 
  • E:指数位的十进制值

(二)浮点数存的过程

                                                           此图源自比特就业

  1. 符号位:

S:0表示正数,1表示负

2.指数位:

偏移量

指数部分存储的是指数真实值+偏移量。

  • 单精度偏移量:127 
  • 双精度偏移量:1023

3.尾数:

规格化数:

当指数不全为0且不全为1时,尾数前隐含一个1,即实际尾数=1.……(二进制)。这样表述的有效数字比存储位多一位,提高精度。

  • 规格化数的尾数在存储时去掉前导1,仅存储小数部分。

      4.特殊值

  • 0:指数和尾数全为0(符号位可为0或+0) 
  • 无穷大:指数全为1,尾数全为0(符号位区分正负无穷

      5.示例

将十进制数-5.75转换为单精度浮点数

1.转换为二进制:-5.75=-101.11 

2.规格化:-1.0111*2^2 

3.符号位:S=1(负数) 

4.指数:E 2+127=129=10000001(二进制) 

5.尾数:去掉 1.……,得到01110000000000000000000(23位) 

6.拼接:1 10000001 01110000000000000000000

将十进制数0.5转换为单精度浮点数

1.转换为二进制:0.1 

2.规格化:1.0*2^(-1) 

3.符号位:S=0(正数) 

4.指数:E -1+127=126=01111110(二进制) 

5.尾数:00000000000000000000000 

6.拼接:0 01111110 00000000000000000000000

(三)浮点取的过程

指数E从内存中取出还可以再分为三种情况: 

E不全为0或不全为1

• 指数计算:真实指数 = 存储的 E 值 - 偏移量(单精度为 127,双精度为 1023) 

• 尾数处理:有效数字 M 前补隐含的 1,即数值形式为 1.M × 2^(真实指数)

• 示例:0.5

  1. 二进制:0.1 → 规格化为 1.0 × 2^(-1) 
  2. 指数:-1 + 127 = 126 → 二进制 01111110 
  3. 尾数:去掉整数部分 1 后补 0 至 23 位 → 00000000000000000000000 
  4. 完整存储:0 01111110 00000000000000000000000(符号位 0 表示正数) 

E 全为 0

  • 指数计算:真实指数 = 1 - 偏移量(单精度为 1-127=-126,双精度为 1-1023=-1022)
  • 尾数处理:有效数字 M 前不再补 1,即数值形式为 0.M × 2^(真实指数)

E 全为 1

  • 若尾数 M 全为 0:表示 “±∞”(符号位决定正负)
  • 若尾数 M 不全为 0:表示NaN(Not a Number),用于无效运算结果(如 0/0、∞-∞)

回顾最开始导入的那段代码

int n=9的存储:00000000 00000000 00000000 00001001(十六进制:0x00000009) 

int取出:直接按 “符号位 + 数值位” 转十进制,所以解析结果就是 9。 

float取出:S=0 E=00000000(十进制 0)M=0000000 00000000 00001001(后面补 0 到 23 位) 最终值 V = (-1)^S*(0.M)*2^(1-127)(极接近 0),所以最终输出0.000000。 

float 9.0 的存储:0 10000010 00100000000000000000000(十六进制:0x41100000) 

int取出:0x41100000 转十进制:1091567616 

float取出:S=0 E=10000010 M=00100000000000000000000 指数=130-127=3 所以会输出9.000000

四、大小端字节序和字节序判断

多字节类型(int/short/long)在内存中怎么排列?比如 0x12345678(4 字节),是先存 0x12 还是 0x78?

这就是**「大小端」**的核心问题(单字节 char 无此问题)。

大端模式(Big-Endian)

  • 核心规则:高位字节 → 低地址,低位字节 → 高地址 
  • 类比:和我们书写数字的顺序一致(如0x12345678,先写高位0x12,再写低位0x78

小端模式(Little-Endian)

  • 核心规则:低位字节 → 低地址,高位字节 → 高地址 
  • 类比:和我们书写数字的顺序相反(如0x12345678,先存低位0x78,再存高位0x12
  • 假设0x12345678存储在地址0x100:

判断字节序

#include <stdio.h>
int main() {
​    int num = 0x12345678; // 4字节整数​    
    char* p = (char*)&num; // 指向低地址的第一个字节​
    // 小端:低地址存 0x78;大端:低地址存 0x12​
    printf("当前字节序:%s\n", *p == 0x78 ? "小端模式" : "大端模式");​      
    return 0;​
 }

运行结果:绝大多数电脑会输出「小端模式」(比如 Windows、Linux 主机)。