在 C++ 中,标准库的 <cmath> 头文件提供了一整套现成的数学函数。平时遇到求绝对值、次方、平方根、对数或三角函数等运算时,我们不需要自己去写循环或推导公式,引入该头文件即可直接调用。
这里列举常用的数学函数只是方便小伙伴们平时查阅,并不是让大家死记硬背,后面写代码时用上几次自然就熟了。常用的数学函数如下表所示。
| 函数 | 英文原意 | 说明 |
|---|---|---|
| std::abs(x) | absolute(绝对值) | 求 x 的绝对值,支持整数与浮点数 |
| std::pow(a, b) | power(幂 / 次方) | 计算 a 的 b 次方 |
| std::sqrt(x) | square root(平方根) | 求非负数 x 的算术平方根 |
| std::floor(x) / std::ceil(x) | floor(地板)/ ceiling(天花板) | 对浮点数 x 向下取整 / 向上取整 |
| std::round(x) | round(四舍五入) | 将浮点数 x 四舍五入到最近的整数 |
| std::exp(x) | exponential(指数) | 计算自然常数 e 的 x 次方 |
| std::log(x) | logarithm(对数) | 计算以 e 为底的自然对数 ln(x) |
| std::log10(x) / std::log2(x) | logarithm(以 10 或 2 为底) | 计算以 10 为底、以 2 为底的对数 |
| std::sin(x) / std::cos(x) / std::tan(x) | sine / cosine / tangent | 计算正弦、余弦、正切,参数 x 为弧度 |
基础数学函数
在 C++ 中,调用 <cmath> 里的函数前需要先通过 #include <cmath> 引入头文件,使用时统一加上 std:: 前缀。下面我们先通过一个简单例子,熟悉这些数学函数的基本调用方式。
示例 1:调用基础数学函数
#include <cmath>
#include <iostream>
int main() {
std::cout << std::abs(-25) << '\n'; // 求绝对值
std::cout << std::pow(2, 5) << '\n'; // 计算 2 的 5 次方
std::cout << std::sqrt(64.0) << '\n'; // 求算术平方根
std::cout << std::round(6.8) << '\n'; // 四舍五入取整
return 0;
}运行结果如下。
25
32
8
7分析:
上面这个例子依次调用了 4 个常用数学函数。其中,std::abs() 用于求绝对值。由于 C++ 支持函数重载,它既能接收 int 整数,也能直接接收 double 浮点数。
后面的 std::pow()、std::sqrt() 和 std::round() 分别用于计算次方、求算术平方根和四舍五入取整。这几个函数默认返回的都是 double 类型,只是 std::cout 在打印整数值时会自动省略末尾的 .0。
关于这几类高频运算的具体应用场景与防坑细节,我们在接下来的几节中会分别展开介绍。
指数与对数运算
自然常数 e 的值约为 2.71828。在科学计算、数据分析或计算二叉树高度时,我们经常会用到以 e 为底的指数运算以及不同底数的对数运算。
语法:
std::exp(x)
std::log(x)
std::log10(x)
std::log2(x)说明:
这 4 个函数默认都返回 double 类型,各自的作用如下:
- std::exp(x):计算自然常数 e 的
x次方,例如std::exp(1.0)的结果约为 2.71828。 - std::log(x):计算以 e 为底的自然对数,对应数学中的 ln(x),要求参数
x > 0。 - std::log10(x):计算以 10 为底的常用对数,对应数学中的 lg(x),要求参数
x > 0。 - std::log2(x):计算以 2 为底的对数,要求参数
x > 0,常用于计算二进制位数。
这里有一个初学者很容易搞混的细节:在中学数学里,单独写 “log” 通常表示以 10 为底的对数。但在 C++、Python、Java 等编程语言中,std::log(x) 表示的都是以 e 为底的自然对数。下面我们来看一个例子。
示例 2:指数与对数运算
#include <cmath>
#include <iostream>
int main() {
double e = std::exp(1.0); // 计算 e 的 1 次方
std::cout << e << '\n';
std::cout << std::log(e) << '\n'; // 以 e 为底
std::cout << std::log10(100.0) << '\n'; // 以 10 为底
std::cout << std::log2(8.0) << '\n'; // 以 2 为底
return 0;
}运行结果如下。
2.71828
1
2
3分析:
double e = std::exp(1.0); 表示计算 e 的 1 次方,也就是得到自然常数 e 本身的近似值。由于以 e 为底的指数与自然对数互为逆运算,接下来调用 std::log(e) 时结果正好还原为 1。
对于后面两行代码,因为 10 的 2 次方等于 100,所以 std::log10(100.0) 返回 2。同理,2 的 3 次方等于 8,所以 std::log2(8.0) 返回 3。
平时如果要算以 10 为底的对数,小伙伴们记得调用 std::log10(),不要误写成 std::log()。
三角函数与弧度换算
在图形绘制、游戏开发或物理模拟中,我们经常会用到正弦(sin)、余弦(cos)和正切(tan)。使用这几个函数时,小伙伴们要特别注意:C++ 三角函数接收的参数单位是 “弧度”,而不是日常习惯说的 “角度”。
语法:
std::sin(rad)
std::cos(rad)
std::tan(rad)说明:
参数 rad 表示弧度值,其中 rad 是英文 “radian(弧度)” 的缩写。如果我们手头拿到的是 30°、45°、60° 这样的角度值,需要先按数学公式 “弧度 = 角度 × π / 180.0” 换算成弧度,再传给三角函数计算。
示例 3:角度换算与三角函数
#include <cmath>
#include <iostream>
int main() {
const double PI = 3.141592653589793;
double rad30 = 30.0 * PI / 180.0; // 30 度转弧度
double rad60 = 60.0 * PI / 180.0; // 60 度转弧度
double rad45 = 45.0 * PI / 180.0; // 45 度转弧度
std::cout << std::sin(rad30) << '\n';
std::cout << std::cos(rad60) << '\n';
std::cout << std::tan(rad45) << '\n';
return 0;
}运行结果如下。
0.5
0.5
1分析:
在这个例子中,我们先通过 30.0 * PI / 180.0 等算式将角度换算为弧度,再传给 std::sin()、std::cos() 和 std::tan() 计算,得到的 0.5、0.5 和 1 与数学中的特殊角三角函数值一致。
需要特别注意的是,千万不要把角度数值直接传进三角函数里。下面这种写法就是错误的,程序会把 30.0 当成 “30 弧度”(约 1718.87°)去计算,算出来的结果是 -0.988032 而不是 0.5。
double wrongVal = std::sin(30.0); // 误把角度直接传入另外,圆周率 PI 在计算机中只能保存有限位的近似值。当我们计算 cos(90°) 或 sin(180°) 这类理论上等于 0 的结果时,可能会输出 6.12323e-17 这样极接近 0 的微小小数,这属于正常的浮点数精度现象。
提示 从 C++20 标准开始,<numbers> 头文件直接提供了官方圆周率常量 std::numbers::pi。在实际开发中引入该头文件即可直接使用,不用自己手敲一长串小数。
特殊浮点值:NaN 与 inf
在使用数学函数时,并不是传入任意数字都能算出有意义的实数。比如在实数范围内,平方根要求被开方数大于或等于 0,而对数要求传入的参数必须大于 0。
一旦传入了超出有效范围的数值,比如对负数开平方或对 0 求对数,C++ 浮点数运算并不会让程序崩溃退出,而是会返回特殊的浮点值标记。下面我们来看一个例子。
示例 4:特殊浮点值与检测函数
#include <cmath>
#include <iostream>
int main() {
double badSqrt = std::sqrt(-4.0);
double badLog = std::log(0.0);
std::cout << badSqrt << '\n';
std::cout << badLog << '\n';
std::cout << "是否为 NaN:" << std::isnan(badSqrt) << '\n';
std::cout << "是否为无穷大:" << std::isinf(badLog) << '\n';
return 0;
}运行结果如下。
-nan
-inf
是否为 NaN:1
是否为无穷大:1分析:
从运行结果可以看出,当参数超出有效范围时,程序分别产生了以下 2 种特殊浮点值:
- NaN(非数):是英文 “Not a Number” 的缩写,表示 “不是一个有效数字”,在不同编译器下会输出为
nan或-nan。对负数开平方或计算0.0 / 0.0时都会产生该值,我们可以使用std::isnan()函数来检测。 - inf(无穷大):是英文 “infinity” 的缩写,分为正无穷
inf和负无穷-inf。像std::log(0.0)趋向负无穷大,或者正数除以0.0时就会产生该值,我们可以使用std::isinf()函数来检测。
检测函数返回的布尔值 true 在控制台中默认打印为 1。这里要特别留意 NaN 的一个特殊规则:它与任何值用 == 比较,结果永远是 false,哪怕写成 badSqrt == badSqrt 和它自己比也是 false。
因此想要判断计算结果是否为 NaN,我们必须调用 std::isnan() 函数,不能使用 == 来判断。
注意 一旦某个浮点变量变成了 NaN,后续任何与它进行的加减乘除运算都会继续得到 NaN。当数据来自外部输入时,我们应该先检查参数范围,比如确认 x >= 0 后再调用 std::sqrt(x)。
