What Range of Values Can Integer Types Store in C++

What range of values can integer types store in C++?

The minimum ranges you can rely on are:

  • short int and int: -32,767 to 32,767
  • unsigned short int and unsigned int: 0 to 65,535
  • long int: -2,147,483,647 to 2,147,483,647
  • unsigned long int: 0 to 4,294,967,295

This means that no, long int cannot be relied upon to store any 10-digit number. However, a larger type, long long int, was introduced to C in C99 and C++ in C++11 (this type is also often supported as an extension by compilers built for older standards that did not include it). The minimum range for this type, if your compiler supports it, is:

  • long long int: -9,223,372,036,854,775,807 to 9,223,372,036,854,775,807
  • unsigned long long int: 0 to 18,446,744,073,709,551,615

So that type will be big enough (again, if you have it available).


A note for those who believe I've made a mistake with these lower bounds: the C requirements for the ranges are written to allow for ones' complement or sign-magnitude integer representations, where the lowest representable value and the highest representable value differ only in sign. It is also allowed to have a two's complement representation where the value with sign bit 1 and all value bits 0 is a trap representation rather than a legal value. In other words, int is not required to be able to represent the value -32,768.

How do you find the range of values that integer types can represent in C++?


C Style

limits.h contains the min and max values for ints as well as other data types which should be exactly what you need:

#include <limits.h> // C header
#include <climits> // C++ header

// Constant containing the minimum value of a signed integer (–2,147,483,648)
INT_MIN;

// Constant containing the maximum value of a signed integer (+2,147,483,647)
INT_MAX;

For a complete list of constants and their common values check out: Wikipedia - limits.h


C++ Style

There is a template based C++ method as other commenters have mentioned using:

  #include <limits>

std::numeric_limits

which looks like:

  std::numeric_limits<int>::max();

and it can even do craftier things like determine the number of digits possible or whether the data type is signed or not:

  // Number of digits for decimal (base 10)
std::numeric_limits<char>::digits10;

// Number of digits for binary
std::numeric_limits<char>::digits;

std::numeric_limits<unsigned int>::is_signed;

Which sections of the C standard prove the relation between the integer type sizes?

Thank you very much everybody who participated in the search of the answer.
Most of the replies have shared what I have already learned, but some of the comments provided very interesting insight.

Below I will summarize what I learned so far (for my own future reference).



Conclusion

Looks like C (as of late draft of C17 [C17_N2176]) does not guarantee that

sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)

(as opposed to C++).

What is Guaranteed

Below is my own interpretation/summary of what C does guarantee regarding the integer types (sorry if my terminology is not strict enough).

Multiple Aliases For the Same Type

This topic moves out of my way the multiple aliases for the same type ([C17_N2176], 6.2.5/4 parenthesized sentence referring to 6.7.2/2, thanks @M.M for the reference).

The Number of Bits in a Byte

The number of bits in a byte is implementation-specific and is >= 8. It is determined by CHAR_BIT identifier.

5.2.4.2.1/1 Sizes of integer types <limits.h>

Their implementation-defined values shall be equal or greater in magnitude (absolute value) to those shown, with the same sign.

number of bits for smallest object that is not a bit-field (byte)

CHAR_BIT 8

The text below assumes that the byte is 8 bits (keep that in mind on the implementations where byte has a different number of bits).

The sizeof([[un]signed] char)

sizeof(char), sizeof(unsigned char), sizeof(signed char), are 1 byte.

6.5.3.4/2 The sizeof and _Alignof operators

The sizeof operator yields the size (in bytes) of its operand

6.5.3.4/4:

When sizeof is applied to an operand that has type char, unsigned char, or signed char,
(or a qualified version thereof) the result is 1.

The Range of the Values and the Size of the Type

Objects may use not all the bits to store a value

The object representation has value bits, may have padding bits, and for the signed types has exactly one sign bit (6.2.6.2/1/2 Integer types).
E.g. the variable can have size of 4 bytes, but only the 2 least significant bytes may be used to store a value
(the object representation has only 16 value bits), similar to how the bool type has at least 1 value bit, and all other bits are padding bits.

The correspondence between the range of the values and the size of the type (or the number of value bits) is arguable.

On the one hand @eric-postpischil refers to 3.19/1:

value

precise meaning of the contents of an object when interpreted as having a specific type

This makes an impression that every value has a unique bit representation (bit pattern).

On the other hand @language-lawyer states

different values don't have to be represented by different bit patterns. Thus, there can be more values than possible bit patterns.

when there is contradiction between the standard and a committee response (CR), committee response is chosen by implementors.

