ARTICLE DETAIL

资讯详情

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

浮点数在内存中如何存储?IEEE 754标准与二进制布局全解析

浮点数在内存中如何存储?IEEE 754标准与二进制布局全解析 1. 为什么0.1加0.2不等于0.3一切要从内存里那串二进制说起很多刚接触底层开发的同事都问过我同一个问题明明在纸上算得清清楚楚的0.1加0.2等于0.3为什么C语言或者Java里一跑结果却是0.30000000000000004更诡异的场景是串口通信里我明明发了一个浮点数10.25对端接收后打印出来却是-1.352e-08这种莫名其妙的数字。这些问题的根源全都指向同一个地方浮点数在内存中的存储格式。我们在日常业务代码里用惯了int、long、String很少会去关心它们在内存里到底是怎么排布的。但一旦你开始做嵌入式开发、写网络协议解析、调串口通信、做异构平台的数据交换或者排查一个我的程序在Windows上正常移植到Linux上数字就变了的诡异Bug你就不得不把目光沉到内存层面去审视这些数据。可以说理解浮点数在内存中的存储是区分会用浮点数和真正懂浮点数的分水岭。这篇文章就以IEEE 754标准为主线把单精度float和双精度double在内存中的二进制布局、规格化规则、特殊值编码、大小端字节序影响这些核心知识点全部拆开揉碎讲一遍最后再附上我自己在实际项目里调试浮点数问题时的排查经验和代码范例。不管是做RTOS嵌入式开发的写通信网关的还是单纯想搞明白0.10.2到底怎么回事的科班学生这篇文章都值得你收藏慢慢看。2. 先从整数说起补码存储为什么不能直接套用到浮点数想要理解浮点数的存储最好的切入点是对比整数的存储方式。整数在内存里的排布是所见即所得的比如一个32位有符号整数7它的二进制就是00000000 00000000 00000000 00000111负数呢用补码表示-7就是7的二进制取反加1得到11111111 11111111 11111111 11111001。补码这套设计非常巧妙它把减法也统一成了加法运算硬件上只需要一个加法器就能完成加减法这也是几乎所有现代处理器都采用补码存储有符号整数的原因。但整数这种表示方式有一个核心假设数值是离散的每个整数都有精确的二进制表示不存在近似一说。浮点数则完全不是这样它的本质是连续实数在有限二进制位下的近似投影。如果你用整数的思路来存浮点数会出现两个致命问题第一个问题范围不够。32位整数最大也就21亿出头如果我想表达1.5×10^300这种天文数字或者1.6×10^-35这种极小值用定点整数的思路根本做不到。物理世界里很多量是跨越几十个数量级的从普朗克常数到宇宙尺度需要一套能够动态缩放的表示方法。第二个问题精度不灵活。如果固定用32位来表示一个小数那么无论数字多大它的小数部分精度都是固定的。可实际需求是数字越大我越不关心它的绝对精度关心的只是相对精度数字越小精度反而越敏感。这就需要精度能够跟随指数的变化而浮动——这正是浮点这个词的含义。IEEE 754标准就是为了解决这两个问题而诞生的。它的核心设计思路是把一个小数拆成符号、指数和尾数三部分用类似于科学计数法的方式存储。科学计数法我们都知道比如地球到太阳的平均距离是1.496×10^8公里这里1.496就是尾数有效数字8就是指数符号为正。浮点数的存储本质就是二进制的科学计数法只不过它把底数固定为2而不是10。这套设计的直观类比就是游标卡尺或者对数坐标纸无论数值是大是小它都能用有限的比特位保证一定数量的有效数字位而不是死板地固定小数点位置。理解了这个初衷后面再去看IEEE 754的那些看上去奇怪的规则就顺理成章了。3. IEEE 754核心布局符号位、指数位、尾数位的三角关系IEEE 754标准规定了两种最常用的浮点数格式单精度float32位和双精度double64位。它们的二进制布局大同小异区别只在于每个字段占据的位数不同。3.1 单精度与双精度的字段划分单精度float总共32位被划分为三个部分第31位符号位0表示正1表示负。第30~23位指数位共8位。第22~0位尾数位也叫有效数字位、小数位共23位。双精度double则是在同样的逻辑下扩大了位数第63位符号位。第62~52位指数位共11位。第51~0位尾数位共52位。用一个表来对照更直观格式总位数符号位指数位尾数位指数偏置值float321823127double64111521023这里的指数偏置值bias是初学者最容易卡壳的地方后面我会用一整节来讲它。现在先记住浮点数的实际指数值并不是直接从8位二进制里读出来的那个数。3.2 从十进制小数到二进制存储一个完整的转换案例理论说多了容易晕我们直接用一个实际数字过一遍完整流程。就以10.25这个比较好算的数为例把它转成32位float在内存里的实际字节序列。第一步把十进制小数转成二进制。10.25可以拆成整数部分10和小数部分0.25。10除以2取余得到10100.25乘以2取整得到0.01。所以10.25的二进制就是1010.01。第二步规格化。所谓规格化就是让二进制小数变成1.xxx × 2^n的形式也就是把小数点移动到第一个非零位的右侧。1010.01左移3位变成1.01001 × 2^3。注意这里的指数3是十进制的在二进制浮点存储体系里指数部分的真实含义是2的3次方。第三步确定三个字段的值。符号位为0这是正数。尾数部分是小数点后面的那串01001剩余的23位用0补齐所以尾数字段是01001000000000000000000。指数部分需要计算真实指数是3加上偏置值127得到130130换算成8位二进制是10000010。第四步把三部分拼起来0 10000010 01001000000000000000000按字节切分就是01000001 00100100 00000000 00000000换算成十六进制就是0x41240000。如果你在C语言里打印float变量10.25的内存字节看到的正是这4个字节。这个结果非常经典在很多通信协议文档里都能见到它的身影比如Modbus协议里读写保持寄存器存储浮点数用的就是类似这种4字节序列的排布方式只是字节顺序需要按照大小端再做一次调整。3.3 指数偏置值为什么存在为了比较更是为了特殊值上一节里指数部分加的这个127双精度是1023就是偏置值。很多初学者不理解指数8位无符号能表示0~255直接存3不就行了为什么要加127再来个偏移这里有一个很容易被忽略的细节浮点数的指数可能为负。1.01001 × 2^-3也是合法的浮点数可指数位只有8位如果采用有符号数比如补码表示那么在比较两个浮点数大小时硬件需要分别比较符号位、指数位、尾数位而且还要处理负指数和正指数的比较规则非常麻烦。IEEE 754的设计是把指数整体平移到非负区间真实指数-126到127对应存储值1到254存储值0和255留作特殊用途。这样一来指数位可以当作一个普通无符号整数直接比较大小配合上符号位的设计甚至可以让整个浮点数作为有符号整数直接比较不考虑NaN的情况下。这种偷懒的设计在硬件实现上省下了大量逻辑门电路也简化了浮点比较指令的实现。另一个更重要的原因是存储值全0和全1被赋予了特殊含义用来表示零、无穷大和NaN非数值。如果指数位用补码表示那么全0和全1对应的指数范围就会不连续特殊值的编码就会变得很别扭。采用偏置方案后存储值0对应的是非规格化数的保留区存储值255对应的是无穷大和NaN的保留区中间的1~254才是正常的规格化数整个编码空间变得非常干净利落。4. 规格化、非规格化与特殊值IEEE 754的隐藏规则写业务代码时研究者很少会遇到NaN或非规格化数但在嵌入式开发和通信协议解析中这些边界情况反而是Bug高发区。我见过有人在协议解析代码里写float temp *((float*)buffer);结果对方传过来一个表示无效数据的NaN程序直接进入异常分支。所以这一节我们把规格化数、非规格化数和特殊值全部讲透。4.1 规格化数绝大多数浮点数的归宿当指数位的存储值在1到254单精度之间时这个浮点数被称为规格化数。规格化数的特点是尾数前隐含一个固定的1也就是二进制的科学计数法永远写成1.xxx的形式这个1不入内存属于隐含位。正因为隐含了这个123位的尾数实际提供了24位的精度双精度是52位尾数提供53位精度。这也是为什么float的有效精度大约只有7位十进制数字double大约是15~16位十进制数字。很多人问我float能精确表示多少位小数标准答案是float的精度大约6到7位有效十进制数字注意是有效数字而不是小数位。对于很大的数比如16777216.02^24float只能表示整数小数部分直接被吃掉对于很小的数比如0.000001它能表示若干位小数但同样的近似误差也存在。规格化数能表示的最小正数就是指数存储值为1、尾数全0的情况即1.0 × 2^(-126)约等于1.17549435e-38。最大正数则是指数存储值为254、尾数全1的情况约等于3.40282347e38。超过这个范围就会溢出为正无穷。4.2 非规格化数当数字小到逼近0时如何自救当指数存储值为0、尾数不全为0时这个数被解释为非规格化数。此时隐含位不再是1而是0也就是说数值被表示为0.xxx × 2^(-126)的形式。这有什么意义呢如果没有非规格化数那么从1.17549435e-38最小规格化正数到0之间会出现一个巨大的空洞比这个数小的正数会直接被舍入为0。这在很多科学计算里是不可接受的比如某个数值在计算过程中需要一步步趋近于0结果提前被截断成0可能引发除零错误甚至整个系统崩溃。非规格化数的存在让这个空洞被无数个密度更低的微小刻度填满最小可表示的正数被扩展到约1.40129846e-45尾数最低位为1的情况。但是非规格化数是有代价的精度大幅降低而且许多硬件对非规格化数的处理非常慢因为它们无法用常规的浮点流水线处理。如果程序里大量出现非规格化数性能会产生断崖式下跌。这在数值计算领域是一个著名的性能陷阱某些数学库会专门做flush to zero处理把极小的非规格化数直接置零以换取计算速度。4.3 零、无穷大与NaN的编码规则零符号位任意指数存储值为0尾数为0。很微妙的是IEEE 754定义了0和-0两种零。虽然它们在绝大多数比较运算中相等但在某些场景下是区分的比如1除以0得正无穷1除以-0得负无穷。无穷大符号位任意指数存储值为255单精度且尾数为0。∞和-∞通过符号位区分。无穷大出现的典型场景包括浮点数溢出、非零数除以零。在协议解析中收到0x7F800000表示正无穷收到0xFF800000表示负无穷。NaNNot a Number指数存储值为255且尾数不为0。它表示这个结果无法用实数定义比如0除以0、无穷减无穷、对负数开平方。NaN还有一个特性任何涉及NaN的比较运算结果都是false包括NaN自身和NaN比较。很多人会在代码里写if (x x)来判断x是否为NaN用的就是这个特性。在协议传输的场景里NaN通常被用来表示传感器无效读数或未初始化的遥测数据。一张表总结所有特殊值指数存储值尾数含义00±00非0非规格化数1~254任意规格化数2550±∞255非0NaN含静默与信号两类5. 内存视角下的浮点数大小端与实操验证理解了二进制布局下一步就是把视角从数学上的位模式切换到内存里的实际字节流。这一步是通信协议解析和跨平台数据交换的重中之重。同一个浮点数在不同平台上的字节流可能截然不同这完全取决于CPU的大小端模式。5.1 大端存储与小端存储对同一字节流的不同解读所谓大端Big-Endian和小端Little-Endian指的是多字节数据在内存地址上的排列顺序。假设内存地址从低到高排列一个32位的整数0x12345678大端模式高字节在前地址从低到高存储的是0x12、0x34、0x56、0x78。小端模式低字节在前地址从低到高存储的是0x78、0x56、0x34、0x12。x86和ARM主流处理器默认是小端而许多网络协议比如TCP/IP规定字节序为大端。这就是为什么做通信开发时总会遇到字节序转换这件事。浮点数和整数一样受大小端影响。前面我们算出的10.25对应的字节序列是0x41、0x24、0x00、0x00在小端的x86平台上内存里的实际顺序是0x00、0x00、0x24、0x41。很多人第一次做Modbus或自定义串口协议解析时直接把收到的4个字节按顺序强转为float得到的结果完全不可理喻几乎都是大小端没对齐导致的。5.2 用C语言把float的字节流打出来看讲理论不如动手看内存。下面这段代码演示了如何用联合体union把float当作字节数组来查看这是调试时最常见的手段#include stdio.h #include stdint.h typedef union { float f; uint8_t bytes[4]; } FloatBytes; int main() { FloatBytes fb; fb.f 10.25f; printf(float value: %.2f\n, fb.f); printf(hex bytes: 0x%02X 0x%02X 0x%02X 0x%02X\n, fb.bytes[0], fb.bytes[1], fb.bytes[2], fb.bytes[3]); return 0; }在x86小端机器上运行输出会是float value: 10.25 hex bytes: 0x00 0x00 0x24 0x41注意这里先输出的是低地址字节。如果你在MARS模拟器等MIPS环境中运行同样逻辑就会看到完全不同的顺序MIPS默认是大端模式配置也可以配置为小端输出的字节流是0x41 0x24 0x00 0x00。这就是为什么在用MIPS汇编写浮点运算或字符串处理时要格外留意平台字节序。5.3 手动解析4字节为浮点数不依赖编译器的硬核做法在很多场景下你不会有一个现成的float指针可以强转。比如通过串口收到的4个字节来自一个未知端序的传感器你必须自己实现解析。这时候手动按IEEE 754规格组装浮点数是最稳妥的。下面给出一个纯逻辑解析的函数用C语言实现不依赖任何强转#include stdint.h #include stdio.h float parse_ieee754_big_endian(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) { // 按大端顺序组合成一个32位无符号整数 uint32_t bits ((uint32_t)b0 24) | ((uint32_t)b1 16) | ((uint32_t)b2 8) | ((uint32_t)b3); int sign (bits 31) 0x1; int exponent (bits 23) 0xFF; uint32_t mantissa bits 0x7FFFFF; if (exponent 0 mantissa 0) { return sign ? -0.0f : 0.0f; } if (exponent 0xFF mantissa 0) { return sign ? -__FLT_MAX__ * 2.0f : __FLT_MAX__ * 2.0f; // 简化处理为无穷大 } if (exponent 0xFF mantissa ! 0) { // NaN return 0.0f / 0.0f; } // 规格化数 float frac 1.0f; float weight 0.5f; for (int i 22; i 0; i--) { if ((mantissa i) 1) { frac weight; } weight * 0.5f; } float value frac * powf(2.0f, (float)(exponent - 127)); return sign ? -value : value; }这段代码的思路是先从字节流中拼出完整的位模式再按IEEE 754规则逐段拆解。实际工程里你也可以用memcpy或联合体来简化但这个逐位解析版本可以帮你验证自己对存储格式的理解是否正确排错时尤其有用。额外提醒一句代码里对无穷大和NaN的处理是示意性的真实项目中建议用isinf()、isnan()宏来识别避免依赖未定义行为。5.4 memcpy强转与直接强转的区别一个隐藏的别名规则陷阱在C/C里把一个float变量按位转成uint32_t最常见的方式有三种指针强转、联合体、memcpy。但指针强转的方式非常危险float f 10.25f; uint32_t bits *(uint32_t*)f; // 违反严格别名规则未定义行为这里的问题在于C语言标准规定了不同类型的指针如果指向同一块内存可能违反严格别名规则strict aliasing rule编译器在优化时可能假设不同类型的指针不会指向同一地址于是产生出乎意料的优化结果。更稳妥的做法是用memcpyfloat f 10.25f; uint32_t bits; memcpy(bits, f, sizeof(bits));或者用联合体虽然C标准对联合体的这种用法也有争议但实践中GCC和Clang都支持得很好广泛应用于各种通信协议代码中。无论用哪种方式核心目的都是把浮点数的内存位模式原样搬运到整型变量里再进行位运算和结构拆分而不是让编译器去解释一个无效的浮点数表示。这段经验同样适用于MIPS汇编在汇编层面浮点寄存器$f0系列和整数寄存器的数据要互通也需要先做bitwise move比如mfc1指令道理是相通的。6. 浮点数的工程陷阱比较、精度、打印与传输的坑存储格式的底层逻辑搞清楚之后很多工程上的玄学问题就有了明确的解释。这一节我把这些年实战中遇到最多的浮点数问题集中梳理一遍每一类都给出定位思路和解决方案。6.1 浮点数相等比较为什么不可靠最常见的坑if (a b)判断两个float是否相等结果时灵时不灵。原因就不用再重复了浮点数本身就是近似值0.1在内存里并不是精确的0.1所以两个应该相等的数经历不同运算路径后低位可能产生细微差别。工程上的标准做法是引入一个很小的阈值epsilon做范围比较#include math.h #include fenv.h int almost_equal(float a, float b, float epsilon) { return fabsf(a - b) epsilon; }epsilon取值多少需要根据数值量级来定。如果比较的值在1附近1e-6通常够用如果比较的值在1e10量级那float的绝对精度已经超过11e-6就没有意义了得用相对误差来比较fabsf(a-b) epsilon * fmaxf(fabsf(a), fabsf(b))。还要注意一个边界如果a和b都是非规格化数直接相减再取绝对值可能得到0也可能因为下溢而变得不准确此时比较结果依然不可靠。处理这种极端情况可以先把两者都转成double再比较或者引入更复杂的算法但99%的业务场景用相对误差方案就够了。6.2 为什么C里浮点数相除的余数也有坑C标准库提供了fmod()函数来计算浮点数相除的余数但很多人用的时候忽略了一个问题fmod的结果符号和被除数一致且结果精度受输入的浮点表示误差影响。比如计算三角函数周期性归约用fmod可能会因为输入大数时精度不足而产生肉眼可见的偏移。如果对精度有要求C11之后可以用std::remainder()它返回的是最接近零的余数在信号处理和计算几何中更受青睐。还有一个衍生坑当被除数很大而除数很小时浮点相除的商可能超过float的表示范围结果直接变成无穷大后续所有计算全部污染。我建议在涉及浮点除法的地方先做一个量级判断防患于未然。6.3 浮点数的打印格式化与格式化串不匹配调试时最崩溃的场景之一打印浮点数却发现输出是一个完全离谱的整数或乱码。这通常是因为printf的格式化占位符和参数类型不匹配。C语言中%f要求参数是double如果你传入一个float会发生隐式提升问题不大但如果传入一个uint32_t行为就未定义了。尤其要注意的是在跨平台代码中%f对double和float的处理以及某些嵌入式工具链如IAR、Keil对格式化输出的支持差异。有的轻量级printf实现甚至不支持%f打印浮点数会直接输出空字符串或固定字符串。遇到这种情况要么换成自定义的浮点转字符串函数要么就用联合体把float拆成4字节十六进制打印从十六进制反推存储格式是否正确。6.4 通信协议中发送浮点数的通用解决方案在Modbus、CAN、串口等通信场景中传输浮点数核心问题只有一个收发双方的字节序和解析口径必须一致。常见的做法有三种按IEEE 754原始字节传输发送方把float拆成4字节按约定的大端或小端顺序发出接收方再组装。优点是保留完整精度缺点是双方必须严格对齐标准。放大为整数传输比如保留两位小数把10.25乘以100变成整数1025后再发送。优点是传输和存储都简单直观整数比较没有精度坑缺点是范围受限、精度固定。字符串传输直接把浮点数格式化为字符串发送比如10.25。优点是便于调试和日志记录缺点是传输效率低、解析开销大。我在实际项目中如果是短距离、低速率的调试场景倾向用字符串或整数传输排障成本最低如果是高速数据采集系统则用原始IEEE 754字节流并且一定在协议文档里写明字节序和浮点格式防止后续对接的人踩坑。7. 浮点数排查实战三个真实案例复盘原理讲了这么多还是用几个真实案例来验证一下这些知识在实战中是怎么用的。7.1 案例一串口收到的浮点数变成乱码某次调试一个温湿度传感器用串口读取数据协议文档写明温度值是4字节float小端传输。我写了一段接收代码直接f *((float*)buffer);就丢给应用层结果读出来的温度是-7.8124e-18这种荒谬的值。排查过程先用逻辑分析仪确认原始字节流发现是0x8F C2 34 41。按小端组装成整数0x4134C28F再手工拆解符号位0指数0x82即130尾数0x34C28F。算出来约等于11.37℃这跟传感器旁边的温度计读数完全吻合。问题出在我的代码里buffer的定义是uint8_t buffer[4]而float*的强转在部分编译器上会因对齐问题4字节float要求4字节对齐而局部数组可能只对齐到1字节被中断读取出的数据错位。解决方案是改用memcpy从对齐问题中彻底解放出来。这个案例的核心教训是解析通信帧时永远优先用无符号整型拼装位模式再用memcpy转换成float而不是试图直接强转未对齐缓冲区的指针。7.2 案例二跨平台程序浮点数结果不一样另一个项目里同样的算法代码在Windows和某个嵌入式Linux平台上跑输出结果末尾几位总是不一样。一开始怀疑是编译器浮点优化选项不同查了半天无果。后来用联合体把两边的float字节流打出来一看发现同一个源数据经算法处理后高3字节完全一致低1字节有差异。这就是经典的浮点运算严格一致性问题不同平台的标准库数学函数比如sin、pow、sqrt实现不同最后一位舍入结果可能有差异加上部分嵌入式平台默认使用更低精度的浮点运算模式比如使用软件浮点库差异就被放大了。方案是如果跨平台结果必须逐位一致就不要依赖标准数学库需要自己实现确定性算法并且在编译时关闭快速浮点等重排优化选项比如GCC的-ffloat-store或者C的#pragma STDC FENV_ACCESS ON。7.3 案例三MIPS汇编环境下的浮点寄存器操作绑定错误有同事在MARS模拟器上写MIPS浮点程序把两个浮点数相乘结果存回内存后用整数指令打印始终是0。问题在于MIPS的浮点单元和整数单元是分离的浮点数存在协处理器的$f0~$f31寄存器中无法直接用整数寄存器访问。要把浮点数传递给整数指令或打印函数必须使用mfc1指令把浮点寄存器数据移动到整数寄存器操作的是底层位模式理解浮点数在内存中的存储规则在这时就显得格外重要。如果你在MARS里想查看一个float变量的十六进制表示可以先用sw把$f0存到内存再lw加载到整数寄存器然后以十六进制打印。这个操作背后的思路和C语言中的联合体是同一件事把浮点数当成一串比特位来观察而不是让指令集去解释它。8. 浮点数常见问题速查表为了便于日常查阅我把项目里经常碰到的浮点数存储相关问题和解决方案整理成了一张表问题现象根本原因解决方案0.10.2不等于0.3二进制无法精确表示十进制小数使用epsilon比较或改用十进制库串口读出的浮点数是乱码大小端不一致或缓冲区未对齐强转统一字节序用memcpy或联合体解析嵌入式平台浮点运算极慢出现大量非规格化数开启FTZflush-to-zero模式打印float输出为0或乱码格式化占位符与参数类型不匹配检查printf的%f/%d参数是否匹配相同代码在不同CPU结果不同数学库实现差异与浮点优化重排固定实现确定性算法关闭浮点重排优化大浮点数转小类型溢出超过目标类型可表示范围检查数值范围使用饱和转换或异常处理浮点数作为Map/字典key失效浮点数比较天然存在近似误差用整数放大或字符串作为key网络传输double多4字节发送float但接收端按double解析严格约定协议字段的位宽与格式取出NaN导致程序异常未对特殊值进行防御判断解析后用isnan/isinf做合法性检查这张表里的每一条背后都对应着一次真实的调试经历。建议各位在自己的项目里遇到浮点数相关问题时优先对照这张表定位方向能省下不少排查时间。9. 一些个人的调试经验和建议做了这么多年底层开发踩过无数浮点数的坑最后分享几条我一直在用的经验。第一所有通信协议中的浮点数一律按字节流处理绝不让编译器帮你做隐式转换。无论是Modbus、CANopen还是自定义串口协议接收端先把字节拼装成无符号整型再按IEEE 754规则解析或memcpy转换。这个习惯帮我避免了几十次莫名其妙的数据错位问题。第二只要涉及跨平台或跨语言数据交换协议里必须明确写出浮点数的格式、字节序和特殊值约定。比如用NaN表示无效数据、用±∞表示超量程这些都要写清楚否则后续维护者只能靠猜。写文档的成本远低于排查问题的成本。第三调试时用十六进制位模式作为第一证据。当浮点数结果看起来不对劲时先把数值打印成十六进制而不是盯着十进制小数看。因为十进制会掩盖二进制层面的规律十六进制能直接反映出符号位、指数位是否异常。比如一个float打印出来是-1.352e-08十六进制是0xAD14A634一眼就能看出指数段是全1问题大概率出在特殊值的编码上。第四浮点数没有银弹精度取舍是工程决策。很多业务场景里用整数加Q格式比如Q15、Q31替代浮点数会得到更稳定的行为尤其是在没有FPU的MCU上。不要为了能用浮点数而盲目使用先评估精度、范围、性能、存储空间这四个维度再选择数值表示方案。浮点数在内存中的存储这个话题说大不大说小不小但它是一道承上启下的桥梁——向上连接语言规范里的类型系统向下连接CPU指令集和内存模型。把这层窗户纸捅破之后很多原来觉得玄学的问题都会变得井然有序。希望这篇文章能帮你在调试现场少掉几根头发。
返回列表