Integer types in C++

Traditional programmers are familiar with `int`, `long` and such integer types. These are known to have different sizes on different systems.

However, a lot of code requires integer types to be exactly a certain size or of at least a certain size. In such situations, it is a good idea to use the fixed width integer types provided by C++ through the header file `cstdlib`.

Types of integers

C++ provides three types of fixed width integer types:

• Integer of precisely the size you specify (Ex: `int32_t`)

• Smallest integer whose size is at least the size you specify (Ex: `int_least32_t`)

• Fastest integer whose size is at least the size you specify (Ex: `int_fast32_t`)

List of the signed and unsigned integer types is:

``````int8_t
int16_t
int32_t
int64_t
uint8_t
uint16_t
uint32_t
uint64_t

int_least8_t
int_least16_t
int_least32_t
int_least64_t
uint_least8_t
uint_least16_t
uint_least32_t
uint_least64_t

int_fast8_t
int_fast16_t
int_fast32_t
int_fast64_t
uint_fast8_t
uint_fast16_t
uint_fast32_t
uint_fast64_t``````

Why these types?

When we specify that we want an integer to have exactly 32 bits, say `int32_t`, that may not be supported on certain exotic systems. Assume a computer which supports only 34-bit and 68-bit integers. That system cannot provide a 32 bit integer. Code which uses `int32_t` will not compile. This is good because the programmer who used `int32_t` might have been assuming the type to have exactly 32 bits, for example, if he is looping over the 32 bits to compute something. Such code should fail, else it would be a bug.

Now the use of a precise width type, like `int32_t`, provides the reason for the other two types of integers: smallest and fastest.

The smallest integer type, say `int_least32_t`, should be used when our code requires to store values that need at least 32 bits, but our first priority is space and not speed of computation. For example, assume that the exotic system described above does 68-bit integer addition faster than 34-bit addition. In such a case, when we use `int_least32_t`, we are asking it use the 34-bit type, foregoing speed for space.

The fastest integer type, say `int_fast32_t`, should be used when our code requires to store values that need at least 32 bits, but our first priority is speed and not space. For example, in the exotic system described above when we use `int_fast32_t`, that systemβs compiler will use the 68-bit type, providing the fastest integer addition, but by increasing space occupied by our program.

On x86_64 Linux

I now come back from a world of exotic computers to the common 64-bit x86_64 Intel processor system Iβm using. On GCC C++ compiler in Linux, I found that the smallest types (like `int_least32_t`) were of the same size as the precise type (like `int32_t`). No surprises there cause x86_64 has integers of size 8, 16, 32 and 64.

You will get a surprise if you use the fast types though! Except for the 8-bit fast types, the rest are all mapped to 64-bit signed and unsigned integers for speed. So, a `int_fast16_t` is mapped to a 64-bit signed integer.

You can print out the sizes of all the fixed width integer types, by compiling and running a simple program, I have shared here:

``````#include <cstdint>
#include <iostream>

int main()
{
std::cout << "Precise" << std::endl;

std::cout << sizeof(int8_t  ) << std::endl;
std::cout << sizeof(int16_t ) << std::endl;
std::cout << sizeof(int32_t ) << std::endl;
std::cout << sizeof(int64_t ) << std::endl;
std::cout << sizeof(uint8_t ) << std::endl;
std::cout << sizeof(uint16_t) << std::endl;
std::cout << sizeof(uint32_t) << std::endl;
std::cout << sizeof(uint64_t) << std::endl;

std::cout << "Least" << std::endl;

std::cout << sizeof(int_least8_t  ) << std::endl;
std::cout << sizeof(int_least16_t ) << std::endl;
std::cout << sizeof(int_least32_t ) << std::endl;
std::cout << sizeof(int_least64_t ) << std::endl;
std::cout << sizeof(uint_least8_t ) << std::endl;
std::cout << sizeof(uint_least16_t) << std::endl;
std::cout << sizeof(uint_least32_t) << std::endl;
std::cout << sizeof(uint_least64_t) << std::endl;

std::cout << "Fast" << std::endl;

std::cout << sizeof(int_fast8_t  ) << std::endl;
std::cout << sizeof(int_fast16_t ) << std::endl;
std::cout << sizeof(int_fast32_t ) << std::endl;
std::cout << sizeof(int_fast64_t ) << std::endl;
std::cout << sizeof(uint_fast8_t ) << std::endl;
std::cout << sizeof(uint_fast16_t) << std::endl;
std::cout << sizeof(uint_fast32_t) << std::endl;
std::cout << sizeof(uint_fast64_t) << std::endl;

return 0;
}``````

Another method is to look at the source code. On my x86_64 Linux with GCC 5, the `cstdint` trail leads to `/usr/include/stdint.h`, where I find these definitions for the fast signed types:

``````/* Fast types.  */

/* Signed.  */
typedef signed char     int_fast8_t;
#if __WORDSIZE == 64
typedef long int        int_fast16_t;
typedef long int        int_fast32_t;
typedef long int        int_fast64_t;
#else
typedef int         int_fast16_t;
typedef int         int_fast32_t;
__extension__
typedef long long int       int_fast64_t;
#endif``````

Tried with: GCC 5.1, Ubuntu 14.04 and x86_64 Intel CPU

Β© 2022 Ashwin Nanjappa β’ All writing under CC BY-SA license β’ π @codeyarns@hachyderm.io β’ π§