Home > Back-end >  C comparison between -1 and 65535/4294967295/18446744073709551615
C comparison between -1 and 65535/4294967295/18446744073709551615

Time:02-08

I have a misunderstanding about the following statements:

printf("%d\n", -1 == 65535);
printf("%d\n", -1 == 4294967295UL);
printf("%d\n", -1 == 18446744073709551615);
printf("%d\n", -1 == 18446744073709551615UL);

The output is:

0
0
0
1

If if don't add the UL suffixes the compiler gives the following warning: main.c:36:28: warning: integer constant is so large that it is unsigned

Can someone explain this to me please? Why for 18446744073709551615 the result is 0 but for 18446744073709551615UL the results is 1? It's ok not to add that suffix?

Thanks

CodePudding user response:

Case 3 exhibits a bug in GCC.

In GCC 11.2 for x86_64, the signed and unsigned int types are 32 bits, the signed and unsigned long and long long types are 64 bits, and there are also extended types __int128_t and __uint128_t with width 128.

In -1 == 65535, -1 and 65535 are both int. They are unequal, so the comparison produces 0.

In -1 == 4294967295UL, -1 is an int, and 4294967295UL is an unsigned long. Per the usual arithmetic conversions, −1 is converted to unsigned long, which wraps modulo 264, producing 18,446,744,073,709,551,615. That does not equal 4,294,967,295, so the comparison produces 0.

In -1 == 18446744073709551615, 18446744073709551615 is too large for the normal types. C 2018 6.4.4.1 5 says the type of an unsuffixed decimal integer constant is the first of int, long int, or long long int that can represent it. Since none of those can represent it in this C implementation, paragraph 6 applies:

If an integer constant cannot be represented by any type in its list, it may have an extended integer type, if the extended integer type can represent its value. If all of the types in the list for the constant are signed, the extended integer type shall be signed…

From this, we see the type of 18446744073709551615 should be __int128_t. This is confirmed by using _Generic to reveal the type; the following program prints “__int128_t”:

#include <stdio.h>


int main(void)
{
    printf("%s\n", _Generic(18446744073709551615,
            __int128_t: "__int128_t",
            default:    "something else")
        );
}

