1

Please explain why the first snippet of code does not result in -1.

    string s = "abc";
    int amount = -1;
    cout << "amount to shift before modulus: " << amount<<endl;
    amount %= s.size();
    cout << "mod " <<s.size()<<endl;
    cout << "amount to shift after modulus: " << amount<<endl;

Output:
amount to shift before modulus: -1
mod 3
amount to shift after modulus: 0

    string s = "abc";
    int sSize = s.size();
    int amount = -1;
    cout << "amount to shift before modulus: " << amount<<endl;
    amount %= sSize;
    cout << "mod " <<sSize<<endl;
    cout << "amount to shift after modulus: " << amount<<endl;

Output:
amount to shift before modulus: -1
mod 3
amount to shift after modulus: -1

I was completing a coding challenge when I ran across this behavior and I don't understand what is going on. This behavior does not occur if the amount is a positive number such as 1.

Calamari
  • 48
  • 7
  • 1
    I ran the numbers, and this is true, this is how two's complement math works out to be on 64 bit platforms. However, explaining what's happening here would just encourage more of these kinds of questions spawned by those useless online context sites, and those kinds of low-quality questions should really be discouraged. There's already too much noise here. – Sam Varshavchik Apr 15 '20 at 01:56
  • @Sam: This issue is the result of the usual arithmetic conversions. I couldn't find a duplicate which seemed appropriate, and I think the issue is sufficiently non-obvious to warrant an answer. – rici Apr 15 '20 at 02:09
  • Sorry let me clarify. The problem is the difference between -1 mod 3 and -1 mod s.size() (which is also 3) Results in two different results. – Calamari Apr 15 '20 at 02:12
  • @calamari: Yes, the 3 is the same in both computations. It's the -1 which differs. – rici Apr 15 '20 at 02:14

1 Answers1

4

If you do arithmetic (including arithmetic comparison) between a signed integer and an unsigned integer, and the unsigned integer type is at least as wide as the signed integer, then the signed integer is converted to an unsigned integer. (This is the result of the so-called "usual arithmetic conversions".)

That almost always has unexpected results, unless the arithmetic operation is addition or subtraction. In many cases, a good compiler will warn you about doing mixed-sign arithmetic, if you request warnings. (Highly recommended!) This is just one relatively uncommon variant.

In the first case, s.size() is of type size_t, which is an unsigned type, probably of 64 bits; therefore, it is wider than an int. So in order to compute amount % s.size(), the compiler first converts amount to an unsigned size_t, which will result in 264-1. As it happens, that number is divisible by 3, so the modulo operation returns 0.

In your second case, you force s.size() to be converted to a signed integer. Since the value is certainly within the limits of an int, the conversion is well-defined. The subsequent modulo operation is performed on ints, which are signed, and yields the expected result if you expect signed modulo to work the way it does in C++. (See Why does C++ output negative numbers when using modulo? for more information.)

rici
  • 234,347
  • 28
  • 237
  • 341