23
#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;
}

The above code prints:

-4
7
5
-4

Can someone please explain why i % v.size() prints 5 instead of -4? I'm guessing it has something to do with vector.size(), but unsure what the underlying reasoning is. Thanks in advance.

R.M.
  • 2,938
  • 1
  • 17
  • 33
George Zhang
  • 347
  • 1
  • 3
  • what have you for 'i % 7' and '-4 % v.size()' ? – schlebe Jun 15 '20 at 06:30
  • Does this answer your question? [Modulo operator with negative values](https://stackoverflow.com/questions/7594508/modulo-operator-with-negative-values) – Spencer Jun 15 '20 at 15:07
  • @Spencer: I think that's only half the problem. The other half is that `v.size()` is `unsigned`, causing weird integer promotion problems – Mooing Duck Jun 15 '20 at 16:49
  • 2
    Does this answer your question? [C++ size\_t modulus operation with negative operand](https://stackoverflow.com/questions/39337196/c-size-t-modulus-operation-with-negative-operand), also [this](https://stackoverflow.com/questions/34523401/why-mod-operation-return-0-when-one-is-size-t-type-and-the-other-is-0) and [this](https://stackoverflow.com/questions/61299317/why-does-the-c-modulo-operator-return-0-for-1-str-size) – Nick Jun 15 '20 at 16:50
  • @MooingDuck which can be rolled into the characterization of unspecified behavior. – Spencer Jun 15 '20 at 17:00
  • @Nick that doesn't even mention unspecified behavior once. It shouldn't be the canonical dupe. – Spencer Jun 15 '20 at 17:02
  • 1
    @Spencer Of course it doesn't... integer conversion is well defined. – Nick Jun 15 '20 at 17:03
  • @Nick Integer conversion is an unimportant detail here. It's the unspecified nature of the behavior of `%` with negative arguments that causes the problem. – Spencer Jun 15 '20 at 17:06
  • 1
    @Spencer There _aren't_ negative arguments for the case in question, they're both converted to unsigned. (if that didn't make sense, the question is explicitly about the `i % v.size()` case or `signed % unsigned`, the signed value gets converted to an unsigned type before the `%` operation actually takes place, hence... nothing unexpected here) – Nick Jun 15 '20 at 17:07
  • @Spencer: There's no undefined behavior. Integer promotion is well defined, and `%` with negative arguments is well defined as well. – Mooing Duck Jun 15 '20 at 17:26
  • 1
    @MooingDuck I didn't say "undefined", I said "unspecified". But even that's wrong, it's actually "implementation-defined" _because [size_t is implementation defined](https://en.cppreference.com/w/cpp/types/size_t)_, and even with the integer conversion you shouldn't expect any particular result across platforms. – Spencer Jun 15 '20 at 17:37

5 Answers5

32

The operands of % undergo the usual arithmetic conversions to bring them to a common type, before the division is performed. If the operands were int and size_t, then the int is converted to size_t.

If size_t is 32-bit then -4 would become 4294967292 and then the result of the expression is 4294957292 % 7 which is actually 0.

If size_t is 64-bit then -4 would become 18,446,744,073,709,551,612 and the result of this % 7 is 5 which you saw.

So actually we can tell from this output that your system has 64-bit size_t.

M.M
  • 130,300
  • 18
  • 171
  • 314
10

In C++ the modulus operator is defined so that the following is true for all integers except for b == 0:

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

So it is forced to be consistent with the integer division, which from C++ 11 onwards truncates to zero even for negative numbers. Hence everything is well defined even for negative numbers.

However, in your case you have an signed / unsigned division (because .size() returns unsigned) and the usual signed/unsigned rules apply. This means that in this case all arguments are converted to unsigned before the operation is carried out (see also Ruslan's comment).

So -4 is converted to unsigned (and becomes a very large number) and then modulo is carried out.

You can also see this as 5 is not a correct answer for -4 modulo 7 with any definition of integer division (3 would be correct).

Arithmetic rules with C and C++ are not intuitive.

Andreas H.
  • 4,439
  • 15
  • 22
  • _"This means that all arguments are converted to unsigned"_ — note that in general this isn't true: e.g. `10U/-3LL` has type `signed long long` (provided `long long` is longer than `unsigned`, which is usually true). Yes, the rules are not intuitive indeed. – Ruslan Jun 15 '20 at 22:56
  • The identity `a/b*b+a%b==a` except for b==0 goes back to C++98, and C89 (thus ARM). C99 and C++11 did change / and % with a _negative_ operand to always round-to-zero (aka truncate) instead of implementation-defined rounding, but as you (and others) correctly say in this case the operands are forced to unsigned, and (when used) are not negative. – dave_thompson_085 Jun 16 '20 at 00:33
  • @Ruslan, dave_thompson_085: Thanks I updated my answer to be more precise. – Andreas H. Jun 16 '20 at 07:01
4

Because v.size return size_t.

cout << -4 % size_t(7) << endl; // 5

Take a look modulo-operator-with-negative-values

UPD: and signed-int-modulo-unsigned-int-produces-nonsense-results

Anton Shwarts
  • 468
  • 1
  • 7
3

This is due to the type of v.size(), which is an unsigned type. Due to integer promotion, this means that the result will also be treated as unsigned, despite i being a signed type.

I am assuming you are compiling on 64 bit. This means that in addition to promotion to unsigned, the result will also be of the 64 bit type unsigned long long. Step by step:

  1. unsigned long long _i = (unsigned long long)-4; // 0xFFFFFFFFFFFFFFFC!
  2. unsigned long long result = _i % (unsigned long long)7; // 5

Since presumably you want to preserve the signedness of i, in this case it is enough to cast v.size() to a signed type to prevent the promotion to unsigned: i % (int)v.size() will give -4.

Matti
  • 31
  • 1
  • 3
2

From cppreference on Usual arithmetic conversions and C++ standard

Otherwise (the signedness is different): If the unsigned type has conversion rank greater than or equal to the rank of the signed type, then the operand with the signed type is implicitly converted to the unsigned type.

-4 is signed and 7 is size_t which is an unsigned type, so -4 is converted to unsigned first and then modulus is carried out.

With that mind, if you break it down, you will immediately see what is happening:

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

The results might be different for a 32-bit system.

cout << -4 % 7 << endl; still prints -4. Why? It's because the type of both -4 and 7 is int.

C++ standard §5.13.2.3 Type of an integer literal

The type of an integer-literal is the first type in the list in Table 8 corresponding to its optional integer-suffix in which its value can be represented. An integer-literal is a prvalue.

Table 8: Types of integer-literals without suffix:

    int
    long int
    long long int

So, -4 and 7 both are int in this case and hence the result of modulo is -4.

Waqar
  • 6,944
  • 2
  • 26
  • 39