C++ 取余和指数运算完全指南:模运算和整数幂运算详解

C++取余和指数运算详解

取余运算符 (operator%)

取余运算符 (通常也称为模运算符或模运算操作符) 是一种在进行整数除法后返回余数的运算符。例如,7 / 4 = 1 余数为 3。因此,7 % 4 = 3。另一个例子,25 / 7 = 3 余数为 4,因此 25 % 7 = 4。取余运算符仅适用于整数操作数。

这对于测试一个数是否能被另一个数整除(即除法后没有余数)非常有用:如果 x % y 的结果为 0,那么我们就知道 x 能被 y 整除。

#include <iostream>

int main()
{
	std::cout << "Enter an integer: ";
	int x{};
	std::cin >> x;

	std::cout << "Enter another integer: ";
	int y{};
	std::cin >> y;

	std::cout << "The remainder is: " << x % y << '\n';

	if ((x % y) == 0)
		std::cout << x << " is evenly divisible by " << y << '\n';
	else
		std::cout << x << " is not evenly divisible by " << y << '\n';

	return 0;
}

以下是该程序的两次运行示例:

Enter an integer: 6
Enter another integer: 3
The remainder is: 0
6 is evenly divisible by 3
Enter an integer: 6
Enter another integer: 4
The remainder is: 2
6 is not evenly divisible by 4

现在让我们尝试一个第二个数比第一个数大的例子:

Enter an integer: 2
Enter another integer: 4
The remainder is: 2
2 is not evenly divisible by 4

余数为 2 可能一开始不太直观,但很简单:2 / 40(使用整数除法),余数为 2。每当第二个数大于第一个数时,第二个数能整除第一个数 0 次,因此第一个数就是余数。

带负数的取余运算

取余运算符也可以用于负数操作数。x % y 的结果总是带有 x 的符号。

运行上面的程序:

Enter an integer: -6
Enter another integer: 4
The remainder is: -2
-6 is not evenly divisible by 4
Enter an integer: 6
Enter another integer: -4
The remainder is: 2
6 is not evenly divisible by -4

在两种情况下,您都可以看到余数带有第一个操作数的符号。

术语命名 (Nomenclature)

C++ 标准实际上并没有给 operator% 指定一个名称。然而,C++20 标准确实说明:“二元 % 运算符产生第一个表达式除以第二个表达式的余数”。

尽管 operator% 常被称为"模"(modulo) 或"模数"(modulus) 运算符,但这可能会引起混淆,因为在数学中,模运算的定义方式通常会导致当操作数之一(且仅有一个)为负数时,其结果与 C++ 中的 operator% 产生的结果不同。

例如,在数学中: -21 模 4 = 3 -21 除以 4 的余数 = -1

出于这个原因,我们认为"取余"(remainder) 是比"模"(modulo) 更能准确描述 operator% 的名称。

在第一个操作数可能为负的情况下,必须注意余数也可能是负数。例如,您可能想这样写一个判断数字是否为奇数的函数:

bool isOdd(int x)
{
    return (x % 2) == 1; // 当 x 为 -5 时会失败
}

然而,当 x 是负奇数时(例如 -5),这将失败,因为 -5 % 2-1,而 -1 != 1

因此,如果您要比较取余运算的结果,最好与 0 比较,这没有正/负数的问题:

bool isOdd(int x)
{
    return (x % 2) != 0; // 也可以写成 return (x % 2)
}

最佳实践 (Best practice)

如果可能,优先将取余运算符 (operator%) 的结果与 0 进行比较

指数运算符在哪里?

您会注意到,^ 运算符(在数学中通常表示指数运算)在 C++ 中是按位异或 (Bitwise XOR) 操作(在课程 O.3 — 使用位运算符和位掩码进行位操作中介绍)。C++ 不包含指数运算符。

要在 C++ 中进行指数运算,需要 #include <cmath> 头文件,并使用 pow() 函数:

#include <cmath>

double x{ std::pow(3.0, 4.0) }; // 3 的 4 次方

注意,pow() 函数的参数(和返回值)类型是 double。由于浮点数中的舍入误差,pow() 的结果可能不精确(即使您传递给它整数或整数)。

如果您想进行整数指数运算,最好使用自己的函数来实现。以下函数实现了整数指数运算(使用了不太直观但高效的"平方求幂法"算法):

#include <cassert> // for assert
#include <cstdint> // for std::int64_t
#include <iostream>