from DR260 Committee Response follows that a bit pattern in object representation doesn't uniquely determine the value.
Different values may be represented by the same bit pattern. So I think an implementation with CHAR_BIT == 8 and sizeof(int) == 1 is possible.

I didn't claim that an object has multiple values at the same time

@language-lawyer's statements make an impression that multiple values (e.g. 5, 23, -1), probably at different moments of time,
can correspond to the same bit pattern (e.g. 0xFFFF)
of the value bits of a variable. If that's true, then the integer types other than [[un]signed] char (see "The sizeof([[un]signed] char)" section above) can have any byte size >= 1
(they must have at least one value bit, which prevents them from having byte size 0 (paranoidly strictly speaking),
which results in a size of at least one byte),
and the whole range of values (mandated by <limits.h>, see below) can correspond to that "at least one value bit".

To summarize, the relation between sizeof(short), sizeof(int), sizeof(long), sizeof(long long) can be any

(any of these, in byte size, can be greater than or equal to any of the others. Again, somewhat paranoidly strictly speaking).

What Does Not Seem Arguable

What has not been mentioned is 6.2.6.2/1/2 Integer types:

For unsigned integer types .. If there are
N value bits, each bit shall represent a different power of 2 between 1 and 2^(N-1), so that objects of
that type shall be capable of representing values from 0 to 2^N - 1 using a pure binary representation ..

For signed integer types .. Each bit that is a value bit shall have
the same value as the same bit in the object representation of the corresponding unsigned type ..

