DEBUG.WIKI
晚安云;香港 CN2 ¥15/月
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →

Canberra 距离算出负数:SciPy 的 int64 最小值溢出

SciPy canberra 在一端含 int64 最小值时可能返回负距离。先核对绝对值溢出与输入顺序,再在计算前明确浮点工作类型。

同一对向量,canberra(u, v) 算出 -1.0,反过来却是 1.0;批量 cdist() 又给出非负结果。如果一端包含 np.int64 的最小值,应先检查整数绝对值溢出,而不是把负数取绝对值当作修复。

明确故障出现在 SciPy 1.17.0、NumPy 2.3.5;SciPy 1.18.1 的正式源码仍保留相关类型处理。Canberra 距离按各分量的 abs(u-v) / (abs(u)+abs(v)) 累加,理应非负且对称。

为什么调换参数会改变结果

受影响实现让第二个向量转为 float64,却可能保留第一个向量的整数类型。int64 最小值是 -2**63,其正数 2**63 无法放进有符号 int64。因此 abs(u) 可溢出成负值,使分母变成负数;交换两端后,进入浮点转换的位置变了,表现也跟着变化。

典型触发输入如下:

import numpy as np

u = np.array([np.iinfo(np.int64).min], dtype=np.int64)
v = np.array([0], dtype=np.int64)

这与普通输入存在负分量不同。Canberra 本来就允许比较带正负号的数值;不能删除所有负数或把它们裁成零。

在 abs 和减法之前选择计算类型

对适合双精度近似的这类数值特征,可以把两端都转换成 float64 后再交给距离函数:

from scipy.spatial.distance import canberra

u_work = np.asarray(u, dtype=np.float64)
v_work = np.asarray(v, dtype=np.float64)
distance = canberra(u_work, v_work)

示例的最小值对零距离应为 1。转换必须发生在求绝对值之前;先对整数做 abs,再把已溢出的结果转成浮点,不能恢复原值。

float64 不能精确表示所有大整数,尤其相邻整数超过 2**53 后可能变得无法区分。若特征是必须保留精确差值的巨大计数或标识,先评估距离定义与数值表示,不能仅为绕开溢出就接受精度损失。标识编号本身也未必适合直接计算这种距离。

不要只交换参数,或对最终负距离套 abs()。前者依赖当前不对称实现,后者无法修复一般多分量结果和错误权重。cdist 采用另一条计算路径,结果一致可以作为对照,却不说明原来的单对接口已被修复。

验收包含对称性和边界输入

保留原输入,用双向调用检查 d(u,v) 与 d(v,u),再覆盖相同向量、全零分量及极端整型值。共同为零的分量按接口约定不增加距离,不能为了避开除零另加任意常数改变公式。若使用权重,保留原权重和验证规则。

这条问题没有必然的异常提示,已经据此生成的近邻、聚类或相似度结果,应按数据范围决定是否重算。后续升级仍要对照具体正式版本和上述边界样本;问题关闭不等于当前环境中的计算逻辑已经改变。