一、绕开二进制精度限制用工程方法解决问题既然误差是硬件底层的必然结果我们就不能改变它但可以规避它。下面是《程序是怎样运行的》中推荐的、在实际开发中最常用的 4 种解决方案方案 1避免直接用比较浮点数问题浮点数是近似值直接比较相等几乎一定会出错。解决用「差值是否小于极小阈值」来判断#include stdio.h #include math.h // 用于fabs函数求绝对值 #define EPS 1e-6 // 定义极小阈值0.000001 int main() { float a 0.1; float b 0.1; if (fabs((a b) - 0.2) EPS) { printf(近似相等\n); } else { printf(不相等\n); } return 0; }✅ 适用场景所有需要判断浮点数相等 / 大小的场景。方案 2使用高精度小数库问题float32 位、double64 位的精度依然有限金融、科学计算需要更高精度。解决使用语言自带的高精度库用字符串存储小数避免二进制转换误差Pythondecimal.DecimalJavajava.math.BigDecimalCboost::multiprecision::cpp_dec_floatPython 示例from decimal import Decimal, getcontext getcontext().prec 20 # 设置精度为20位 a Decimal(0.1) b Decimal(0.1) print(a b) # 输出0.10000000000000000000 print(a b Decimal(0.2)) # 输出True✅ 适用场景金融、会计、高精度科学计算。方案 3转换为整数运算问题整数可以用二进制精确表示没有精度误差。解决把小数放大为整数比如把 “元” 变成 “分”运算后再缩小#include stdio.h int main() { // 0.1元 10分用整数存储 int sum 0; for (int i 0; i 100; i) { sum 10; // 每次加10分即0.1元 } printf(总金额%d元%d分\n, sum / 100, sum % 100); return 0; }运行结果总金额10元0分完全精确✅ 适用场景货币计算、小数位数固定的场景。方案 4减少运算次数避免误差累积问题多次运算会让微小误差不断放大比如 0.1 累加 100 次。解决尽量合并计算用公式代替循环#include stdio.h int main() { // 直接用乘法0.1 * 100 10误差远小于循环累加 float sum 0.1 * 100; printf(%f\n, sum); // 输出10.000000近似精确 return 0; }✅ 适用场景大量重复运算的场景。核心认知这些方案的本质都是绕开 “二进制小数无法精确存储” 的硬件限制用整数 / 字符串存储 → 避免二进制转换误差用阈值比较 → 接受近似值避免绝对相等的执念减少运算次数 → 降低误差累积的概率。这正是《程序是怎样运行的》中 “程序要适配硬件特性” 的最佳实践。二、 二进制数和十六进制数为什么要学十六进制十六进制是查看内存数据的窗口—— 二进制太长十进制不直观十六进制是二进制的 “简写版”1 位十六进制 4 位二进制0xF11112 位十六进制 1 字节8 位二进制32 位浮点数 8 位十六进制二进制 ↔ 十六进制 转换规则二进制十六进制二进制十六进制00000100080001110019001021010A001131011B010041100C010151101D011061110E011171111F示例二进制1011 0011→ 十六进制0xB3 浮点数的十六进制表示我们可以用十六进制直接查看浮点数在内存中的样子以 C 语言为例#include stdio.h int main() { float a 0.1; // 把float的地址强制转为char*逐字节打印十六进制 unsigned char *p (unsigned char *)a; printf(0.1 的内存十六进制); for (int i 0; i 4; i) { printf(%02X , p[i]); } printf(\n); return 0; }运行结果小端序CD CC 4C 3E✅ 这串十六进制就是0.1在内存中的真实存储对应我们之前拆解的 IEEE 754 格式。底层逻辑十六进制是连接 “人类可读” 和 “机器存储” 的桥梁程序员可以通过十六进制直接查看内存中的浮点数、整数、字符串理解十六进制才能真正看懂《程序是怎样运行的》中 “内存布局”“数据存储” 的核心内容。三、本部分核心结论实践验证用代码可以直观看到0.1是近似值直接比较浮点数相等会出错误差规避通过「阈值比较、高精度库、整数运算、减少运算次数」可以有效避免小数运算误差底层认知十六进制是查看内存数据的工具帮助我们理解浮点数的真实存储格式最终升华计算机小数运算 “出错” 不是 bug是硬件限制的必然结果 ——理解规则才能利用规则写出可靠的程序。
3.3 在代码中验证与避免误差
一、绕开二进制精度限制用工程方法解决问题既然误差是硬件底层的必然结果我们就不能改变它但可以规避它。下面是《程序是怎样运行的》中推荐的、在实际开发中最常用的 4 种解决方案方案 1避免直接用比较浮点数问题浮点数是近似值直接比较相等几乎一定会出错。解决用「差值是否小于极小阈值」来判断#include stdio.h #include math.h // 用于fabs函数求绝对值 #define EPS 1e-6 // 定义极小阈值0.000001 int main() { float a 0.1; float b 0.1; if (fabs((a b) - 0.2) EPS) { printf(近似相等\n); } else { printf(不相等\n); } return 0; }✅ 适用场景所有需要判断浮点数相等 / 大小的场景。方案 2使用高精度小数库问题float32 位、double64 位的精度依然有限金融、科学计算需要更高精度。解决使用语言自带的高精度库用字符串存储小数避免二进制转换误差Pythondecimal.DecimalJavajava.math.BigDecimalCboost::multiprecision::cpp_dec_floatPython 示例from decimal import Decimal, getcontext getcontext().prec 20 # 设置精度为20位 a Decimal(0.1) b Decimal(0.1) print(a b) # 输出0.10000000000000000000 print(a b Decimal(0.2)) # 输出True✅ 适用场景金融、会计、高精度科学计算。方案 3转换为整数运算问题整数可以用二进制精确表示没有精度误差。解决把小数放大为整数比如把 “元” 变成 “分”运算后再缩小#include stdio.h int main() { // 0.1元 10分用整数存储 int sum 0; for (int i 0; i 100; i) { sum 10; // 每次加10分即0.1元 } printf(总金额%d元%d分\n, sum / 100, sum % 100); return 0; }运行结果总金额10元0分完全精确✅ 适用场景货币计算、小数位数固定的场景。方案 4减少运算次数避免误差累积问题多次运算会让微小误差不断放大比如 0.1 累加 100 次。解决尽量合并计算用公式代替循环#include stdio.h int main() { // 直接用乘法0.1 * 100 10误差远小于循环累加 float sum 0.1 * 100; printf(%f\n, sum); // 输出10.000000近似精确 return 0; }✅ 适用场景大量重复运算的场景。核心认知这些方案的本质都是绕开 “二进制小数无法精确存储” 的硬件限制用整数 / 字符串存储 → 避免二进制转换误差用阈值比较 → 接受近似值避免绝对相等的执念减少运算次数 → 降低误差累积的概率。这正是《程序是怎样运行的》中 “程序要适配硬件特性” 的最佳实践。二、 二进制数和十六进制数为什么要学十六进制十六进制是查看内存数据的窗口—— 二进制太长十进制不直观十六进制是二进制的 “简写版”1 位十六进制 4 位二进制0xF11112 位十六进制 1 字节8 位二进制32 位浮点数 8 位十六进制二进制 ↔ 十六进制 转换规则二进制十六进制二进制十六进制00000100080001110019001021010A001131011B010041100C010151101D011061110E011171111F示例二进制1011 0011→ 十六进制0xB3 浮点数的十六进制表示我们可以用十六进制直接查看浮点数在内存中的样子以 C 语言为例#include stdio.h int main() { float a 0.1; // 把float的地址强制转为char*逐字节打印十六进制 unsigned char *p (unsigned char *)a; printf(0.1 的内存十六进制); for (int i 0; i 4; i) { printf(%02X , p[i]); } printf(\n); return 0; }运行结果小端序CD CC 4C 3E✅ 这串十六进制就是0.1在内存中的真实存储对应我们之前拆解的 IEEE 754 格式。底层逻辑十六进制是连接 “人类可读” 和 “机器存储” 的桥梁程序员可以通过十六进制直接查看内存中的浮点数、整数、字符串理解十六进制才能真正看懂《程序是怎样运行的》中 “内存布局”“数据存储” 的核心内容。三、本部分核心结论实践验证用代码可以直观看到0.1是近似值直接比较浮点数相等会出错误差规避通过「阈值比较、高精度库、整数运算、减少运算次数」可以有效避免小数运算误差底层认知十六进制是查看内存数据的工具帮助我们理解浮点数的真实存储格式最终升华计算机小数运算 “出错” 不是 bug是硬件限制的必然结果 ——理解规则才能利用规则写出可靠的程序。