MathHelper

浮点近似相等(ULP 比较)

基本信息

属性 值
路径 squeek.tictooltips.helpers.MathHelper
行数 25
方法数 2(均 public static boolean)
参考来源 源码注释 :10 指向 randomascii 2012 年的浮点比较文章

两个重载

方法 参数
equals(float a, float b) (:5) 委托给下述 4 参版,固定 maxDiff = Float.MIN_NORMAL、maxUlpsDiff = 1
equals(float a, float b, float maxDiff, int maxUlpsDiff) (:12) 完整实现

算法(:12-24)

final float absDiff = Math.abs(a - b);
if (absDiff <= maxDiff) return true;              // :14 绝对容差先行

final int aSign = (int) Math.signum(a);
final int bSign = (int) Math.signum(b);
if (aSign != bSign) return false;                 // :18 符号不同直接否

final int aInt = Float.floatToRawIntBits(a);
final int bInt = Float.floatToRawIntBits(b);
int ulpsDiff = Math.abs(aInt - bInt);
return ulpsDiff <= maxUlpsDiff;                   // :23

三级判定:绝对差容差 → 符号比对 → ULP(Units in the Last Place)位差。

用 floatToRawIntBits 而非 floatToIntBits

floatToRawIntBits 不做 NaN 规范化, 所以 0.0f 与 -0.0f 的位模式不同(0x00000000 vs 0x80000000), ulpsDiff 会算出 Integer.MIN_VALUE 的绝对值(仍是很大的数)→ 判不等。 但两者符号判定(:16-18)会先返回 false,结论一致。

代价是 NaN 与任意值的 absDiff <= maxDiff 恒为 false, 符号判定 Math.signum(NaN) 返回 NaN,(int) NaN 得 0, 于是 NaN 与 0.0f 会走到 ULP 比较 → 位差很大 → 判不等。行为可接受。

默认容差

单参重载传的是 Float.MIN_NORMAL(最小的正规格化数,约 1.17549435E-38), 不是 0f、也不是 Float.MIN_VALUE(约 1.4E-45)。

所以 equals(a, b) 的实际语义是「差值小到不可能比最小正浮点还小,或相差 1 个 ULP」, 实际等价于纯 ULP 相等判定。

调用点

grep 全仓:本类在 mod 源码内无任何调用点 (equals(float, float) 与 4 参版都未被引用)。 与 RomanNumeralHelper 的 fromRoman 同属未使用的对称 API。

apiPackage 在 gradle.properties 中为空,不对外暴露, 所以这两个方法实际上是死代码。

潜在误用风险

若将来有人拿它比 handleDurability / flightSpeedMax 这类 量级差异很大的字段,ULP 判定会过于严格(要求几乎逐位相同), 而绝对容差 Float.MIN_NORMAL 对这些值形同虚设。 真正的宽松比较需要传显式的 maxDiff。