![]()
「0.1加0.2等于0.30000000000000004。」这不是某个实习生写的烂代码,是IEEE 754浮点标准(二进制浮点数算术标准)在JavaScript、Python、Java里跑了半个世纪的"正确"结果。
一位谷歌工程师最近在技术博客复盘分数运算时,把这个问题重新拽回开发者视野。他说得很直接:浮点数的精度陷阱,本质上是我们用十进制的大脑去猜二进制的心思,猜错了。
1960年代的遗产:为什么0.1在电脑里是无限循环小数
故事要从1960年代说起。当时计算机科学家面临一个选择:用二进制表示小数,还是用十进制?二进制赢了,因为硬件实现更快、更省硅片。
但代价是隐形的。十进制的0.1,换成二进制是0.0001100110011...无限循环。IEEE 754标准只能存64位,剩下的尾巴被砍掉。单次误差小到10^-16级别,但叠加几千次,财务系统里的 penny 就开始莫名其妙消失。
2008年,欧洲航天局阿丽亚娜5号火箭发射37秒后自毁,2亿美元炸成烟花。事后调查发现,一个64位浮点数被塞进16位整数,溢出报错。导航系统判定"出错了",触发自毁程序。这不是分数运算的锅,但属于同一类问题:人类以为"差不多就行",机器较真起来要命。
分数运算的古老规则,恰恰能避开这个坑。1/3加1/3加1/3,浮点数给你0.999999...,分数直接等于1。没有近似,没有累积误差。
分数运算的三条铁律:为什么通分比乘法更烧脑
加减法最折腾。3/7加5/11,你不能直接分子加分子、分母加分母。必须先找公分母,把两块不同大小的"披萨切片"切成同样大小。
最笨的办法:7乘11得77,分子变成33加35,结果68/77。但4/6加1/4,分母直接相乘得24,其实最小公倍数(LCD)是12。用LCD计算,中间数字更小,溢出风险更低。
乘法反而最简单。分子乘分子,分母乘分母,3/7乘5/11直接得15/77。除法就是乘以倒数,把第二个分数上下翻转。
![]()
每次运算完必须约分。24/36的最大公约数(GCD)是12,约成2/3。谷歌工程师在博客里贴了一段欧几里得算法的JavaScript实现,十行代码,跑了两千年。
带分数是人类的妥协,假分数是机器的母语。2又3/4写成11/4,运算时少一层转换。输出时再换回带分数,用户看着舒服。
现代编程语言的分裂:谁还在用分数算钱
Python有fractions模块,Java有BigDecimal,C#用decimal类型。这些都不是默认选项,得开发者主动想起来用。
金融领域早就学乖了。彭博终端、交易所核心系统,要么用定点数(fixed-point),要么直接用整数存"分"而不是"元"。1.23美元存成123,彻底避开浮点。
但科学计算和图形渲染不一样。物理模拟要速度,GPU用FP16、FP32满天飞。误差?每帧重新算,上一帧的偏差不累积。游戏画面抖动一帧,玩家看不出来;银行账户少一分钱,客服电话被打爆。
谷歌工程师的观察是:分数运算在"需要精确"和"需要速度"的夹缝里存活。有理数库(rational number library)存在,但调用成本比原生浮点高一个数量级。大多数开发者选择"先跑起来,精度问题以后再说"——然后永远没以后。
一个木工师傅的启示:为什么手工测量比CAD更准
文章结尾,作者讲了一个场景。木工做柜子,CAD图纸标了0.3333英尺,师傅直接拿尺子找1/3标记。十进制是给人看的,分数是给活干的。
编程同理。0.1+0.2的笑话流传二十年,不是没人想修,是修的成本比忍的成本更高。IEEE 754已成硬件标准,全世界CPU都认。要改?等于让所有人换一把尺子。
但分数运算的知识没有过时。2023年Stack Overflow调研显示,"浮点数精度问题"仍是开发者搜索的前50话题之一,年搜索量超47万次。每个搜这个问题的人,都在重新发现1960年代那个被砍掉的无限循环尾巴。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.