std::midpoint是c++20唯一标准保证不触发有符号整数溢出的中点计算工具,其通过a + (b - a) / 2避免溢出,仅对同类型整型和同数组内原生指针真正安全,浮点数不保精度也不防溢出。

std::midpoint 是 C++20 唯一被标准保证「不触发有符号整数溢出」的中点计算工具,但仅对整型和指针真正安全;对 float/double 它不防溢出、不保精度,甚至可能更差。
std::midpoint 为什么能绕过整数溢出
它不依赖 a + b,而是等价于 a + (b - a) / 2(对有符号整型且 b >= a 时完全定义)。差值 b - a 永远不会比 a 或 b 的绝对值更大,因此不会溢出。例如:std::midpoint(INT_MAX, INT_MIN) 返回 INT_MIN / 2,而 (INT_MAX + INT_MIN) / 2 是未定义行为。
底层实现可能用位运算优化(如对齐处理),但语义上你只需记住:只要类型一致、同为有符号整型,且你接受向下取整(floor((a+b)/2))的结果,它就比手写加法除以 2 更可靠。
哪些类型能用,哪些会编译失败
必须满足三要素:同类型、可比较、支持 operator- 和 operator+(对指针还要求同数组内可达)。
-
std::midpoint(10, 20)✅ int 与 int -
std::midpoint(ptr_a, ptr_b)✅ 同类型原生指针(如int*) -
std::midpoint(10U, 20U)✅ unsigned int 与 unsigned int(减法自动回绕,语义是模算术中点) -
std::midpoint(10, 20L)❌ 编译失败:int 与 long 不匹配 -
std::midpoint(1.5f, 2.5f)❌ C++20 标准未定义浮点重载(某些库可能提供,但非标准行为) -
std::midpoint(v.begin(), v.end())❌std::vector::iterator不是原生指针,需转成&v[0]或用std::data(v)
指针中点是它唯一不可替代的场景
这是 std::midpoint 最硬核、最无替代方案的用途:计算大数组边界指针的中间地址,且严格规避 end - begin 超出 ptrdiff_t 范围导致的未定义行为。
例如:
int arr[SIZE_MAX / sizeof(int) + 1]; int* begin = arr; int* end = arr + SIZE_MAX / sizeof(int); // end 地址接近最大值 auto mid = std::midpoint(begin, end); // 安全,标准强制要求实现避免指针算术溢出
而手写 begin + (end - begin) / 2 理论上可行,但若 end - begin > PTRDIFF_MAX,该减法本身已是未定义行为——std::midpoint 就是为此而生。
注意:begin 和 end 必须指向同一数组(或 one-past-the-end),否则行为未定义;也不支持 void* 或 const 修饰不一致的混用(如 int* 与 const int*)。
浮点数别乱用,它不解决你的精度焦虑
对 float 或 double,std::midpoint 既不防溢出,也不提升精度。标准只要求它“存在重载”,没规定算法细节。实际中,多数实现就是调用 (a + b) / 2,在 a = 1e30、b = 1.0 时仍会丢失 b 的贡献。
真要稳健插值,优先考虑:
-
std::fma(a, 0.5, b * 0.5)(需#include <cmath></cmath>),利用融合乘加减少舍入误差 - 手动分支判断量级:
if (std::abs(a) >= std::abs(b)) return a + (b - a) * 0.5; else return b + (a - b) * 0.5; - 直接用
std::lerp(a, b, 0.5)—— 它才是为浮点稳定性设计的,且对t == 0.5语义明确
真正容易被忽略的是:它只管“两个合法值之间的中点怎么算安全”,不管输入是否有效、区间是否为空、业务逻辑是否需要校验边界。你传了 nullptr 或非法指针,它照样编译通过、运行崩溃。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











