简体   繁体   中英

Why does the C++ modulo operator return 0 for -1 % str.size()?

I'm confused why the following code produces this output:

#include <iostream>
#include <string>

using namespace std;

int main()
{
    int i = -1;
    string s = "abc";
    int j = s.size();
    int x = 1 % 3;
    int y = i % j; 
    int z = i % s.size(); 
    cout << s.size() << endl; // 3
    cout << x << endl;        // 1
    cout << y << endl;        // -1
    cout << z << endl;        // 0
}

Why is z = 0? Does it have to do with casting?

What really happens here:

int z = i % s.size();

is i is converted to size_t because the other side s.size() is size_t . And unlike int , size_t is unsigned; That is to say the value is interpreted as a positive number.

Check the output of this line:

std::cout << (size_t)-1 << std::endl;

to see what -1 has become.

So, stripping down your code to a minimal example, you're asking why this prints 0 :

#include <iostream>
#include <string>

int main()
{
    int a = -1;
    std::string::size_type b = 3; 
    int c = a % b;
    std::cout << c << '\n';
}

The primary operation in question here is this:

a % b

Per the standard,

5.6 Multiplicative operators [expr.mul]

  1. The operands of * and / shall have arithmetic or unscoped enumeration type; the operands of % shall have integral or unscoped enumeration type. The usual arithmetic conversions are performed on the operands and determine the type of the result .

So.. what about those "usual arithmetic conversions"? This is to mate the types of the two operands to a common type prior to performing the actual operation. The following are considered in order :

  • If both operands are integers, integer promotion is first performed on both operands. If after integer promotion the operands still have different types, conversion continues as follows:
    • If one operand has an unsigned type T whose conversion rank is at least as high as that of the other operand's type, then the other operand is converted to type T.
    • Otherwise, one operand has a signed type T whose conversion rank is higher than that of the other operand's type. The other operand is converted to type T only if type T is capable of representing all values of its previous type.
    • Otherwise, both operands are converted to the unsigned type that corresponds to the signed type T.

That's a lot of legalize for what effectively says this:

  • You have two operands, a signed int and a std::string::size_type
  • The rank of std::string::size_type is greater than that of signed int
  • Therefore, the signed int operand is converted to type std::string:size_type prior to the operation being requested.

So all that is left is the conversion, to wit, there is one more piece of legalize:

4.7 Integral conversions [conv.integral]

  1. If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source integer (modulo 2 n where n is the number of bits used to represent the unsigned type). [Note: In a two's complement representation, this conversion is conceptual and there is no change in the bit pattern (if there is no truncation). —end note]

That means, on a 32-bit std::string::size_type platform, you're going to get 2 32 -1 as the converted value from int (-1).

Which means...

4294967295 % 3

Which is... zero . If std::string::size_type is 64-bits, then everything above stays the same, save for the final calculation, which would be:

18446744073709551615 % 3

@GhaziMajdoub's answer is correct, but - why don't you let the compiler tell you what's happening?

Let's use the Flags to enable thorough and verbose g++ warnings ...

$ g++ -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy \ 
-Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations \
-Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual \
-Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-null-sentinel \
-Wstrict-overflow=5 -Wswitch-default -Wundef -Werror -Wno-unused -o a a.cpp
a.cpp: In function ‘int main()’:
a.cpp:12:13: error: conversion to ‘std::__cxx11::basic_string<char>::size_type’ {aka
‘long unsigned int’} from ‘int’ may change the sign of the result
[-Werror=sign-conversion]
   12 |     int z = i % s.size();
      |             ^
cc1plus: all warnings being treated as errors

a.cpp: In function ‘int main()’:
a.cpp:12:13: warning: conversion to ‘std::__cxx11::basic_string<char>::size_type’ 
{aka ‘long unsigned int’} from ‘int’ may change the sign of the result [-Wsign-
conversion]
   12 |     int z = i % s.size();
      |             ^

and there you have it: i is converted to long unsigned int , so it's no longer -1 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM