NumPy einsum 开启优化后,safe casting 仍把小数写成整数
部分 einsum 优化路径恢复形状后以普通赋值写回 out,绕过安全转换。把独立计算与显式受约束写回分开,拒绝静默截断。
同一个 np.einsum(),关闭优化会拒绝把浮点结果写入整数 out,开启 optimize=True 却悄悄截掉小数。即使已经指定 casting="safe",也要核对结果是否经过了普通数组赋值。
NumPy 2.5.4 的部分优化路径将运算转为矩阵乘法,随后为了恢复输出轴顺序或形状,用 out[...] = ab 写回目标。这个赋值没有沿用调用方的安全转换要求,因此某些下标表达式与输出布局会表现不同;不能推定所有开启优化的 einsum 都会出错。
计算与受约束的写回分开
目标数组必须保留整数 dtype 时,不应把小数截断当作可接受结果。可先让 einsum 生成独立结果,再在写回边界明确验证转换规则:
import numpy as np
a = np.array([[0.25, 1.5]], dtype=np.float64)
b = np.array([[2.0], [3.0]], dtype=np.float64)
out = np.zeros((1, 1), dtype=np.int64)
result = np.einsum("ij,jk->ik", a, b, optimize=True)
if result.shape != out.shape:
raise ValueError("output shape differs")
if not np.can_cast(result.dtype, out.dtype, casting="safe"):
raise TypeError("unsafe result dtype conversion")
np.copyto(out, result, casting="safe")
在这组类型下,代码应在写入前拒绝转换,整数目标仍保留原值。若业务本来需要浮点输出,就应在调用前分配正确的浮点目标,而不是用宽松 casting 消掉错误。
safe 主要按 dtype 转换能力判断,不会因为某个浮点结果恰好是 2.0,就自动认为转换成整数符合原契约。确实需要舍入或整数化时,应单独定义舍入方式、范围和非法值处理,不能悄悄依赖数组赋值的截断。
保留原下标式和批次含义
把 einsum 随手改成 dot(),可能改变收缩轴、批次广播或输出顺序。先保持原来的下标表达式,只分离结果与写回,更容易核对这次修改的范围。复杂表达式本身的输入 dtype 选择和中间精度也要另外确认;安全写回不能恢复已经在计算中丢失的精度。
截至 2026 年 10 月 26 日,2.5.4 的该写回分支仍存在。验收覆盖原下标式、真实 out 布局、优化开关与代表性的非整数值,确认不允许的转换会在目标被修改前失败;单纯比较两边打印出的整数,可能漏掉已经发生的截断。


