# C++ integer types a tutorial !

The integer types in C++ are fundamental types. A fundamental type , has a mapping to hardware , so the operations performed on a fundamental type , are hardware performed .

Boolean , Characters , and integer types , are called the integral types. The integral types and the floating point types are called the arithmetic types .

# What are the C++ integer types

An integer type is a whole number, as in or . A whole number does not contain a fractional part , such as .

Integer types can either be unsigned or signed . An unsigned integer , can only be non negative , for example , or , whereas a signed integer , can be negative , positive , or zero , as in , or .

C++ standards , prior to C++20 , do not specify the algorithm for representing the signed , or unsigned integer types . A signed integer , can be represented , using one’s complements , two’s complement , or sign and magnitude . C++20 , specifies that signed integers , must be represented using two’s complement .

The C++ standards dictates that unsigned arithmetic , such as addition , or subtraction , must obey arithmetic modulo two , to the power , this is related to overflow . Overflow happens , when the result is too large to fit in the allocated width , for an integer type .

An integer type , can have multiple type specifiers, for example : , , and , represent the same integer type , which is signed . This being said , an integer type can be declared in multiple ways , or using multiple keywords.

`short int , short unsigned short int , unsigned short int , signed , signed int unsigned int  , unsigned long int , signed long int , signed long unsigned long int , unsigned long , long long int , signed long long int, signed long long , long longunsigned long long int , unsigned long long`

It is clear , from the preceding list , that for signed integer types , it is not necessary to use the keyword .

A signed integer type , and its unsigned version , such as , and , have the same storage size and alignment . Alignment is where in memory , an object can be placed .

An implementation can define other integer types, called the extended integer types . For each extended integer type , a signed , and an unsigned version , must be defined .

# What are the C++ integer types ranges

The C++ standard specifies the minimum ranges, that a signed or an unsigned integer type , might have , an implementation , can define larger ranges .

Before C++ 20 , and because signed integer types could have , a sign and magnitude representation , the minimum ranges were :

Starting C++20 , the minimum ranges are :

The minimum ranges of the integral types , as implemented on a given machine , are defined by the C++ header .

`#include<iostream>#include<climits>int main(void ){    using std::cout;    using std::endl;    cout << "CHAR_BIT : "  << CHAR_BIT << endl;    /*Print the number of bits in a char .*/    cout << "SCHAR_MIN : " << SCHAR_MIN << endl;    /*Print the minimum value of signed char .*/    cout << "SCHAR_MAX : " << SCHAR_MAX << endl;    /*Print the maximum value of signed char .*/    cout << "UCHAR_MAX : " << UCHAR_MAX << endl;    /*Print the maximum value of unsigned char .*/    cout << "CHAR_MIN : " << CHAR_MIN << endl;    /*Print the minimum value of char .*/    cout << "CHAR_MAX : " << CHAR_MAX  << endl;    /*Print the maximum value of char .*/    cout << "MB_LEN_MAX : " << MB_LEN_MAX << endl;    /*Print maximum number of bytes available in    a multibyte character .*/    cout << "SHRT_MIN : " << SHRT_MIN << endl;    /*Print the minimum value of short .*/    cout << "SHRT_MAX : " <<  SHRT_MAX  << endl;    /*Print the maximum value of short .*/    cout << "USHRT_MAX : " <<  USHRT_MAX << endl;    /*Print the minimum value of unsigned short .*/    cout << "INT_MIN : " << INT_MIN << endl;    /*Print the minimum value of int .*/    cout << "INT_MAX : " <<  INT_MAX << endl;    /*Print the maximum value of int .*/    cout << "UINT_MAX : " << UINT_MAX << endl;    /*Print the maximum value of unsigned int .*/    cout << "LONG_MIN : " << LONG_MIN  << endl;    /*Print the minimum value of long .*/    cout << "LONG_MAX : " << LONG_MAX  << endl;    /*Print the maximum value of long .*/    cout << "ULONG_MAX : " <<  ULONG_MAX << endl;    /*Print the maximum value of unsigned long .*/    cout << "LLONG_MIN : " <<  LLONG_MIN << endl;    /*Print the minimum value of long long .*/    cout << "LLONG_MAX : " << LLONG_MAX  << endl;    /*Print the maximum value of long long .*/    cout << "ULLONG_MAX : " << ULLONG_MAX  << endl;     /*Print the maximum value of unsigned long long .*/ }/*Output CHAR_BIT : 8SCHAR_MIN : -128SCHAR_MAX : 127UCHAR_MAX : 255CHAR_MIN : -128CHAR_MAX : 127MB_LEN_MAX : 6SHRT_MIN : -32768SHRT_MAX : 32767USHRT_MAX : 65535INT_MIN : -2147483648INT_MAX : 2147483647UINT_MAX : 4294967295LONG_MIN : -9223372036854775808LONG_MAX : 9223372036854775807ULONG_MAX : 18446744073709551615LLONG_MIN : -9223372036854775808LLONG_MAX : 9223372036854775807ULLONG_MAX : 18446744073709551615 */`

