浮点数这东西,在计算机世界简直就是个“无底洞”。 一般/平平人一听只认定是数字,一懂了就发现这事儿忒玄乎了。就比方说你还在用 Excel 算账,那个格子里的东西,实际上是个“十进制”的整数要么小数。你输入 12345,它乖乖地就是个整数。但你只要改个后缀,比如写成 12345.67,要么 1.2345678,那它立马就得变身了。
这时候它就叫“浮点数”。 浮点数的名字听着挺高大上,指的就是那一局部。但计算机里到底如何存,如何算,彻底是一个个“魔法”在跑。 先说它最核心的矛盾点:精度。 在十进制里,你加加减减,误差一般挺小,直到那个小数点后最终一位启动,只要再多一点点,原来的数字就彻底变了。
你想想,2.718 开方是 1.64872127... 这一串数字能不能无限加下去,是不是一辈子加不完?但在浮点数世界里,这玩意儿有个硬性的边界。为了省内存,计算机不得不把小数点往后挪,要么直接扔掉那些不需求的尾数。
这就好比你在房间里数蚂蚁,你数到十万,突然发现后面还有无数只。
这时候,你要么数到十万,要么就数到十万加一。结局要么就变了,要么就丢了。
这就是浮点数“精度有限”的本质。 再看看它是如何存的。 实际上并不是所有浮点数都一样。最常用的是 IEEE 754 标准,这是国际大联盟定下的“规矩”。
这个规矩大约分了三种“人设”。
第一种叫单精度,也就是 32 位。你把它压缩进一个 32 位的小方块里,既省空间,又保留了大约 7 位有效数字。对于日常编程、做游戏、画贴图,这就够了。
第二种叫双精度,64 位。把方块加倍,能保留 15 到 16 位。
这时候精度就靠谱多了,别看多了几倍,但根本不会再出现这种“数数数到后面又回来”的尴尬。
第三种叫单精度浮点,是 48 位。别看我们在后端开发里用得少,但在嵌入式设备要么老式嵌入式系统里见过。 说了如此多,还是得拿个例子把这概念具象化。 咱们用 Python 算个例子吧。 ```python a = 0.1 b = 2.0 c = 0.1 + 2.0 print(f"32 位浮点数:{a}") 打印出来是 0.10000000000000001 print(f"32 位浮点数:{b}") print(f"32 位浮点数:{c}") ``` 你看,`0.1` 和 `2.0`,它们的二进制表示在电脑里是如何存的?实际上是个长长的 010...010 字符串,中间有个小数点。当你把它们加起来的时候,计算机只能在有限的位数里塞进结局。 你看第一行,`a` 打印出来是 `0.10000000000000001`。
为啥?出于它本来只是 `0.1`,但在二进制世界里,它本身就是一个无限循环小数。计算机为了存下它,只能截断掉后面那些“无限循环”的局部,多留一点点。便它就变成了那亿八千多某个数。 再看 `c`。`0.1 + 2.0` 这个式子。在二进制世界里,`2.0` 实际上是个好办的 `10.0`,后面没啥小数。而 `0.1` 后面有无数个 `1010`。你把它们拼在一起加。结局呢?依然还是那个精度不足的难题。你可能会在另一台电脑(用了不同浮点标准)看到 `0.29999999999999998` 要么 `0.30000000000000004`。
哪怕你用的是官方库里的标准库,有时候为了追求极致的科学计算效率,也会让尾数再往右移一位,害得误差略微大一点点,但反正是不变的。 这里有个挺有意思的现象。浮点数加减法,有时候听起来挺顺,但实际上可能会让数字“跳”个位置。
比如 `0.5 + 0.5` 等于 `1.0`,这在数学上是铁律。但在浮点数里,有时候 `0.1 + 0.2` 等于 `0.30000000000000004`。
为啥?出于 `0.1` 和 `0.2` 在二进制里都不是整数的,它们本身就是“带误差的数”。两个带误差的数相加,误差累积。
这就好比你拿两个已经“歪了”的尺子去量东西,最终量出来的长度肯定比实际长度长一点点。
这就是计算机浮点运算的“近似性”。 再说说它的应用。 浮点数在数据科学里是个“老伙计”。你在处理金融交易、股票分析、要么玩 3D 游戏时,显卡就连 CPU 都会把这玩意儿塞进内存。出于内存是宝贵的,不能全用高精度的整数。
比如一个 3D 模型里的顶点坐标,是整数吗?不是。它们是浮点数,能更灵活地表达形状。
比如你需求一个略微歪一点的角度,用整数存,可能得把位数往挪,效率低。用浮点数,直接存,立马搞定。 再比如图像压缩。
要是你要存一张照片,像素总共有几千万,要是全是整数,那缓存就爆表了。
这时候就得把像素值存成浮点数,略微存点误差,反正人眼看不出来。 不过,浮点数也不是完美的。 最大的敌人是“精度丢失”和“溢出”。 溢出,就是数字忒大,存不下。
比如你用 32 位浮点数存整数,范围大约是 -3.4 亿到 3.4 亿。你给它存个 40 亿,它就报错“溢出”了。
这时候该如何办?
要么换用 64 位浮点数,要么在应用程序里手动“截断”,要么用二进制补码法(Bipolar)在程序员那端做“补偿”,反正就是把范围撑大,用更宽的内存。 精度丢失,就是前面说的,误差累积。科学计算里最怕这个。
要是你做一组实验,每算一步,数值都有点绝对误差。
那最终算出来的结局,是不是就彻底不可信了?这时候就要引入“相对误差”和“绝对误差”的概念。相对误差是那个误差占原始值的百分比,这个在浮点数世界里是“硬”的,计算机内部精度是固定的,相对误差也是固定的。
故此,在科学计算里,我们要用“相对误差”来衡量质量的优劣,而不是看“绝对误差”。 还有一个好办掉进去的坑是“下溢”。 要是数字小到一定程度,计算机忘了存它,直接设为 0。
比如 `0.0000000000000000000001`,这玩意儿在双精度范围内就根本存不下了。
这时候,结局就是 0。在科学世界里,这代表啥?可能代表检测不到,也可能代表误差忒大,彻底失效了。
不过大多数应用场景,比如做表格数据,这种下溢一般不是难题。 最终总结一下,浮点数就像是计算机的“妥协方案”。它为了省空间,牺牲了精度,也牺牲了一些数学上的完美。它不是数学上完美的数字,而是工程上最实用的数字。 当你写代码,处理数据时,你要知道它不是确实那个数字。它是一个用 32 位或 64 位盒子装起来的、有误差的、能存下的、最好用的数字。懂这个,你就不会再被打印出来的怪小数给搞晕了。