DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

Go DivMod 算出 q=0、m=0,避免让余数覆盖除数

Go DivMod 的余数输出若覆盖除数,可能破坏后续商与余数调整。使用独立输出对象并保存输入,再核对欧几里得恒等式;正式修复版本尚未确认。

math/big.Int.DivMod 没有报错,却把负数除法算成 q=0、m=0,可以检查余数输出是否复用了除数对象。问题不只是负数的取模约定:错误结果还可能违反 x = y*q + m。

一个已确认的具体例子是 x=-5、y=3,将同一个 *big.Int 同时作为除数和余数输出:

x := big.NewInt(-5)
ym := big.NewInt(3)
q := new(big.Int)
q.DivMod(x, ym, ym)

该例在 Go 1.27.0 等版本的报告中得到 0 与 0。DivMod 使用欧几里得除法,预期商为 -2、余数为 1;用 Go 普通整数 /、% 的截断规则解释这个结果,会混淆两套约定。

受影响实现需要在初步除法后使用原除数调整负余数。它防护了商输出与除数重叠,却没有完整防护余数输出重叠;初步运算把除数的存储改成余数,后面的符号判断和加减又读到这份被改写的数据。新 Int.Divide 的舍入调整也有同类报告,不能靠换一个方法名自动消除所有别名风险。

暂时采用明确的独立输出对象,原具体报告中的这个对照能得到正确结果:

package main

import (
    "fmt"
    "math/big"
)

func main() {
    x := big.NewInt(-5)
    y := big.NewInt(3)
    q, m := new(big.Int), new(big.Int)
    q.DivMod(x, y, m)

    check := new(big.Int).Mul(y, q)
    check.Add(check, m)
    fmt.Println(q, m, check.Cmp(x)) // 预期 -2 1 0
}

业务需要最后覆盖某个输入时,可以先在独立结果里完成计算和校验,再用 Set 写回。不要先覆盖除数,再拿它验证等式;验证必须使用原输入。也不要直接复制 big.Int 结构体当成独立对象,内部字存储仍可能共享;用 new(big.Int).Set(original) 建立需要保留的值。

已核正式 Go 1.27.2 的相关代码仍不足以给出“升级后所有余数别名都安全”的保证,因此不能把一个已合入的补丁当成稳定版验收。这里的方案是按已确认对照隔离输出,正式完整修复版本仍未确认。

验收至少包含正负被除数、正负除数、大整数与原来触发的别名调用。对 DivMod 检查原值恒等式和 0 <= m < |y|;对其他舍入模式使用该 API 自己的约定。除数为零仍是另一条错误边界。

这个问题与多个 goroutine 共享可变 big.Int 也要分开。独立输出可以避开本次单调用中的覆盖路径,但不会让并发写同一个整数变得安全。若原输入需要被多个任务使用,应继续保持明确的所有权和同步。