The sizeof operator , returns the number of bytes, which are reserved for a given type , hence it can be used to get the number of bytes , reserved for integer types .

`#include<iostream>int main(void ){    using std::cout;    using std::endl;    cout << sizeof(short ) << endl;    //Output on this machine : 2    cout << sizeof(int ) << endl;    //Output on this machine : 4    cout << sizeof(long ) << endl ;    //Output on this machine : 8    cout << sizeof(long long ) << endl;    /*Output on this machine : 8 */ }`

# C++ integer literals types

An integer literal such as , can be written in base , , , or , as follows :

`#include<iostream>int main(void ){    using std::cout;    using std::endl;    cout << "17 in binary is : " << 0b1'00'01 << endl;    /*Binary literals start by 0B , case insensitive ,    a single quote can be use in any integer    literal for readability .*/    cout << "17 in octal is : " << 021 << endl;    /*Octal literals start by 0 .*/    cout << "17 in hexadecimal is : " << 0x11 << endl;    /*Hexadecimal literals start by    0X , case insensitive .*/    cout << "17 in decimal is : " << 17 << endl;    /*Decimal literals must not start by    0 .*//*Output :17 in binary is : 1717 in octal is : 1717 in hexadecimal is : 1717 in decimal is : 17 */ }`

Since C++ is a typed language , an integer literal has a type. An integer literal is always non negative , the negation operator , is applied on the gotten integer literal type .

Decimal integer literals , have a default type of, if too large to fit in an , they will have a type , if too large to fit in a , they will have the type . If still too large to fit , and the implementation defines extended integer types , they are tried , as described , if still too large , the behavior is implementation defined .

Binary , octal , and hexadecimal integer literals , have a default type of, if too large to fit in an , they will have a type of , if too large to fit in an , they will have a type of , next , next , next , next if the implementation defines extended integer types , they are tried as stated , if still too large , the behavior is implementation defined .

`#include<iostream>int main(void ){    using std::cout;    using std::endl;    unsigned int var_i = -2147483648;    /*int on this machine has a range of    [-2147483648 , 2147483647 ] .    2147483648 is larger than INT_MAX ,    2147483648 is a decimal integer literal ,    hence long int is tried .    long int on this machine has a range of    [-9223372036854775808 , 9223372036854775807 ]    , hence 2147483648 is of type long int .    The negation operator is applied on    2147483648 , as such -2147483648 , is gotten .    var_i is an unsigned int , and the gotten    value is a long . Hence the gotten value    is converted first to unsigned long , bits    are kept as is , just reinterpreted , so    the converted value is 2147483648 in    unsigned long , next the converted value    is truncated to an unsigned int , the value     is preserved in truncation . */    cout << var_i << endl ;    /*Output :    2147483648 .*/    var_i = -0x80000000;    /*0x80000000 in hexadecimal is equal to    2147483648 in decimal .     First int is tried .    On this machine it has a range of    [-2147483648 , 2147483647 ] , next    unsigned int is tried .     On this machine  , unsigned int has     a range of [0 , 4294967295 ]  ,    hence 0x80000000 is of type unsigned     int .    The negation operator is applied ,     modulo 4294967296 is applied , and the     result is 2147483648 .*/    cout << var_i << endl ;    /*Output :        2147483648 .*/ }`

