std::midpoint是C++20唯一标准保证不触发有符号整数溢出的中点计算接口,但仅对同类型整型和同数组指针真正安全;浮点数上不防溢出、不提精度,反而可能掩盖问题;需启用C++20、包含、参数类型严格一致。

直接用 (a + b) / 2 算离散坐标中点,在 a 和 b 接近整型极限时大概率触发有符号整数溢出(UB),结果不可预测;std::midpoint 是 C++20 唯一被标准保证「不溢出」的中点计算接口,但只在整型和指针上真正生效,浮点数上它不防溢出、不提精度,还可能更糟。
std::midpoint(int, int) 必须满足的编译与类型前提
它不是“写了就能用”的函数,编译器和参数类型稍有偏差就直接报错或静默失效:
- 必须启用 C++20:GCC/Clang 加
-std=c++20,MSVC 加/std:c++20;不加则std::midpoint不可见 - 头文件是
,不是或——错包含会编译失败 - 两个参数类型必须完全一致:
std::midpoint(1, 1U)、std::midpoint(1L, 1)、std::midpoint(short{1}, int{2})全部编译失败 - 不接受表达式作为参数:如
std::midpoint(x, y + 1)中若y + 1已溢出,std::midpoint不兜底,它只保自身内部运算安全
std::midpoint(T*, T*) 是大数组指针中点唯一安全写法
这是它最不可替代的用途——当数组极大(比如物理引擎中接近 SIZE_MAX 的刚体池),手写 begin + (end - begin) / 2 中的减法本身可能超出 ptrdiff_t 范围,导致未定义行为;std::midpoint 被标准强制要求规避该问题:
- 两个指针必须指向同一数组(含 one-past-the-end):
std::midpoint(&v[0], &v[0] + v.size())合法,std::midpoint(v.data(), u.data())(不同容器)未定义 - 必须是原生指针:
std::midpoint(v.begin(), v.end())编译失败;得转成std::midpoint(std::data(v), std::data(v) + v.size()) - const 修饰必须匹配:
const int*和int*混用会编译失败,这不是缺陷,是帮你提前暴露 const 正确性问题 - 空容器必须提前判断:
v.empty()为真时,连&v[0]都非法,std::midpoint不检查这个
无符号坐标(size_t / uint32_t)要用但得懂模语义
std::midpoint 支持 size_t、uint32_t 等无符号类型,但它算的不是线性中点,而是模算术意义下的中点——减法会回绕(wrap around):
-
std::midpoint(1U, 0U)返回0U(因为(1U - 0U) / 2 == 0,再加0U),这符合无符号除法规则,但可能违背你对“中点”的直觉 - 若业务逻辑依赖严格线性顺序(如断言区间非递减),不能直接套用;需确认是否允许回绕,否则应手动实现:
a + (b > a ? (b - a) / 2 : 0) - 跨类型转换要显式:
int坐标转size_t前必须确认非负,用static_cast,别靠隐式转换(x) - 结果向下取整:
std::midpoint(-3, 2)返回-1,不是0;这对格点对齐、碰撞盒判定是关键语义,别当成四舍五入
float/double 坐标千万别用 std::midpoint
它对浮点数只是语法合法,毫无安全增益,反而掩盖精度问题:
-
std::midpoint(1e30f, 1.0f)底层仍是(a + b) / 2,1e30f + 1.0f == 1e30f,结果丢失全部低位;而手写a + (b - a) * 0.5f至少保留相对误差可控 - 没有 NaN/inf 特殊处理,异常值照常传播;某帧位置因数值不稳定产生
inf,std::midpoint仍返回inf - 真需要稳健浮点中点,优先用
std::fma(a, 0.5f, b * 0.5f)(需),或按量级分支:std::abs(a) >= std::abs(b) ? a + (b - a) * 0.5f : b + (a - b) * 0.5f - C++20 标准未定义浮点重载的防溢出行为,部分编译器允许只是兼容性妥协,不是设计意图
最容易被忽略的是:它只解决「算术中间步骤不溢出」这一个点,不管输入是否有效、区间是否为空、坐标是否越界、物理语义是否合理——这些全得你兜底。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











