Vladimir Prokofev
Vladimir Prokofev

Reputation: 21

C++ Standard: Strange signed/unsigned arithmetic division behavior for 32 and 64 bits

I encountered a wrong behavior of my code. Investigating it leads me to a short example which shows the problem:

//g++  5.4.0

#include <iostream>
#include <vector>

int main()
{
    std::vector<short> v(20);

    auto D = &v[5] - &v[10];   
    auto C = D / sizeof(short);

    std::cout << "C = " << C;

}

The example is a quite common. What is the result it will print?

C = 9223372036854775805

Tested here: https://rextester.com/l/cpp_online_compiler_gcc Tested also for Clang C++, VS C++ and C. Result the same.

Discussing with colleagues I was pointed to the document https://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions .

It tells:

It seems the second rule is working here. But it is not true.

To confirm the second rule, I have tested such example:

//g++  5.4.0
#include <iostream>

int main()
{
            typedef uint32_t u_t; // uint64_t, uint32_t, uint16_t uint8_t
            typedef int32_t i_t;  // int64_t, int32_t, int16_t int8_t

            const u_t B = 2;
            const i_t X = -1;

            const i_t A1 = X * B;
    std::cout << "A1 = X * B = " << A1 << "\n";

            const i_t C = A1 / B;  // signed / unsigned division
    std::cout << "A1 / B = " << C << "\n";
}

with different rank combinations of u_t and i_t and found that it works correctly for any combination, EXCEPT for 32 and 64 bits (int64_t/uint64_t and int32_t/uint32_t). So the second rule DOES NOT work for 16 and 8 bits.

Note: the multiplication operation is working correct for all cases. So it is only division problem.

Also the SECOND rule sounds like it is wrong:

the signed operand is converted to the unsigned operand's type

The signed cannot be converted to unsigned - it is an !! error !! for NEGATIVE values!! But opposite conversion is correct - the unsigned operand is converted to the signed operand's type

Looking at this I can note that here is a possible mistake in the C++ Standard Arithmetic operations. Instead of:

Otherwise, if the unsigned operand's conversion rank is greater or equal to the conversion rank of the signed operand, the signed operand is converted to the unsigned operand's type.

it SHALL be:

Otherwise, if the signed operand's conversion rank is greater or equal to the conversion rank of the unsigned operand, the unsigned operand is converted to the signed operand's type.

On my opinion, if signed and unsigned multiplication/division is met then unsigned operand is converted to signed and after that it is casted to correct rank. At least the x86 Assembler follows it.

Please, explain me where here is an error. I want the first test in this post works correct for any type involved in place of the auto type, but now it is not possible and the C++ Standard tells that it is correct behavior.

Sorry for a strange question, but I am in a stuck with the problem. I am coding on C/C++ for 30 years but it is first problem I cannot explain it clearly - whether it is a bug or an expected behavior.

Upvotes: 2

Views: 798

Answers (1)

YSC
YSC

Reputation: 40150

There's a lot to chew on here... I'll address only one point as you forgot to actually ask a question.

In your second code snippet:

const u_t B = 2;
const i_t X = -1;
const i_t A1 = X * B;

you see than A1 is -2 and conclude that in the expression X * B both operands are promoted to signed integers. This is not true.

In X * B, both operands are promoted to unsigned integers, as per the Standard, but its result is then converted to a signed integer with the affectation const i_t A1 = ....

You can easily check that:

const u_t B = 2;
const i_t X = -1;
const auto A1 = X * B; // unsigned

You can also play with decltype(expression) and std::is_signed:

#include <iostream>
#include <iomanip>
#include <type_traits>

int main()
{
    signed s = 1;
    unsigned u = 1;
    std::cout << std::boolalpha
              << "  signed *   signed is signed? " << std::is_signed_v<decltype(s * s)> << "\n"
              << "  signed * unsigned is signed? " << std::is_signed_v<decltype(s * u)> << "\n"
              << "unsigned *   signed is signed? " << std::is_signed_v<decltype(u * s)> << "\n"
              << "unsigned * unsigned is signed? " << std::is_signed_v<decltype(u * u)> << "\n";
}

/*
  signed *   signed is signed? true
  signed * unsigned is signed? false
unsigned *   signed is signed? false
unsigned * unsigned is signed? false
*/

demo

Upvotes: 1

Related Questions