直接比较sqrt(n)与取整值易错,因double精度不足(仅精确到2^53≈9e15),大数如9999999999999999会误判;正确做法是取整后检查r−1、r、r+1三个候选值平方是否等于n。

用 sqrt 函数判断时为什么常出错?
直接调用 sqrt(来自 <cmath></cmath>)得到浮点结果再平方验证,看似合理,但极易因浮点精度误差失败。比如对大整数 999999999999999999,sqrt 返回值可能略小于真实根,取整后平方就比原数小 1。
- 必须先将
sqrt结果四舍五入或向零截断后再转为整型,不能直接static_cast<long long>(sqrt(x))</long> - 对负数要提前返回
false,sqrt对负输入返回NaN或未定义行为 -
sqrt对long long超出double精确表示范围(约 2⁵³)的数完全不可靠
整数二分法最稳妥,但要注意边界
在 [0, x] 区间内二分查找是否存在整数 m 满足 m * m == x,全程使用整数运算,无精度问题。关键在于右边界设置和乘法溢出防护。
- 右边界不能设为
x(当x > 1时,平方根一定 ≤ x/2),更安全设为x / 2 + 1或min(x, 1LL 避免过大区间 -
m * m可能溢出,比较前应先判断m > sqrt_max(如sqrt_max = numeric_limits<long long>::max() / m</long>),或改用m 做粗筛 - 循环条件用
left ,找到即返回,找不到则结束
牛顿迭代法收敛快,但需处理收敛震荡
对非负整数 x,用迭代公式 next = (cur + x / cur) / 2 求平方根整数解,比二分更快,但整数除法会导致迭代在两个相邻整数间震荡。
- 初始值设为
x或x / 2 + 1,避免为 0 - 必须监控迭代是否进入
{a, b}循环(如a → b → a),一旦发生,检查a*a、b*b是否等于x - 当
next >= cur时停止(说明已收敛或开始上漂),此时只需验证cur*cur == x或(cur-1)*(cur-1) == x
小范围查表或位运算优化只适合特定场景
若只判断 32 位以内正整数,可预计算所有完全平方数存入 unordered_set,O(1) 查询;或利用模 64 的余数快速排除 75% 非平方数(因为平方数 mod 64 只有 12 种可能余数)。
- 查表法内存开销约 65536 个
int,适合高频调用且值域可控 - 模运算法写成
if ((x & 63) != 0 && (x & 63) != 1 && ...),能立刻 return false,但只是预筛,仍需后续精确验证 - 注意:模筛选不能替代主逻辑,仅作加速,漏判会导致错误
真正棘手的是大整数(如 uint64_t 接近 2⁶⁴−1)——此时连 sqrt 和普通二分都要小心溢出与边界,牛顿法也得加收敛保护。别图省事跳过溢出检查,那才是最容易翻车的地方。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