This makes me believe that each value bit adds a unique value to the overall value of the object. E.g. the least significant value bit (I'll call it a value bit number 0) (regardless of where in the byte(s) it is located) adds a value of 2^0 == 1, and no any other value bit adds that value, i.e. the value is added uniquely.
The value bit number 1 (again, regardless of its position in the byte(s), but position different from the position of any other value bit) uniquely adds a value of 2^1 == 2.

These two value bits together sum up to the overall absolute value of 1 + 2 == 3.

Here I won't dig into whether they add a value when set to 1 or when cleared to 0 or combination of those.
In the text below I assume that they add value if set to 1.

Just in case I'll also quote 6.2.6.2/2 Integer types:

If the sign bit is one, the value shall be modified in one of the following ways:

...

— the sign bit has the value -(2^M) (two’s complement);

Earlier in 6.2.6.2/2 it has been mentioned that M is the number of value bits in the signed type.

Thus, if we are talking about 8-bit signed value with 7 value bits and 1 sign bit, then the sign bit, if set to 1, adds the value of -(2^M) == -(2^7) == -128.

Earlier I considered an example where the two least significant value bits sum up to the overall absolute value of 3.
Together with the sign bit set to 1 for the 8-bit signed value with 7 value bits, the overall signed value will be -128 + 3 == -125.

As an example, that value can have a bit pattern of 0x83 (the sign bit is set to 1 (0x80), the two least significant value bits are set to 1 (0x03), and both value bits add to the overall value if are set to 1, rather than cleared to 0, in the two's complement representation).

This observation makes me think that, very likely, there is a one-to-one correspondence
between the range of values and the number of value bits in an object - every value has a unique pattern of value bits and every pattern of value bits uniquely maps to a single value.

(I realize that this intermediate conclusion can still be not strict enough or wrong or not cover certain cases)

Minimum Number of Value Bits and Bytes

5.2.4.2.1/1 Sizes of integer types <limits.h>:

Important sentence:

Their implementation-defined values shall be equal or greater in magnitude (absolute value) to those shown, with the same sign.

Then:

SHRT_MIN -32767 // -(2^15 - 1)

SHRT_MAX +32767 // 2^15 - 1

USHRT_MAX 65535 // 2^16 - 1

This tells me that

short int has at least 15 value bits (see SHRT_MIN, SHRT_MAX above), i.e. at least 2 bytes (if byte is 8 bits, see "The Number of Bits in a Byte" above).

unsigned short int has at least 16 value bits (USHRT_MAX above), i.e. at least 2 bytes.

Continuing that logic (see 5.2.4.2.1/1):

int has at least 15 value bits (see INT_MIN, INT_MAX), i.e. at least 2 bytes.

unsigned int has at least 16 value bits (see UINT_MAX), i.e. at least 2 bytes.

long int has at least 31 value bits (see LONG_MIN, LONG_MAX), i.e. at least 4 bytes.

unsigned long int has at least 32 value bits (see ULONG_MAX), i.e. at least 4 bytes.

long long int has at least 63 value bits (see LLONG_MIN, LLONG_MAX), i.e. at least 8 bytes.

unsigned long long int has at least 64 value bits (see ULLONG_MAX), i.e. at least 8 bytes.

This proves to me that:

1 == sizeof(char) < any of { sizeof(short), sizeof(int), sizeof(long), sizeof(long long) }.

The sizeof(int)

6.2.5/5 Types

A "plain" int object has the natural size suggested by the architecture of the execution environment (large enough to contain any value in the range INT_MIN to INT_MAX as defined in the header <limits.h>).

This proves to me that:

sizeof(int) == 4 on 32-bit architecture (if byte is 8 bits),

sizeof(int) == 8 on 64-bit architecture (if byte is 8 bits).

The sizeof(unsigned T)

6.2.5/6 Types

For each of the signed integer types, there is a corresponding (but different) unsigned integer type (designated with the keyword unsigned) that uses the same amount of storage (including sign information) and has the same alignment requirements.

This proves to me that:

sizeof(unsigned T) == sizoef(signed T).

The Ranges of Values

6.2.5/8 Types

For any two integer types with the same signedness and different integer conversion rank (see 6.3.1.1), the range of values of the type with smaller integer conversion rank is a subrange of the values of the other type.

(See the discussion of 6.3.1.1 below)

I assume that a subrange of values can contain the same or smaller number of values than the range.
I.e. the type with the smaller conversion rank can have the same or smaller number of values than the type with the greater conversion rank.

6.3.1.1/1 Boolean, characters, and integers

— The rank of long long int shall be greater than the rank of long int, which shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.

— The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type, if any.

— The rank of _Bool shall be less than the rank of all other standard integer types.

— The rank of any enumerated type shall equal the rank of the compatible integer type (see 6.7.2.2).

This tells me that:

range_of_values(bool) <= range_of_values(signed char) <= range_of_values(short int) <= range_of_values(int) <= range_of_values(long int) <= range_of_values(long long int).

For the unsigned types the relation between the ranges of values is the same.

This establishes the same relation for the number of value bits in the types.

But still does not prove the same relation between the sizes in bytes of objects of those types.

I.e. C (as of [C17_N2176]) does not guarantee the following statement (as opposed to C++):

sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)

Range of values in C Int and Long 32 - 64 bits

In C and C++ you have these least requirements (i.e actual implementations can have larger magnitudes)

signed char: -2^07+1 to +2^07-1
short: -2^15+1 to +2^15-1
int: -2^15+1 to +2^15-1
long: -2^31+1 to +2^31-1
long long: -2^63+1 to +2^63-1

Now, on particular implementations, you have a variety of bit ranges. The wikipedia article describes this nicely.

If a C signed integer type is stored in 22 bits, what is the smallest value it can store?

Thanks everyone for the replies. I figured it out with the help of all of your info but I will explain the answer to show exactly what gaps of knowledge I had that lead to my misunderstanding.

The data type does matter in this question because for signed data types the first bit is used to represent whether or not a binary number is positive or negative. 0111 = 7 and 1111 = -7

sign int and unsigned int use the same number of bits, 32 bits. Since an unsigned int is unsigned: the first bit isn't used to represent positive or negative so it can represent a larger number with that extra bit. 1111 converted to an unsigned int is 15 whereas with the signed int it was -7 since the furthest left bit represents the sign: 1 is negative and 0 is positive.

Now to answer "If a C signed integer type is stored in 22 bits, what is the smallest value it can store?":

If you convert binary to decimal you get 1111111111111111111111 = 4194304
This decimal value -1 is the maximum value an unsigned could hold. Since our data type is signed it has to use one less bit for the number value since the first bit represents the sign. This gives us -2097152.

Thanks again, everyone.

Range of values in C

"Range of values in C" is a broad topic. Just talking about integer ranges, a 1 bit integer field may have a narrow range of 0 to 1 or (-1 to 0). intmax_t/uintmax_t deals with numbers in the 64-bit range or more. The range limitations and conversion amongst the many integer types in C is the tricky bit.


To focus on OP's example:

In the below, 128 is an integer constant with the value of 128 and type of int.

signed char variable1;
variable1 = 128;

signed char has a range is SCHAR_MIN to SCHAR_MAX. This is typically -128 to 127 and could be as small as -127 to 127. (See C11 §5.2.4.2.1 1)

The assignment needs to convert an int to signed char and the following applies per the assumed signed char range: (My emphasis)

When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged. C11dr §6.3.1.3 1

Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised. C11dr §6.3.1.3 3

A typical implementation defined result is a 2's complement wrap-around, so a value of -128 is common. OP's platform did something different and gave it the value of -127. It is implementation-defined behavior. @2501


Not sure that "The output of the compiler(using dev C++) was : -127" implies OP was correctly using a C compiler. OP should insure that.



Related Topics



Leave a reply



Submit