1. 从一次“地图乱飞”的Bug说起:NaN与INF是什么?
上周我差点被一个地图相关的Bug搞崩溃。项目里有个功能,用户点击地图上的一个点,应该弹出一个信息框。但测试的时候,经常一点击,地图视角就“嗖”地一下飞到世界边缘,比如南太平洋的某个莫名其妙的位置,或者直接卡死。那感觉就像你明明想开家里的灯,结果把整个小区的电闸给拉了。
当时用的Qt版本比较老,调试起来特别费劲。我一开始以为是坐标转换的逻辑错了,或者是事件处理的问题,对着地图渲染和鼠标事件代码查了两天,头发都薅掉不少。最后,还是用最原始的“打印大法”,在关键路径上把经纬度坐标的double值输出来看,才发现问题所在。有一个计算中间结果的变量,在某些极端情况下(比如用户快速拖拽后点击),竟然变成了一个叫nan的玩意儿。当这个nan值被传递给后续的坐标变换函数时,整个计算链条就全乱套了,最终导致渲染位置完全错误。
这个nan,还有它的“好兄弟”inf,就是咱们今天要聊的主角。它们不是普通的数字,而是浮点数世界里的两个“特殊居民”,专门用来表示计算中出现的异常情况。你可以把它们理解成程序世界里的“红灯”或“警报器”。
- NaN (Not a Number):翻译过来是“非数”。它表示一个无效的、未定义的或者无意义的数学运算结果。比如,你对一个负数开平方根(在实数范围内),或者用0除以0,就会得到
nan。一旦一个计算中混入了nan,那么整个表达式的结果通常也会被“污染”成nan,就像我遇到的那个Bug一样,一个nan让整个坐标体系崩溃。 - INF (Infinity):表示“无穷大”。它通常出现在结果超出了浮点数能表示的最大范围时,比如用一个非常大的数除以一个非常接近0的数。它分为正无穷(
+inf)和负无穷(-inf)。+inf在概念上比任何有限数都大(除了它自己和nan),-inf则比任何有限数都小。
为什么整型(int)计算很少遇到这种问题呢?因为整型运算遇到类似除零这种错误时,处理方式更“粗暴”——程序通常会直接触发一个硬件中断,导致崩溃(比如著名的“浮点例外”或“除零异常”)。而浮点数标准(IEEE 754)的设计者则更“优雅”一些,他们引入了nan和inf这两个特殊值,让程序在遇到异常时可以不立即崩溃,而是给出一个明确的“标志”,让开发者有机会去检测和处理。这既是优点(增强了鲁棒性),也是缺点(错误容易隐藏和传播)。对于刚接触数值计算或者从整型思维转换过来的朋友,很容易在这里踩坑。
2. 追根溯源:NaN与INF是如何产生的?
知道了它们是什么,我们更得知道它们从哪儿来。这样才能在写代码时提前规避,或者在调试时快速定位。绝大部分现代计算机和编程语言(包括C/C++)都遵循IEEE 754浮点数标准,nan和inf的行为也是这个标准定义的。
2.1 产生NaN的常见操作
nan的产生,核心在于进行了“在数学上无定义”或“结果无法用实数表示”的运算。下面这些操作是“生产”nan的“高危车间”:
- 对负数进行开平方根运算:
sqrt(-1.0)。在实数范围内,没有一个数的平方等于负数,所以结果是nan。 - 0.0除以0.0:
0.0 / 0.0。0除以多少是0?无穷大除以多少是无穷大?这个操作在数学上本身就是不确定的,所以结果是nan。 - 无穷大减无穷大:
inf - inf。两个无穷大相减,谁大谁小?不确定,所以是nan。 - 无穷大乘零:
inf * 0.0。无穷大乘以一个无限趋近于0的数,结果可能是0,也可能是无穷大,也可能是一个常数,数学上称为“未定式”,计算中产生nan。 - 对负数取对数:
log(-1.0)。实数域的对数函数定义域是正数。 - 某些数学函数的非法参数:比如
asin(2.0)(反正弦函数的参数必须在[-1,1]之间)。 - 未初始化的浮点数变量:在某些编译器或运行时环境下,未初始化的浮点变量可能被填充为
nan,这是一个非常隐蔽的错误来源。 - 数据类型转换错误:就像我开篇遇到的Bug,将一个超出
float表示范围的double值(可能已经是nan或非常大的数)强制转换或赋值给<

401

被折叠的 条评论
为什么被折叠?



