HOWTO · C++

在 C++ 中使用模数运算符

本文介绍了如何在 C++ 中使用 modulo 运算符。

本页内容

C++ 的 % 运算符返回整数除法后的余数。例如,10 % 3 的结果是 1。它适用于整数类型,包括有符号整数;浮点操作数不适用。除数必须非零。该运算可用于检查奇偶性、循环确定位置和计算有界桶索引;处理有符号值时需考虑其影响。

C++ % 运算符的返回值

对于整数 a 和 b,a % b 表示 a / b 后剩下的余数。C++ 的整数除法向零截断,因此 10 / 3 的结果是 3,10 % 3 的结果是 1。两个操作数都必须是整数类型;如果操作数不是整数类型,使用 % 会使表达式格式错误。除数也必须非零,因为除数为零会导致未定义行为。

下面的小程序展示了一个常见的正数计算:

#include <iostream>

int main() {
    const int dividend = 17;
    const int divisor = 5;
    std::cout << dividend % divisor << '\n';
}
2

% 运算符不能用于浮点值。需要计算浮点余数时,可使用 <cmath> 中的 std::fmod。

% 如何处理有符号整数

对于有符号操作数,整数除法向零截断;当余数非零时,余数的符号与被除数相同。因此,-10 % 3 的结果是 -1,而 10 % -3 的结果是 1;这两种情况下,商乘以除数再加上余数都等于被除数。这是 C++ 规定的行为,并非由编译器自行决定。

由于负余数仍为负数,仅使用 % 并不总能得到非负索引。若需要将结果规范化,应确保模数 m 为正,把 value % m 存入余数变量,并且只在余数为负时加上 m。由于余数大于 -m,这样得到的结果会落在 [0, m) 范围内,也不会因中间求和而溢出。商还必须能够表示:如果对应的整数除法所得商无法表示,% 会导致未定义行为,例如有符号类型的最小值 % -1。上述余数等式不适用于这种未定义行为情形。

用 % 检查奇偶性并循环访问值

当 value % 2 == 0 时,整数为偶数;否则为奇数。负整数也适用,因为二的整数倍的余数为零。

要循环使用索引,请确保集合大小为正数,且位置非负:

#include <iostream>

int main() {
    const int values[] = {10, 20, 30};
    const int count = static_cast<int>(sizeof(values) / sizeof(values[0]));

    for (int position = 0; position < 5; ++position) {
        const int index = position % count;
        std::cout << values[index] << '\n';
    }
}
10
20
30
10
20

数组包含三个元素,因此每个索引都在 0 到 2 的范围内;随着循环推进,位置序列会不断重复。此模式假设集合至少包含一个元素。如果位置可能为负数,请先规范化余数,再将其用作索引。

在哈希和随机数范围中使用余数

哈希表可以用余数将非负键映射到含有 tableSize 个桶的表中:key % tableSize。这要求 tableSize > 0。如果键可能为负数,在将结果转换为索引之前,请按上文所述规范化结果。实际的哈希表还需要哈希函数和冲突处理策略;余数运算只负责选出桶的范围。

生成随机整数时,不要认为 std::rand() % bound 的分布一定均匀:如果生成器可能产生的值的数量不是 bound 的倍数,某些结果出现的次数就会更多。需要生成包含上下界的整数范围时,优先搭配随机引擎使用 std::uniform_int_distribution:

#include <iostream>
#include <random>

int main() {
    std::mt19937 engine(12345);
    std::uniform_int_distribution<int> range(1, 6);
    std::cout << range(engine) << '\n';
}
6

分布对象的参数定义了包含在范围内的下界和上界。使用此种子、引擎以及经过测试的标准库实现中的 std::uniform_int_distribution 时,示例会输出 6。C++ 不要求不同标准库实现以相同方式将引擎输出映射到分布,因此确切输出和序列不具备可移植性;只有引擎和分布实现都相同时,结果才可重复。若需要不同的序列,请根据应用选择合适的种子来源。

将 std::modulus 用作函数对象

<functional> 中的 std::modulus 是一个函数对象,可对两个值应用 %。当算法需要可调用对象而非运算符表达式时,它会很有用:

#include <functional>
#include <iostream>

int main() {
    const std::modulus<int> remainder;
    std::cout << remainder(17, 5) << '\n';
}
2

它的操作数遵循与 % 相同的整数类型和除数非零规则。对于普通表达式,a % b 通常是最清晰的写法。