为什么负数取模向量大小的结果不是负数?

26
#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main()
{
  vector<int> v = {1, 2, 3, 4, 5, 6, 7};
  int i = -4;

  cout << i << endl;
  cout << v.size() << endl;
  cout << i % v.size() << endl;
  cout << -4 % 7 << endl;
}

以上代码输出:

-4
7
5
-4

有人可以解释一下为什么i % v.size()输出的是5而不是-4吗?我猜想这可能与vector.size()有关,但不确定其中的原因。先谢谢了。


2
这个回答解决了你的问题吗?C++ size_t取模运算与负操作数,还有这个这个 - Nick stands with Ukraine
@Nick 这甚至没有提到未指定的行为。它不应该成为规范的重复。 - Spencer
1
@Spencer 当然不会...整数转换是明确定义的。 - Nick stands with Ukraine
1
@Spencer,这种情况下没有负参数,它们都被转换为无符号数。(如果这不太好理解,问题明确是关于i % v.size()signed % unsigned的情况,有符号值在%操作实际发生之前被转换为无符号类型,因此...这里没有什么意外的情况) - Nick stands with Ukraine
1
@MooingDuck 我没有说“未定义”,我说的是“未指定”。但即使这样也是不对的,实际上它应该是“实现定义”,因为 size_t 是实现定义的,即使在整数转换时,在不同平台上也不应该期望任何特定的结果。 - Spencer
显示剩余6条评论
5个回答

31
% 运算符的操作数在执行除法之前会进行常规算术转换,以将它们转换为公共类型。如果操作数是 intsize_t,则会将 int 转换为 size_t
如果 size_t 是 32 位,则 -4 将变为 4294967292,然后表达式的结果是 4294957292 % 7,实际上是 0
如果 size_t 是 64 位,则 -4 将变为 18,446,744,073,709,551,612,而此表达式的结果为 % 75,这也是您看到的结果。
因此,从这个输出我们可以判断您的系统具有 64 位的 size_t

9

C++中的取模运算符被定义为,对于除数不等于0的所有整数,以下陈述成立:

(a/b)*b + a%b == a

因此,它被迫与整数除法保持一致,从C++ 11开始,即使是负数也会向零截断。因此即使对于负数,一切都是明确定义的。
然而,在您的情况下,您有一个带符号/无符号除法(因为.size()返回无符号值),并且适用通常的带符号/无符号规则。这意味着在这种情况下,在执行操作之前,所有参数都将转换为无符号类型(请参见Ruslan的评论)。
因此,-4被转换为无符号数(变成了一个非常大的数字),然后进行模运算。
您还可以将其视为5不是任何整数除法定义下-4模7的正确答案(3才是正确的)。
使用C和C++的算术规则并不直观。

这意味着所有参数都会被转换为无符号类型。需要注意的是,一般情况下并非如此:例如10U/-3LL的类型为signed long long(前提是long longunsigned更长,这通常是正确的)。是的,规则确实不直观。 - Ruslan
身份 a/b*b+a%b==a 除了 b==0 外,可以追溯到 C++98 和 C89(因此 ARM)。C99 和 C++11 已经改变了 / 和 % 的负操作数,以始终舍入为零(也称截断),而不是实现定义的舍入,但正如您(和其他人)在这种情况下正确地指出的那样,操作数被强制为无符号,并且(在使用时)不是负数。 - dave_thompson_085
@Ruslan, dave_thompson_085:谢谢,我更新了我的答案以使其更加准确。 - Andreas H.

4

3
这是由于 v.size() 的类型是无符号类型。由于整数提升,这意味着结果也将被视为无符号类型,尽管 i 是有符号类型。
我假设你正在编译 64 位。这意味着除了晋升为无符号数之外,结果还将是 64 位类型的 unsigned long long。一步一步来:
  1. unsigned long long _i = (unsigned long long)-4; // 0xFFFFFFFFFFFFFFFC!
  2. unsigned long long result = _i % (unsigned long long)7; // 5
因为您可能希望保留 i 的符号,所以在这种情况下,只需将 v.size() 强制转换为有符号类型即可防止晋升为无符号数: i % (int)v.size() 将给出 -4

如果是64位,它可能是unsigned long - M.M

1

来自cppreference关于通常的算术转换C++标准

否则(有符号性不同):如果无符号类型的转换等级大于或等于有符号类型的等级,则具有有符号类型的操作数将隐式转换为无符号类型。

-4signed7size_t,是一个unsigned类型,因此首先将-4转换为unsigned,然后进行模运算。

考虑到这一点,如果你分解它,你会立即看到发生了什么:

size_t s = -4; // s = 18446744073709551612 on a 64 bit system
size_t m = 7;
std::cout << s % m << '\n'; //5

32位系统的结果可能会有所不同。

cout << -4 % 7 << endl; 仍然打印出 -4。为什么?因为 -47 的类型都是 int

C++ 标准 §5.13.2.3 整数字面值的类型

整数字面值的类型是表 8 中与其可选的整型后缀对应的列表中的第一个类型,该类型可以表示其值。 整数字面值是 prvalue。

表 8:没有后缀的整数字面值的类型

    int
    long int
    long long int

所以,在这种情况下,-47都是int类型,因此模运算的结果是-4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接