ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

整型与浮点数内存存储全解析:补码、IEEE 754与字节序实战

整型与浮点数内存存储全解析:补码、IEEE 754与字节序实战 很多同学刚接触底层开发时都会被“整形与浮点数在内存中的存储”这个话题绕晕。尤其是当你调试网络协议、解析二进制文件或者用偏移量读一个结构体时明明读出来的是一个看似正常的十六进制序列转成整数却变成了负数转成浮点又是一位天文数字那种感觉实在让人抓狂。其实这些看似“玄学”的现象背后都有一套非常确定、非常机械的存储规则。这篇文章就把整形和浮点数在内存里到底怎么摆放、为什么这么摆放、怎么手动把一个十六进制序列翻译成看得懂的数值一次性讲透。内容不区分开发语言C/C、Python、Java、Go的读者都能参考也适合刚学《计算机组成原理》或者准备面试的在校同学。我在实际工作中经常要处理设备上报的二进制协议数据早期因为不熟悉补码和阶码经常解析出荒谬的结果后来把这两类数据的存储规则彻底吃透之后基本能做到看到字节就能心算出最终数值。下面把我的理解、实操步骤和踩坑经验全部整理出来尽量用大白话加例子保证你读完能直接上手。1. 核心设计思路先搞懂“为什么这样存”再看“怎么存”1.1 整形和浮点数的本质差异在动手拆解内存布局之前必须想清楚一个问题为什么整数和浮点数不能共用一套存储方案因为它们的“数学模型”完全不同。整数在数轴上是离散的1就是12就是2中间没有歧义。而浮点数是连续实数在计算机里的近似表示本质上是把一个数拆成“符号、指数、尾数”三部分再按规格化规则存下来。这个差异直接决定了存储策略整数追求“精确且高效”用二进制补码把加减法统一成加法让硬件电路简单可靠浮点数追求“能在有限的位数里表示极大和极小的数”所以引入了类似科学计数法的结构用一部分位存指数一部分位存尾数代价是精度有限、运算有舍入。如果把内存比作一个仓库整数就像是带有固定编号的格子每个格子放一个确定的值浮点数则像是一个带刻度的手动天平你能读出大概重量但永远存在最小刻度以下的误差。这个类比非常重要后面所有关于精度和溢出的问题都能从它推导出来。1.2 为什么你迟早要面对这个主题很多人觉得“这玩意一辈子也用不上”其实不是。有几个非常现实的场景绕不开它第一协议解析和二进制序列化。无论是写Modbus、CAN总线还是解析自定义的通信协议数据都是以原始字节在网络或总线上传输的。接收端拿到字节流后必须知道这段字节是整型还是浮点才能正确解释成数值。第二内存调试与崩溃分析。程序崩溃后你打开dump文件、查看变量窗口看到的往往就是某个内存地址里的原始字节。不懂存储规则就分不清“0x41C80000”到底是整数11亿多还是浮点数25.0。第三存储优化。数据库表设计、结构体字段重排、序列化协议压缩背后全是对存储布局的理解。一个结构体字段顺序写错可能白白浪费几十个字节一个浮点被当整数存取数据直接损坏。所以这篇文章不仅是“考试知识点”更是一线开发的生存技能。下面进入硬核部分。2. 整数在内存中的存储补码的来龙去脉2.1 原码、反码、补码三兄弟的恩怨在计算机里整数通常以“补码”形式存储。要理解补码先得知道它两个“前任”原码和反码。原码最简单最高位是符号位0为正1为负其余位是数值的绝对值。比如一个8位整数7就是0000 0111-7就是1000 0111。这种表示直观但有两个硬伤一是存在“正零”和“负零”两个零0000 0000和1000 0000浪费且判断麻烦二是做减法时电路复杂因为需要额外考虑借位和符号判断。反码在原码基础上把负数的数值位全部取反。例如 -7 的反码是1111 1000。反码解决了部分减法问题但“正零”“负零”的问题依然存在1111 1111表示负零。补码彻底解决了这个问题。补码的定义是正数的补码等于原码负数的补码等于原码除符号位外按位取反再加1。还是以 -7 为例原码 1000 0111 取反 1111 1000 加1 1111 1001所以 -7 在8位内存里存的是1111 1001。这里有一个更形象的公式补码 2^n - 绝对值其中 n 是位宽。8位情况下-7 就是 256 - 7 249换成二进制正好是1111 1001。这个公式看着像是数学游戏但它的意义在于统一了加减法任何减法都能转化为补码相加硬件只需要一个加法器。2.2 为什么补码能彻底统一加减法我自己消化补码的“啊哈时刻”是用模运算来理解的。想象一个只有两位的十进制计数器从99再加1会变成00。在这种“模100”的世界里减去7就等于加上93因为 100 - 7 93。比如 50 - 7在模100世界里等价于 50 93得到143截断到后两位就是43结果恰好正确。二进制计算机的整数运算也是这样。以8位为例一切运算都在“模256”下进行。于是加一个负数就等价于加它的“补数”256 - 绝对值。因为 256 恰好是 2^8二进制里的“按位取反再加1”就是求这个补数最省事的算法。这直接解决了两件事第一0的表示唯一。8位里正零和负零的补码都是0000 0000不存在二义性。第二取值范围比原码多一个数。8位原码能表示 -127到1278位补码能表示 -128到127。多出来的 -128它的补码是1000 0000。注意这个数“取反加1”还是它自己因为 128 在8位里已经溢出了它的补码正好是符号位为1、数值位全0。举几个实际内存例子加深印象十进制8位补码内存字节说明00000 0000唯一零10000 0001正数直接存1270111 1111最大正数-11111 1111全1就是-1-1281000 0000最小负数我用“全1就是 -1”这个经验在调试时很管用看到一个内存字节是0xFF第一反应该想想它是不是一个被当作无符号处理的 -1。2.3 实操在C/Python里亲眼看看整数内存布局理论说完必须动手验证。C语言里可以直接用指针或者联合体把变量的内存“看”出来。下面这段代码在常见小端平台上会把 int 型变量的每个字节按16进制打印出来#include stdio.h #include string.h void dump_bytes(void *p, size_t len) { unsigned char *b (unsigned char *)p; for (size_t i 0; i len; i) { printf(%02X , b[i]); } printf(\n); } int main(void) { int a -7; printf(int a -7, 内存字节(小端): ); dump_bytes(a, sizeof(a)); int b 0x12345678; printf(int b 0x12345678, 内存字节(小端): ); dump_bytes(b, sizeof(b)); return 0; }在x86等小端平台上输出会是int a -7, 内存字节(小端): F9 FF FF FF int b 0x12345678, 内存字节(小端): 78 56 34 12第一个输出很好理解-7 的32位补码是FFFFFFF9。第二个输出则揭示了“小端字节序”数据高字节存内存高地址数据低字节存内存低地址所以低地址看到78 56 34 12而人类阅读习惯是12 34 56 78两者顺序正好相反。如果不想要C语言的指针操作Python也很方便import ctypes a ctypes.c_int32(-7) print(a.value, hex(a.value 0xFFFFFFFF)) # -7 0xfffffff9这里 0xFFFFFFFF是把负数补码用无符号形式打印出来。关于字节序大小端我建议每个开发者都养成习惯看到十六进制字节序列第一时间问一句“这个平台是大端还是小端”。C语言打印出来的字节顺序和你在读者自己机器上可能完全相反。常见的x86、ARM处理器默认小端而网络字节序规定是大端所以跨端传输整数时一定要做转换否则数值会被“倒着读”。3. 浮点数在内存中的存储IEEE 754完全拆解3.1 单精度与双精度的位布局浮点数的存储标准是 IEEE 754当前所有主流语言和硬件都遵循它。它以“科学计数法的二进制版”为核心把内存分成三块符号位、指数位、尾数位。单精度 float 总共32位编号从高位到低位位数含义长度第31位符号位S1位0为正、1为负第30~23位指数位E8位无符号数第22~0位尾数位M23位双精度 double 总共64位位数含义长度第63位符号位S1位第62~52位指数位E11位第51~0位尾数位M52位有人会问那存储的数字 (-1)^S × 1.M × 2^(E-127) 对吗大体对但有一个重要前提——这是“规格化数”的情况。这里的 1.M 不是单纯把M当作二进制小数而是隐含了一个“1.”前缀这叫隐含位。后面单列一节详细说。3.2 规格化、隐含位与指数偏移量规格化Normalized是浮点数表示的核心规则。它要求任何非零数都写成“1.xxx × 2^n”的形式小数点前只有一位且必须是1。比如十进制25.0二进制是11001.0规格化后写成 1.1001 × 2^4。既然每个规格化数的小数点前总是1那这一位就不必显式存储可以省出来多存一位有效数字这就是隐含位。23位尾数实际表达的有效位是24位52位尾数表达的是53位。这一点在日常判断“float到底能精确到几位”时很关键。指数为什么要用偏移量而不是直接存负数因为指数可正可负如果直接用补码存储比较大小会非常麻烦而用“无符号整数加偏移”可以简化硬件比较器。单精度偏移量127双精度偏移量1023。存储的指数E 实际指数 偏移量。还是用25.0做例子25.0 符号位 S 0规格化尾数 1.1001实际指数 4存储指数 E 4 127 131二进制1000 0011尾数部分 M 存小数位1001后面补0整体32位为0 10000011 10010000000000000000000换成十六进制就是0x41C80000。这里可以直接记住一点一个 float 在内存中的原始十六进制就是这个规则算出来的产物。3.3 特殊值、非规格化数与范围表IEEE 754预留了一些特殊指数值来处理边界情况指数E尾数M含义全0全0±0全0非0非规格化数靠近0的极小数全1全0±无穷大全1非0NaN不是一个数非全0非全1任意规格化数非规格化数Denormalized我最初以为没啥用直到处理传感器数据时才体会到它的价值。当数值小到最接近0时规格化数的隐含位“1.”无法继续缩小指数因为指数已经全是0了。此时规则切换隐含位变成0即表示0.M × 2^(1 - 偏移量)这样可以继续表示更小但精度稍微降低的数。没有非规格化数下溢就意味着直接变0有了它可以“逐渐逼近零”让曲线平滑很多。单精度浮点数的绝对值范围大约是 1.18×10^-38 到 3.40×10^38双精度大约是 4.9×10^-324 到 1.8×10^308。注意这些范围说的是“绝对值上限”而不是有效数字位数。有效数字方面float大约7位十进制double大约15~17位十进制。这是工程上选型的最直观依据。3.4 实战拆解25.0和0.1在内存里长出什么样25.0上面已经推过结果是0x41C80000。这里补充一个验证方法在Python里用一行代码看任意浮点数的十六进制内存值import struct print(hex(struct.unpack(I, struct.pack(f, 25.0))[0])) # 输出: 0x41c80000再来看一个更揪心的例子0.1。十进制0.1转换成二进制是无限循环小数0.0001100110011001100... 规格化后是 1.10011001100110011001100... × 2^-4。但尾数只有23位必须截断或舍入。所以0.1在内存里存的其实是“离0.1最近的一个二进制浮点数”并不是精确的0.1。这直接解释了为什么计算机里0.1 0.2 ! 0.3。因为参与运算的三个数都是近似值结果自然也是近似值而且舍入误差会累积。如下面的代码所示print(0.1 0.2 0.3) # False print(0.1 0.2) # 0.30000000000000004这不是计算机“算错了”而是它在用二进制近似十进制小数误差是常态。理解这一点是避开无数浮点比较坑位的起点。4. 实操十六进制转浮点数与内存查看的完整流程4.1 手动拆分一个十六进制浮点数拿到一个32位十六进制比如0x3F800000怎么知道它对应哪个float第一步把十六进制拆成二进制。0x3F800000等于0011 1111 1000 0000 0000 0000 0000 0000。第二步按“1位符号 8位指数 23位尾数”切开符号位 S 0 指数位 E 01111111 127 尾数位 M 00000000000000000000000 0第三步还原。实际指数 E - 127 0隐含位为1尾数为0所以数值 1.0 × 2^0 1.0。没错0x3F800000就是 float 1.0。再拆一个0x40A00000二进制0100 0000 1010 0000 ... 符号位 S 0 指数位 E 10000001 129实际指数 2 尾数位 M 01000000000000000000000隐含位1 尾数位“0.01”所以尾数整体 1.01。二进制里的1.01换算成十进制是 1 0.25 1.25。最终数值 1.25 × 2^2 5.0。所以0x40A00000是 float 5.0。这个手动拆解的过程不需要任何工具熟练之后看十六进制的关键位基本能估算出数值的大概量级。遇到要精算的场景再用代码验证。4.2 用Python/C一键完成字节流转换手动拆适合理解原理实际工程里必须有一批趁手的工具函数。Python的struct模块是最方便的没有之一import struct def hex_to_float(hex_str): 输入形如 0x41C80000 的十六进制字符串返回 float 值 value int(hex_str, 16) return struct.unpack(f, struct.pack(I, value))[0] def float_to_hex(f): 输入 float返回十六进制字符串 return hex(struct.unpack(I, struct.pack(f, f))[0]) print(hex_to_float(0x41C80000)) # 25.0 print(float_to_hex(5.0)) # 0x40a00000注意我用了I表示无符号整数按小端处理、f表示单精度浮点小端。如果你在处理文件或网络报文一定要先确认字节序再决定符号。如果字节序反了哪怕十六进制相同结果也完全不一样。C语言里转换更直接但要注意“严格别名”的问题。推荐用memcpy而不是直接指针强转#include stdio.h #include string.h #include stdint.h int main(void) { uint32_t raw 0x41C80000; float f; memcpy(f, raw, sizeof(f)); // 安全地把位模式复制到float printf(%f\n, f); // 输出 25.000000 float g 5.0f; uint32_t raw2; memcpy(raw2, g, sizeof(raw2)); printf(0x%08X\n, raw2); // 输出 0x40A00000 return 0; }4.3 内存查看实操从变量地址开始真实调试时你想看的不是“已知十六进制算值”而是“已知变量想看他内存里的原始字节”。在GDB里用x/4bx就能查看一个float变量的4个字节(gdb) set var $f 25.0 (gdb) x/4bx $f 0xffffcbbc: 0x00 0xc8 0x41 0x41不对这里注意小端存储下 25.0 0x41C80000在内存低地址到高地址依次是00 C8 41 。这里要不回看一遍0x41C80000写成四个字节0x41、0xC8、0x00、0x00。小端存储时低位字节0x00存在低地址高位字节0x41存在高地址所以内存中顺序是00 00 C8 41。刚才我写0x00 0xc8 0x41 0x41是错的需要更正为00 00 C8 41。这种小细节最容易让人出错写文档演示时更要加倍小心。下面是正确的演示(gdb) set var $f 25.0 (gdb) x/4bx $f 0xffffcbbc: 0x00 0x00 0xc8 0x41第一字节0x00对应低8位最后一字节0x41对应最高8位。把顺序倒过来读就是便于人类理解的0x41C80000。调试新手最容易犯的错误就是看到内存字节00 00 C8 41直接当成大端数读成0x0000C841然后算出完全错误的数值。在一切二进制调试中我都建议先把“平台字节序”写在便签上最好连十六进制转储工具也默认配置成字节序感知模式。5. 浮点数运算的典型坑位与排查方法5.1 不要直接比较浮点数相等前面已经演示了0.1 0.2 ! 0.3。实际业务里最典型的翻车现场是“金额汇总后判断是否为零”或者“迭代收敛判断”。只要涉及浮点运算直接用比较就是定时炸弹。正确的比较方式是设定一个允许误差通常用一个很小的 epsilondef float_eq(a, b, eps1e-6): return abs(a - b) eps print(float_eq(0.1 0.2, 0.3)) # Trueepsilon 怎么取才是关键。如果值是千量级误差可能到1e-9甚至更大如果值在小数点后8位epsilon取1e-12可能又太严格。经验法则epsilon的量级要比你实际关心的“最小有效位”小一到两个数量级同时比你计算的舍入误差大。不要照搬网上所有1e-6要结合自己的数值范围去调整。5.2 观察舍入方向为什么打印结果有时偏大有时偏小浮点舍入规则默认是“就近舍入”也就是看被丢弃部分是否超过最低保留位的一半决定进位还是舍去。这就会让误差在正负方向上随机分布有时结果偏大有时偏小。比如 0.1 存储后的近似值实际略大于0.1而 0.2 的近似值略大于0.2两者相加后累积误差就到了小数点后第17位最终打印成0.30000000000000004。排查舍入问题时可以先把数值用高精度打印出来看完整形式print(f{0.1 0.2:.20f}) # 输出: 0.30000000000000004441看到这样的展开你就知道问题出在二进制近似的尾数上而不是某个程序逻辑错误。5.3 大数吃小数的“吸星大法”浮点还有一个隐蔽的坑当一个很大的数和一个很小的数相加时小数的有效位数可能被完全吞掉。原因是规格化后两者要对齐指数尾数为了对齐会右移右侧被挤出位宽的直接丢弃。large 1e20 small 1.0 print(large small) # 1e20small被吞掉这解释了为什么在累计求和时如果先加大量级数据后加小量级数据结果可能不准确。改进策略是“从小到大累加”或者用Kahan求和算法补偿误差。工程项目里我会优先选择整数类型存金额以分为单位真需要浮点累加时再用补偿算法。这类“移动小数点变为定点数”的思路其实是很多计费系统惯用的做法。另外一个高频坑循环里不断累加0.01到后面误差会被放大。比如用浮点累加100次0.01结果可能是0.9999999999999999再转成int直接变成0而不是1。解决方法是最后加一个小的epsilon再取整或者全程用整数分。6. 存储层面的效能与细节结构体对齐、原位布局与成本意识6.1 结构体字段顺序如何影响实际占用的内存整型与浮点数的存储规则不仅影响单个变量还影响结构体的整体内存布局这就是对齐Alignment。处理器读取内存时按“字长”分块读取更高效。许多体系结构要求4字节或8字节类型按边界对齐比如4字节int的地址必须是4的倍数否则可能报总线错误在ARM上常见或者性能骤降。编译器会自动在结构体里插入填充字节保证每个字段对齐。看一个经典例子struct Example1 { char c; // 1字节 float f; // 4字节 short s; // 2字节 };默认对齐下c后面会填充3个字节f占用4字节s后面填充2个字节使得结构体总大小恰好是12字节。如果调整字段顺序把大的字段放前面struct Example2 { float f; // 4字节 short s; // 2字节 char c; // 1字节 };占用就会变成8字节比原来节省1/3的空间。在密集存储上百万条结构化记录的系统里这个优化的收益非常可观。回归到“整形和浮点数存储”主题结构体对齐的本质就是让每个字段的存储满足“字段首地址 类型大小 × k”的约束。如果你在手工解析二进制文件结构体或者是用字节流读写结构体时必须知道哪些地方有编译器插入的填充字节否则文件里读出来的数据会和结构体定义对不上轻则乱码重则内存错位。6.2 用位域和压缩手段提升存储密度在一些对空间极其敏感的场景嵌入式、存储引擎、网络协议开发者会使用位域Bit Fields手动分配每个字段的位数。例如用一个32位字段同时存储符号位、指数部分和尾数部分正好就是浮点数的位布局。不少通信协议干脆用“用户自定义位宽”来压缩时间戳、状态标志等这本质上是在内存/字节层面精确控制存储而不仅仅是使用现成类型。如果你在C语言里定义了一个带位域的结构体请记住两个原则其一位域在内存中的分配方向跟字节序有关跨平台可移植性差其二位域的访问通常比整型字段慢因为它需要额外的位移和掩码操作。凡是用位域节省的空间都需要用执行时间做交换。6.3 JavaScript/Java等语言里的“字段重排”与存储优化Java HotSpot VM会按照对象头之后的字段顺序重新排列字段以最小化填充字节。JVM的字段分配不仅取决于声明顺序还取决于虚拟机的“字段重分配”策略。如果你在写需要精确控制内存布局的Java代码比如对接Off-Heap内存或者用Unsafe读写结构就不能简单假设“字段声明顺序就是内存顺序”。在Go语言里也有类似情况编译器会重新组合结构体字段顺序来减少填充。所以你在Go中定义结构体时应该把大字段放前面或者用工具检查实际大小。很多时候一个简单的字段位置调整就能让结构体从24字节降到16字节。不管是哪种语言掌握底层“整数和浮点数怎么摆放”的原则都能让你更清楚地预测结构体实际占用和序列化后的字节流形态而不是依赖编译器偶然行为。7. 常见问题速查与避坑经验7.1 排查问题时的第一反应清单我把日常工作中遇到和存储表示相关的问题整理成了一张表遇到类似现象直接按表定位症状可能原因排查方向读到的大整数是负的符号位被误判或者类型声明错误无符号读取成了有符号检查协议字段定义确认用int还是uint字节反转导致数值离谱大小端不匹配统一字节序用网络序转换工具float打印成天文数字把整数类型按浮点解读确认类型位宽与符号0.1 0.2 ! 0.3二进制浮点本质误差用epsilon比较或改定点数累加结果和预期偏差越来越大大数吞小数或未补偿舍入调整累加顺序使用Kahan补偿结构体读写与文件对不上编译器填充字节导致偏移错位用#pragma pack或手工序列化并在代码里记录实际偏移从内存dump里找不到目标值字节序或位宽用错数值有舍入打印所有可能的解读组合int/uint/float/double我的习惯是解析任何二进制数据前先写一个最小测试用已知字节喂给解析逻辑确认输出和预期一致再接入真实数据。比如把0x41C80000交给float解析必须等于25.0否则不要把真实数据倒进来调试否则错误叠加很难定位。7.2 手工换算浮点数的练习方法如果想熟练到“看到十六进制就知道浮点数大概多少”我建议做三组练习。第一组从整数到float随便想一个十进制小数比如3.75转换成二进制11.11规格化1.111 × 2^1写出存储的E128、M111000...最终得到0x40700000再用代码验证。第二组从float十六进制到十进制随便抓一个十六进制如0x3DCCCCCD拆开算能得到约0.1正好是0.1的float近似。第三组边界值算一个非规格化数比如最小值附近的值体会指数全0的变化。这个练习累计做10次左右你对浮点数的直觉就会大幅提升。7.3 关于“输入整形器”的联想与澄清“输入整形器”乍一看和整形存储有点撞车但它其实是控制系统里的技术术语用来生成平滑的控制指令避免机械结构产生振动。它和本篇文章主题无关。在搜索资料时看到这个词建议不要混淆。这也提醒我们在项目文档中用词要精确“整型”是数据类型“整形”常见于信号处理与控制系统。本文所有“整形”均指程序设计中的整数类型即“整型”。7.4 一个完整的16进制转浮点解析流程示例最后给一个综合示例场景是从文件偏移0x20处读到一个4字节序列41 C8 00 00小端存储。要求还原成浮点数。步骤一确认平台字节序。如果文件由本机程序写入且本机为小端则内存字节就是41 C8 00 00意味着数值的十六进制应反向读取为0x0000C841不对这里要细想。小端模式下41 C8 00 00在内存中四个字节低地址41、C8、00、00。一个小端32位数的数值表示为最低有效字节在前也就是0x0000C841的十六进制解释为地址顺序41 C8 00 00等一下不对。其实例子有点绕。让我清晰一点如果文件/内存字节顺序是00 00 C8 41小端读取为整数0x41C80000正好是float 25.0。如果字节顺序是41 C8 00 00小端读取的整数是0x0000C841对应浮点数非常小约1.8e-41。所以在实际文件中一定要先明确“文件里存的是大端还是小端我按什么方式读取”。如果用struct.unpack(f, b\x00\x00\xc8\x41)读取结果就是25.0如果用struct.unpack(f, b\x00\x00\xc8\x41)读取结果约成1.9e-19完全离谱。这就是字节序错误造成的典型现象。实际排查中我看到一个字节序列时通常这样做1. 先确定位宽4字节/8字节。 2. 确定字节序来自协议文档/设备手册。 3. 使用正确的字节序解析。 4. 解析结果如果出现极小的荒谬数值立即检查字节序是否反了。 5. 如果数值正常但精度奇怪检查是不是把float当double读了。这种“顺序化排查”特别实用。很多同事让我帮忙看协议解析问题我用这五步能在几分钟内定位90%以上的错误剩下的才是逻辑问题。8. 个人经验与扩展建议我在很多项目里反复体会到越是想“绕过底层细节”做应用越容易被底层细节绊倒。存储规则就是典型例证。协议解析、写文件格式、设计数据库字段类型、排查崩溃转储、做性能优化、实现序列化凡是要把数值落成二进制的地方理解整型和浮点型的存储就是前提条件。这个知识点像一门“通用语言”跨语言、跨平台、跨场景都适用。再分享最后一个小技巧把常用字节序列的十六进制“记住”。比如0x3F800000是1.0f0x40000000是2.0f0x40490FDB约等于3.1415926float的π0x3F000000是0.5f。看到这些字节能立刻判断数值量级也能用它们快速校准解析代码。类似的整数方面记住-1的8位表现0xFF、16位表现0xFFFF、32位表现0xFFFFFFFF在排查“有符号/无符号混淆”时非常有用。这个主题还可以继续延伸的方向很多比如定点数在嵌入式环境中的使用、Kahan求和算法实现、SIMD向量化对存储对齐的要求、序列化框架Protocol Buffers/MessagePack如何用变长编码压缩整数、JSON/CSV中浮点格式化对解析效率的影响。每一步扩展底层都绕不开“数值怎么在二进制里表达”这个根源。先把本文的内容吃透扩展起来会顺畅得多。
返回列表