Long.MaxValue转Single再转回Long触发OverflowException的原因探究
Long.MaxValue 的值为 9,223,372,036,854,775,807。将其转换为单精度(Single)值后变为 9.223372E18,但转换回 Long 时会抛出 System.OverflowException——尽管这个 Single 值看起来小于 Long 的最大值。
通过监视窗口实验发现:
- 数值
8.070450532247929344e18是可通过CType安全转换为 Long 的最大双精度(Double)值,可在监视窗口执行以下语句测试:CType(8.070450532247929344000001e18, Long) - 数值
8.070450807125836288e18是先转换为 Single 后,可安全转换回 Long 的最大 Double 值,可在监视窗口执行以下语句测试:CType(CType(8.0704508071258362880000001e18, Single), Long)
上述反常行为的核心原因是浮点数的存储精度限制:
Single 类型的精度瓶颈
Single 是 32 位浮点数,仅含 23 位有效尾数(加隐含的 1 位共 24 位),仅能精确表示2^24(即 16777216)以内的整数。超过该范围后,Single 无法精准表示每一个整数,而是以固定间隔的“阶梯值”近似存储。Long.MaxValue转换为 Single 时会被近似为9.223372E18,但该浮点值对应一个整数区间,转换回 Long 时区间上限会超过Long.MaxValue,触发溢出异常。Double 转 Long 的精度边界
Double 是 64 位浮点数,有 52 位有效尾数(加隐含位共 53 位),仅能精确表示2^53(约 9e15)以内的整数。超过该范围后,相邻可表示的浮点值间隔大于 1,8.070450532247929344e18是刚好小于2^63(Long 最大值为2^63-1)且能被 Double 精确表示的最大整数边界,超过它的话,Double 的间隔会跨越Long.MaxValue,转换时就会溢出。Single 往返转换的额外精度损失
Double 先转 Single 再转 Long 会经历两次精度损失:第一次是 Double 转 Single 时的近似,第二次是 Single 转 Long 时的区间判断。8.070450807125836288e18是经过两次近似后仍能落在 Long 有效范围内的最大 Double 值,超过它的话,Single 近似后的区间会触及 Long 的上限。
补充实验
上述实验基于监视窗口和 CType,另一段实验代码得出了不同结果:
Dim d As Long = 200000000000 Dim delta As Long = d / 2 Dim c As Long Dim a As Single Dim b As Long While delta > 0 c = Long.MaxValue - d a = c Try b = a delta = delta \ 2 d = d - delta Catch ex As Exception d = d + delta End Try End While
该代码得出的可安全往返转换的最大 Long 值为:
9,223,371,761,976,868,860
即:
Long.MaxValue - 274,877,906,947
这个结果差异是因为代码采用二分法寻找可安全往返的最大 Long 值,直接针对「Long → Single → Long」的往返过程验证,而之前的监视窗口实验针对的是「Double → 目标类型」的单向/二次转换,测试场景不同,因此结果有区别。
内容的提问来源于stack exchange,提问作者J Collins

