HOWTO · C++
Use of 128-Bit Integer in C++
This tutorial discusses 128-bit integer in C++, its importance and possible alternatives.
On this page
C++ has no standard built-in integer type that is guaranteed to hold exactly 128 bits. If you need a wider value, choose between a compiler extension for arithmetic, a pair of fixed-width words for storage, or a big-integer library when calculations can grow beyond a fixed width.
Use of 128-Bit Integers in C++
A 128-bit unsigned value can represent numbers from 0 through 2^128 − 1; a signed two’s-complement value has a different range. Whether a type is available for these values depends on the implementation. C++ does not require int, long, or long long to be 64 bits, so check the target implementation rather than assuming their widths.
First decide whether you need to store 128 bits or perform arithmetic on them. A representation made from two words can hold the bits, but addition, multiplication, comparison, and conversion need their own implementation. Fixed-width arithmetic also cannot hold every result: multiplying two 128-bit values can require up to 256 bits.
Choosing a 128-Bit Integer Approach
Use the compiler’s __int128 extension when your compiler and target support it and you need ordinary integer arithmetic within a fixed 128-bit range. It is non-standard, so code that uses it may need a different implementation on another compiler or target.
For storage, use two uint64_t members, an array of two 64-bit words, or an array of 16 bytes. These layouts hold 128 bits but do not define how to perform arithmetic or serialize the value. If the required precision can grow beyond 128 bits, use an arbitrary-precision library. The big-integer guide describes that alternative.
Use GCC’s __int128 Extension
GCC provides __int128 on supported targets. The following complete program uses the extension for a small addition and asks sizeof for its size. It is a compiler-specific example; compile it only with a GCC-compatible compiler and target that support the extension. No runtime result is supplied here.
Run this example:
#include <iostream>
int main() {
__int128 value = 20;
value += 22;
std::cout << "Value: " << static_cast<long long>(value) << '\n';
std::cout << "Size: " << sizeof(value) << " bytes\n";
return 0;
}
The cast in this example is safe for the chosen value, but it is not a general way to print an arbitrary __int128. Standard iostreams do not provide a formatter for the extension. For larger values, write a decimal or hexadecimal conversion routine, or use a library with suitable formatting support. sizeof reports bytes occupied by the object; it does not by itself describe padding in every type or guarantee an extension’s availability.
Represent a 128-Bit Value with Two 64-Bit Integers
A structure with two uint64_t members is a simple storage representation. This complete program initializes both words and reports the structure’s size. Since each member is exactly 64 bits when uint64_t is provided, their values together represent 128 bits; sizeof reports the structure size in bytes, which may include implementation-defined padding in general.
Run this example:
#include <cstdint>
#include <iostream>
struct UInt128Words {
std::uint64_t low;
std::uint64_t high;
};
int main() {
UInt128Words value{0x123456789ABCDEF0ULL, 0x0FEDCBA987654321ULL};
std::cout << "Words: " << value.high << ' ' << value.low << '\n';
std::cout << "Size: " << sizeof(value) << " bytes\n";
return 0;
}
The two fields provide a place to keep the high and low words; they do not automatically behave as one integer. To add two such values, for example, code must add the low words and carry into the high word. Use a byte array when the task is simply to retain or transmit 16 bytes, and define the byte order in any file or network format.
Inspect a 128-Bit Value in Hexadecimal and as Bytes
This example prints a two-word value as 32 hexadecimal digits and copies its object representation into 16 bytes. The hexadecimal number is assembled as high followed by low; the byte sequence reflects the host’s byte order and the structure’s memory layout, so it is not a portable serialization format. The example has not been executed, and its byte output will vary with the host representation.
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <iostream>
struct UInt128Words {
std::uint64_t low;
std::uint64_t high;
};
int main() {
UInt128Words value{0x123456789ABCDEF0ULL, 0x0FEDCBA987654321ULL};
std::uint8_t bytes[sizeof(value)];
std::memcpy(bytes, &value, sizeof(value));
std::cout << "Hex: " << std::hex << std::setfill('0')
<< std::setw(16) << value.high << std::setw(16) << value.low
<< '\n';
std::cout << "Object bytes: ";
for (std::uint8_t byte : bytes) {
std::cout << std::setw(2) << static_cast<unsigned int>(byte) << ' ';
}
std::cout << '\n';
return 0;
}