1149 字
6 分钟
浮点数输入与 sqrt 底层实现详解
浮点数输入与 sqrt 底层实现详解
概述
正确处理带小数点的标准输入流读取,并深入理解平方根函数(sqrt)的多种底层算法实现原理。
基本用法
头文件:<cmath> 与 <iostream>
double sqrt( double arg ); // (C++98)arg: 待计算平方根的非负实数。- 当传递的数为负数时,返回
NaN。现代 C++ 标准库的sqrt一般直接调用 CPU 硬件指令(如 x86 的sqrtsd或FSQRT),或使用极度优化的软件实现。
进阶用法与多角度扩展
在无法直接调用硬件指令或标准库的特殊场景下,手写平方根算法是经典的算法考题。以下提供三种截然不同视角的实现方案:
1. 牛顿迭代法(Newton-Raphson)
原理:求 的根,牛顿迭代公式为 。
特点:极快,具有平方收敛性(每次迭代有效位数翻倍),通常 5~6 次即可达到 double 的满精度(相对误差 )。通用推荐。
#include <cmath>
double mySqrtNewton(double S) { if (S < 0) return NAN; if (S == 0) return 0.0;
double x = S; // 初始猜测值 double prev; const double eps = 1e-15; // 精度控制
do { prev = x; x = 0.5 * (x + S / x); } while (std::abs(x - prev) > eps);
return x;}2. 二分查找法
原理:利用平方根函数的单调性,在区间 (若 )或 (若 )上进行二分逼近。 特点:简单但稍慢。需要大约 次迭代。若 且精度 ,大约需要 50 步以上。适合学习算法或无浮点指令的低配嵌入式环境。
double mySqrtBinary(double S) { if (S < 0) return NAN; if (S == 0) return 0.0;
double left = (S >= 1.0) ? 0.0 : S; double right = (S >= 1.0) ? S : 1.0;
const double eps = 1e-15; double mid; while (right - left > eps) { mid = (left + right) / 2; if (mid * mid > S) right = mid; else left = mid; } return (left + right) / 2;}3. 快速平方根倒数(Quake 3 经典算法)
原理:针对 float 单精度的著名技巧。利用 IEEE 754 浮点数表示法的位级 Hack 操作获得极快的初始猜测值,再配合一次牛顿迭代进行修正。
特点:极致的“魔法”。仅作趣味展示,现代硬件下已无必要,不推荐用于通用代码。
float Q_rsqrt(float number) { long i; float x2, y; const float threehalfs = 1.5F;
x2 = number * 0.5F; y = number; i = *(long*)&y; // 邪恶的浮点位级 hacking i = 0x5f3759df - (i >> 1); // 神奇常数 y = *(float*)&i; y = y * (threehalfs - (x2 * y * y)); // 1次牛顿迭代 return y; // 平方根倒数,所以 sqrt(x) = 1 / Q_rsqrt(x)}示例:距离计算中的类型陷阱
#include <iostream>#include <iomanip>#include <cmath>
// 使用 sqrt 替代 pow(..., 0.5),更高效且意图清晰double getDis(double x1, double y1, double x2, double y2) { double dx = x1 - x2; double dy = y1 - y2; return std::sqrt(dx * dx + dy * dy);}
int main() { // 正确:使用 double 声明坐标以接收带小数的输入 double x1 = 4.123, y1 = 5.123; double x2 = 3.123, y2 = 1.123;
double dist = getDis(x1, y1, x2, y2); std::cout << std::fixed << std::setprecision(2) << dist << "\n";}常见问题
- 用 int 读取带小数的数据引发流崩溃
如果在标准输入流中遇到带小数点的数字(如
4.123),用int接收会导致只有整数部分4被读取,剩余的小数部分.123会留在输入流中。当后续变量试图读取数字时,由于遇到.而失败,引发流状态错误和未定义行为(即俗称的“三位小数就会报错”)。
// 错误:输入 "4.123 5.123",x 读到 4,y 试图读 ".123" 失败,保持未初始化int x, y;std::cin >> x >> y;
// 正确:使用 double 声明坐标double dx, dy;std::cin >> dx >> dy;- 比较两个 double 变量是否相等
因为双精度浮点数(约 15~17 位有效十进制数字)的截断误差,相等比较不能直接使用
==。
// 错误:直接比较可能因极其微小的误差而返回 falseif (a == b) {}
// 正确:判断差值的绝对值是否小于指定精度阈值(如 1e-9)if (std::abs(a - b) < 1e-9) {}复杂度
std::sqrt硬件级指令:通常仅需几个时钟周期,O(1)。- 牛顿迭代:约 5~6 次循环运算。
关联知识点
std::pow:计算任意幂次。由于涉及对数和指数运算,其性能往往劣于直接调用sqrt或通过乘法展开。
赞助支持
如果这篇文章对你有帮助,欢迎赞助支持!
浮点数输入与 sqrt 底层实现详解
https://whywood.cn/posts/2026-4-20/sqrt/ 最后更新于 2026-04-21