// 注意:指数 exp 必须为非负数
// 注意:不执行范围/溢出检查,请谨慎使用
constexpr std::int64_t powint(std::int64_t base, int exp)
{
	assert(exp >= 0 && "powint: exp parameter has negative value"); // 断言确保 exp 非负

	// 处理 base 为 0 的情况
	if (base == 0)
		return (exp == 0) ? 1 : 0; // 0^0 定义为 1,其他 0^exp 为 0

	std::int64_t result{ 1 };
	while (exp > 0)
	{
		if (exp & 1)  // 如果 exp 是奇数
			result *= base;
		exp /= 2; // 等价于 exp = exp / 2;
		base *= base; // 平方底数
	}

	return result;
}

int main()
{
	std::cout << powint(7, 12) << '\n'; // 7 的 12 次方

	return 0;
}

输出:

13841287201

如果您不理解这个函数的工作原理,不用担心 —— 调用它并不需要理解其原理。

我们在第 9.6 课 —— 断言(assert)和静态断言(static_assert)中介绍断言(assert),在第 F.1 课 —— 常量表达式函数(constexpr functions)中介绍常量表达式函数(constexpr)。

供高级读者参考 (For advanced readers)

constexpr 说明符允许函数在用于常量表达式时在编译时求值;否则,它的行为就像常规函数并在运行时求值。

警告 (Warning)

在绝大多数情况下,整数指数运算会溢出整数类型。这很可能是标准库一开始没有包含这样一个函数的原因。

以下是上面指数函数的一个更安全版本,它检查了溢出:

#include <cassert> // for assert
#include <cstdint> // for std::int64_t
#include <iostream>
#include <limits> // for std::numeric_limits

// powint() 的一个更安全(但更慢)版本,检查溢出
// 注意:指数 exp 必须为非负数
// 如果发生溢出,返回 std::numeric_limits<std::int64_t>::max()
constexpr std::int64_t powint_safe(std::int64_t base, int exp)
{
	assert(exp >= 0 && "powint_safe: exp parameter has negative value"); // 断言确保 exp 非负

	// 处理 base 为 0 的情况
	if (base == 0)
		return (exp == 0) ? 1 : 0;

	std::int64_t result { 1 };

	// 为了使范围检查更容易,我们将确保 base 是正数
	// 如果需要,我们会在最后翻转结果的符号
	bool negativeResult{ false };

	if (base < 0)
	{
		base = -base; // 将 base 变为正数
		negativeResult = (exp & 1); // 如果指数是奇数,结果应为负数
	}

	while (exp > 0)
	{
		if (exp & 1) // 如果 exp 是奇数
		{
			// 检查 result 乘以 base 是否会溢出
			if (result > std::numeric_limits<std::int64_t>::max() / base)
			{
				std::cerr << "powint_safe(): result overflowed\n"; // 报告结果溢出
				return std::numeric_limits<std::int64_t>::max(); // 返回最大可表示值
			}

			result *= base; // 将当前 base 乘入结果
		}

		exp /= 2; // 指数减半

		// 如果已完成,在此处跳出循环
		if (exp <= 0)
			break;

		// 以下代码仅在需要再次迭代时才需要执行

		// 检查 base 乘以自身是否会溢出(用于下一次迭代的平方)
		if (base > std::numeric_limits<std::int64_t>::max() / base)
		{
			std::cerr << "powint_safe(): base overflowed\n"; // 报告底数溢出
			return std::numeric_limits<std::int64_t>::max(); // 返回最大可表示值
		}

		base *= base; // 平方底数
	}

	if (negativeResult)
		return -result; // 如果结果应为负,返回负值

	return result;
}

int main()
{
	std::cout << powint_safe(7, 12) << '\n'; // 7 的 12 次方 (未溢出)
	std::cout << powint_safe(70, 12) << '\n'; // 70 的 12 次方 (将返回最大的 64 位 int 值)

	return 0;
}

测验时间 (Quiz time)

问题 #1

以下表达式的结果是什么? 6 + 5 * 4 % 3

显示答案

问题 #2

编写一个程序,要求用户输入一个整数,并告诉用户该数字是偶数还是奇数。编写一个名为 isEven()constexpr 函数,如果传递给它的整数是偶数则返回 true,否则返回 false。使用取余运算符测试整数参数是否为偶数。确保 isEven() 对正数和负数都有效。

显示提示

您的程序应匹配以下输出:

Enter an integer: 5
5 is odd

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"