C++ chrono

在 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)。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号