In contrast, Clang 13.0.0 uses unsigned long long.](https://godbolt.org/z/a5Y3h5Wbo)

Thus, GCC’s error message that states “integer constant is so large that it is unsigned” is incorrect. GCC uses a different type than its error message states, so this is a bug in GCC.

Given that the type is __int128_t, −1 and 18,446,744,073,709,551,615 are both representable in the type, so evaluation of the expression converts −1 to __int128_t with no change in value, the values are unequal, and the comparison produces 0.

(Since Clang uses unsigned long long, it produces 1.)

For confirmation, we see that printf("%d\n", -18446744073709551615 < 0); prints “1” in GCC, showing the comparison was performed with a signed type, whereas Clang prints “0”.

Finally, in -1 == 18446744073709551615UL, the usual arithmetic conversions convert −1 to unsigned long, which wraps modulo 264, producing 18,446,744,073,709,551,615. Then the two operands are equal, and the comparison produces 1.

CodePudding user response:

The result depends on the type the compiler selects for integer constants.

If an integer decimal constant does not have a suffix then the compiler considers the following types to represent a constant: int, long int. long long int.

This value 18446744073709551615 is a maximum value that can be stored in an object of the type unsigned long or unsigned long long. It can not be represented as having the type signed long int or signed long long int.

When a constant is specified with a suffix ul or UL the compiler considers sequentially the types unsigned long int and unsigned long long int and selects the first type an object of which can represent the constant.

So in this expression

-1 == 18446744073709551615UL

the right operand of the comparison has the maximum value represented in the corresponding unsigned integer type selected by the compiler and the left operand is converted to this type by promoting the sign bit due to teh usual arithmetic conversions. As a result -1 is represented as the maximum unsigned value stored in the selected unsigned integer type.

By the way it seems that in the used by you system sizeof( unsigned long ) is equal to sizeof( unsigned long long ). Otherwise if sizeof( unsigned long ) would be equal to sizeof( unsigned int ) (as it is in Windows) then this call

printf("%d\n", -1 == 4294967295UL);

also outputted 1.

CodePudding user response:

This is due to how integer constants are treated.

For decimal constants with no suffix, the compiler checks the value to see if it will fit into the following types, in this order:

  • int
  • long int
  • long long int

The compiler gives you a warning because the value 18446744073709551615 will not fit into any of those types. What happens next is dictated by section 6.4.4.1p6 of the C standard:

If an integer constant cannot be represented by any type in its list, it may have an extended integer type, if the extended integer type can represent its value. If all of the types in the list for the constant are signed, the extended integer type shall be signed. If all of the types in the list for the constant are unsigned, the extended integer type shall be unsigned. If the list contains both signed and unsigned types, the extended integer type may be signed or unsigned. If an integer constant cannot be represented by any type in its list and has no extended integer type, then the integer constant has no type

And since 18446744073709551615 doesn't fit into one of the above types, the compiler will attempt to fit the value into a signed extended type if one exists. GCC in particular supports a type called __int128 , so assuming you're using GCC then the constant has that type.

Then for the comparison -1 == 18446744073709551615, the left side of the comparison is converted to type __int128 which it can do without a change of value, and the result is false.

In the case of the constant 18446744073709551615UL, the suffix gives it type unsigned long and the value does fit into that type. Then for the comparison -1 == 18446744073709551615UL, -1 is converted to type unsigned long. By the rules of converting an out-of-range signed type to an unsigned type, the int value -1 is converted to the unsigned long value 18446744073709551615 and the comparison is true.

The warning generated by gcc is misleading in that it suggests that the constant is unsigned when it actually is not. This may be a reference to C89 behavior where this constant would in fact have type unsigned long. If the -std=c89 flag is passed to gcc, you'll get another warning in addition to the first: warning: this decimal constant is unsigned only in ISO C90. The results will also differ because of this, with the third call to printf outputting 1.

CodePudding user response:

This seems to be an example of inconsistent behavior of the compiler leading to surprising results:

  • printf("%d\n", -1 == 18446744073709551615); compiled with gcc produces a warning integer constant is so large that it is unsigned because it exceeds the range of type long long int. If you were to store this number into an unsigned long long, you would probably get the expected unsigned long long value, but it is used in a test and you are comparing -1 with a positive integer of an extended type, so the compiler might just optimize the test as false in all cases. gcc outputs 1 for printf("%d\n", 18446744073709551615 == 36893488147419103231) so the compiler does not exactly use 128 bit arithmetics to evaluate this test.

  • compiling with clang produces a similar warning integer literal is too large to be represented in a signed integer type, interpreting as unsigned but the code generated is different and the output is 1, meaning that 18446744073709551615 is typed as unsigned long long internally, consistently with the comment emitted.

Here is a program to substantiate my statement:

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

#define typeof(X)  _Generic((X),                                        \
                   long double: "long double",                          \
                   double: "double",                                    \
                   float: "float",                                      \
                   unsigned long long int: "unsigned long long int",    \
                   long long int: "long long int",                      \
                   unsigned long int: "unsigned long int",              \
                   long int: "long int",                                \
                   unsigned int: "unsigned int",                        \
                   int: "int",                                          \
                   unsigned short: "unsigned short",                    \
                   short: "short",                                      \
                   unsigned char: "unsigned char",                      \
                   signed char: "signed char",                          \
                   char: "char",                                        \
                   bool: "bool",                                        \
                   default: "other")

int main() {
#define TEST(x)  printf("%8s has type %s and size %zu\n", #x, typeof(x), sizeof(x))
    TEST(9223372036854775807);
    TEST(18446744073709551615U);
    TEST(18446744073709551615);
}

Output with clang:

typeof.c:27:10: warning: integer literal is too large to be represented in a signed integer type, interpreting as unsigned
      [-Wimplicitly-unsigned-literal]
    TEST(18446744073709551615);
         ^
typeof.c:27:10: warning: integer literal is too large to be represented in a signed integer type, interpreting as unsigned
      [-Wimplicitly-unsigned-literal]
2 warnings generated.

9223372036854775807 has type long int and size 8
18446744073709551615U has type unsigned long int and size 8
18446744073709551615 has type unsigned long long int and size 8

Output with gcc:

typeof.c: In function `main':
typeof.c:27:30: warning: integer constant is so large that it is unsigned
     TEST(18446744073709551615);
                              ^
typeof.c:27:30: warning: integer constant is so large that it is unsigned
9223372036854775807 has type long int and size 8
18446744073709551615U has type unsigned long int and size 8
18446744073709551615 has type other and size 16
  •  Tags:  
  • Related