The suffixes , and , case insensitive , can be used with an integer literal , to state that it is of type , or . In such cases , and to determine the type of the integer literal , the compiler starts from , or , depending on the suffix , and try the next types , as described earlier .

The suffix , case insensitive , can be applied to an integer literal , to state that it is unsigned . In such case , is first tried , followed by , followed by . If still too large , and the implementation defines extended integer types , the extended integer types are tried , as stated , if still too large , the behavior is implementation defined .

The suffix can be used with the suffixes , and to state that an integer literal is , or . In such a case , the compiler tries , the next larger unsigned type , if the literal is too large to fit ,and if no unsigned integer type can fit the literal , then the behavior is implementation defined .

`#include<iostream>int main(void ){    using std::cout;    using std::endl;    int var_i = 2147483648u;    /*The suffix u is used , as such    the integer literal 2147483648    is of an unsigned integer type .    unsigned int , on this machine    has a range of [0 , 4294967295 ]    , 2147483648 can fit in this range ,    so 2147483648 is of type unsigned int .    var_i is a signed int , as such the    gotten unsigned value , is reinterpreted     as being signed .*/    cout << var_i << endl ;    /*Output    -2147483648 */    var_i = 9223372036854775807L ;    /*9223372036854775807 is suffixed with    L , as such long is first tried .    Long on this machine , has a range    [-9223372036854775808 , 9223372036854775807 ] .    It can hold 9223372036854775807 , so the    integer literal is of type long .    var_i is of type int , as such ,    the gotten long value is truncated ,    and the result is -1 .*/    cout << var_i << endl ;    /*Output :    -1 */    auto var_ul = 1lu;    /*The integer literal 1 , is suffixed     with lu , so it is of the unsigned long     type .     auto is used , as not to write the    type of var_ul , since the integer    literal is of type unsigned long , hence    var_ul , is of type unsigned long .     ul could have been used instead of lu .*/ }`

# C++ Standard library integer types

The integer types defined by the C++ standard , are defined to have a least width , as such a least range , so the range of a standard integer type , is not uniform across all implementations .

For example, on a bit architecture , have typically a width of bits , whereas on a bits architecture , has typically a width of bits .

The question to ask is as such , what if what was needed , is to have a fixed length width , for an integer type , across all implementations ?

Thestandard library header , defines fixed width integer types, they are :

`int8_t uint8_t/*Fixed width 8 bits signed and   unsigned integers .*/int16_t uint16_t /*Fixed width 16 bits signed and   unsigned integers .*/int32_tuint32_t/*Fixed width 32 bits signed and   unsigned integers .*/int64_tuint64_t/*Fixed width 64 bits signed and   unsigned integers .*/`

The fixed width integer types , are optional, so it is not necessary for an implementation to provide them .

What about , if what was needed , is an integer type , for which a processor in an execution environment , so where the program is being executed , is faster to perform operations, and this integer type , is to be of a minimum length ? The standard library header , defines the following integer types , that fit these requirements :

`int_fast8_tuint_fast8_t/*Fastest , signed , unsigned integer   types , that have at least 8 bits .*/int_fast16_tuint_fast6_t/*Fastest , signed , unsigned integer   types , that have at least 16 bits .*/int_fast32_tuint_fast32_t/*Fastest , signed , unsigned integer   types , that have at least 32 bits .*/int_fast64_tuint_fast64_t/*Fastest , signed , unsigned integer   types , that have at least 64 bits .*/`

Finally what if what was needed , is to have the largest integer type , available on an implementation . To fulfill , this requirement , the header defines :

`intmax_tuintmax_t/*widest signed , unsigned , integer types   available on an implementation .*/`

Originally published at https://twiserandom.com on February 22, 2021.