Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does modulo division go wrong for mix of size_t and unsigned int in C++

Given a program

#include <iostream>
using namespace std;

int main()
{
     const size_t DoW = 7;
     const unsigned int DAYS_OF_WEEK = static_cast<unsigned int> (DoW);
     unsigned int dayOfFirstDay = 0;
     unsigned int _firstDayOfWeek = 1;
     unsigned int diff = (DAYS_OF_WEEK+ (dayOfFirstDay - _firstDayOfWeek) ) % DAYS_OF_WEEK;
     cout << "diff = ("  << DAYS_OF_WEEK << " + (" << dayOfFirstDay << " - " << _firstDayOfWeek << ")) %" << DAYS_OF_WEEK
         << " = " << diff << endl;
     return 0;
}

The output of that program is

diff = (7 + (0 - 1)) %7 = 6

which is expected. But a modified program without static_cast

#include <iostream>
using namespace std;

int main()
{
     const size_t DAYS_OF_WEEK = 7;
     unsigned int dayOfFirstDay = 0;
     unsigned int _firstDayOfWeek = 1;
     unsigned int diff = (DAYS_OF_WEEK+ (dayOfFirstDay - _firstDayOfWeek) ) % DAYS_OF_WEEK;
     cout << "diff = ("  << DAYS_OF_WEEK << " + (" << dayOfFirstDay << " - " << _firstDayOfWeek << ")) %" << DAYS_OF_WEEK
         << " = " << diff << endl;
     return 0;
}

outputs

diff = (7 + (0 - 1)) %7 = 3

which is not expected. Why?

(Both programs are compiled with g++ 9.3.0 on Ubuntu 64 Bit)

like image 600
lazyboy Avatar asked May 17 '21 10:05

lazyboy


People also ask

What is the difference between modulo and Division in C++?

Modulo − Represents as % operator. And gives the value of the remainder of an integer division. Division − represents as / operator. And gives the value of the quotient of a division.

Can We do modular division by 0?

Can we always do modular division? The answer is “NO”. First of all, like ordinary arithmetic, division by 0 is not defined. For example, 4/0 is not allowed. In modular arithmetic, not only 4/0 is not allowed, but 4/12 under modulo 6 is also not allowed. The reason is, 12 is congruent to 0 when modulus is 6. When is modular division defined?

What is the modulo operator in C?

The modulo operator, denoted by %, is an arithmetic operator. The modulo division operator produces the remainder of an integer division. Syntax: If x and y are integers, then the expression: Take a step-up from those "Hello World" programs.

Why size_t is not the same as unsigned int?

Because unsigned int is not the only unsigned integer type. size_t could be any of unsigned char, unsigned short, unsigned int, unsigned long or unsigned long long, depending on the implementation. Second question is that size_t and unsigned int are interchangeable or not and if not then why?


2 Answers

It seems on your platform size_t is 64-bit, and unsigned int is 32-bit.

There is no integral promotion to 64-bits1. This is the danger of mixing 64-bit operands in expressions.

So a 32-bit wraparound of -1 remains as 4294967295 when converted to 64 bits.

And we get 7 + 4294967295 (performed in 64 bits) = 4294967302 (no wraparound).

4294967302 % 7 = 3


1 Except for systems where (unsigned) int itself is 64 bits, which is currently unlikely.

like image 101
rustyx Avatar answered Sep 28 '22 10:09

rustyx


Such result can happen when size_t has more width than unsigned int.

The subtraction of unsigned int and unsigned int wraps around and results in unsigned int. 0 - 1 results in -1, and it may become 0xffffffff when unsigned int is 4-byte long.

Then, adding that with another unsigned int will result in unsigned int, so the result looks like normal subtraction and addition.

On the other hand, adding with size_t will have it calculate in size_t domain, so truncation doesn't happen and the value 7 + 0xffffffff will be divided instead of 7 - 1.

Here is an example code to check the values before division:

#include <iostream>
#include <ios>

int main()
{
     const size_t DoW = 7;
     const unsigned int DAYS_OF_WEEK = static_cast<unsigned int> (DoW);
     unsigned int dayOfFirstDay = 0;
     unsigned int _firstDayOfWeek = 1;
     size_t to_add = dayOfFirstDay - _firstDayOfWeek;
     size_t diff_uint = DAYS_OF_WEEK+ (dayOfFirstDay - _firstDayOfWeek);
     size_t diff_sizet = DoW+ (dayOfFirstDay - _firstDayOfWeek);
     std::cout << "sizeof(unsigned int) = " << sizeof(unsigned int) << '\n';
     std::cout << "sizeof(size_t) = " << sizeof(size_t) << '\n';
     std::cout << std::hex;
     std::cout << "to add     : 0x" << to_add << '\n';
     std::cout << "diff_uint  : 0x" << diff_uint << '\n';
     std::cout << "diff_sizet : 0x" << diff_sizet << '\n';
     return 0;
}

Here is an example of output:

sizeof(unsigned int) = 4
sizeof(size_t) = 8
to add     : 0xffffffff
diff_uint  : 0x6
diff_sizet : 0x100000006
like image 39
MikeCAT Avatar answered Sep 28 '22 10:09

MikeCAT