Code Yarns β€πŸ‘¨β€πŸ’»
Tech Blog ❖ Personal Blog

Integer types in C++

πŸ“… 2015-Oct-12 ⬩ ✍️ Ashwin Nanjappa ⬩ 🏷️ cstdint, integer ⬩ πŸ“š Archive

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:

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 β€’ πŸ“§