Byte Order Conversions

#include <boost/int128/byte_conversions.hpp>

The library provides functions for converting uint128 and int128 to and from big-endian or little-endian byte order, which is what serializing a 128-bit value into a file format, a network packet, or a database column requires. Unlike the functions in <bit>, every one of these has both an unsigned and a signed overload: reversing a two’s complement bit pattern is the same operation for either sign.

All of these functions are constexpr, and are available using C++14 like the rest of the library.

There are two families:

  • to_be / from_be / to_le / from_le operate on whole values. On the platform whose byte order is being asked for they are the identity, and on the other they are a byteswap. The result of to_be and to_le is a value whose object representation is in the requested order, so it is meant to be written out (or `memcpy’d) rather than read as a number.

  • to_*bytes / from*_bytes operate on 16-byte arrays. These never depend on the host byte order (with the sole exception of the ne pair), so to_be_bytes returns the most significant byte first on every platform.

The byte arrays are built with shifts rather than from the object representation, so the byte-array functions give identical results on little-endian and big-endian hosts.

Byte Array Element Type

The array functions are templated on the element type, which defaults to std::uint8_t. The accepted types are char, signed char, unsigned char, and, when the standard library provides it (C++17), std::byte:

const auto bytes {boost::int128::to_be_bytes(value)};              // std::array<std::uint8_t, 16>
const auto as_byte {boost::int128::to_be_bytes<std::byte>(value)}; // std::array<std::byte, 16>
const auto as_char {boost::int128::to_be_bytes<char>(value)};      // std::array<char, 16>
The number of bytes is fixed at sizeof(T), which is 16, so the from_*_bytes functions do not need a length argument. The std::array overloads reject a mismatched size at compile time, and the pointer overloads read exactly 16 bytes from the address given. A std::span, std::vector, or any other contiguous range is passed by handing .data() to the pointer overload.
In a CUDA translation unit the pointer overloads of from_be_bytes, from_le_bytes, and from_ne_bytes are the portable choice. The std::array overloads read the array through std::array::operator[], which is a constexpr host function, so nvcc warns (#20013-D) unless it is given --expt-relaxed-constexpr. The warning appears even when the call itself is host code, because nvcc compiles the device side of a host device template as well. Writing bytes with to_be_bytes, to_le_bytes, or to_ne_bytes is unaffected: the returned array is constructed and assigned without calling any of its member functions.

to_be

Converts a value from the native byte order to big-endian byte order. On a big-endian platform the value is returned unchanged, and on a little-endian platform byteswap is applied.

namespace boost {
namespace int128 {

BOOST_INT128_HOST_DEVICE constexpr uint128 to_be(uint128 value) noexcept;
BOOST_INT128_HOST_DEVICE constexpr int128 to_be(int128 value) noexcept;

} // namespace int128
} // namespace boost

from_be

Converts a value from big-endian byte order to the native byte order. This is the inverse of to_be, and since a byte reversal is its own inverse it delegates directly to to_be.

namespace boost {
namespace int128 {

BOOST_INT128_HOST_DEVICE constexpr uint128 from_be(uint128 value) noexcept;
BOOST_INT128_HOST_DEVICE constexpr int128 from_be(int128 value) noexcept;

} // namespace int128
} // namespace boost

to_le

Converts a value from the native byte order to little-endian byte order. On a little-endian platform the value is returned unchanged, and on a big-endian platform byteswap is applied.

namespace boost {
namespace int128 {

BOOST_INT128_HOST_DEVICE constexpr uint128 to_le(uint128 value) noexcept;
BOOST_INT128_HOST_DEVICE constexpr int128 to_le(int128 value) noexcept;

} // namespace int128
} // namespace boost

from_le

Converts a value from little-endian byte order to the native byte order. This is the inverse of to_le, and delegates directly to it.

namespace boost {
namespace int128 {

BOOST_INT128_HOST_DEVICE constexpr uint128 from_le(uint128 value) noexcept;
BOOST_INT128_HOST_DEVICE constexpr int128 from_le(int128 value) noexcept;

} // namespace int128
} // namespace boost

to_be_bytes

Returns the 16 bytes of the value with the most significant byte first, on every platform.

namespace boost {
namespace int128 {

template <typename ByteType = std::uint8_t>
BOOST_INT128_HOST_DEVICE constexpr std::array<ByteType, sizeof(uint128)> to_be_bytes(uint128 value) noexcept;

template <typename ByteType = std::uint8_t>
BOOST_INT128_HOST_DEVICE constexpr std::array<ByteType, sizeof(int128)> to_be_bytes(int128 value) noexcept;

} // namespace int128
} // namespace boost

from_be_bytes

Reconstructs a value from 16 bytes in big-endian order. The target type is given explicitly and must be uint128 or int128.

namespace boost {
namespace int128 {

template <typename T, typename ByteType, std::size_t N>
BOOST_INT128_HOST_DEVICE constexpr T from_be_bytes(const std::array<ByteType, N>& bytes) noexcept;

template <typename T, typename ByteType>
BOOST_INT128_HOST_DEVICE constexpr T from_be_bytes(const ByteType* bytes) noexcept;

} // namespace int128
} // namespace boost

The std::array overload requires N == sizeof(T), and any other size is a static_assert failure. The pointer overload reads sizeof(T) bytes starting at bytes, and the caller is responsible for that many bytes being readable.

to_le_bytes

Returns the 16 bytes of the value with the least significant byte first, on every platform.

namespace boost {
namespace int128 {

template <typename ByteType = std::uint8_t>
BOOST_INT128_HOST_DEVICE constexpr std::array<ByteType, sizeof(uint128)> to_le_bytes(uint128 value) noexcept;

template <typename ByteType = std::uint8_t>
BOOST_INT128_HOST_DEVICE constexpr std::array<ByteType, sizeof(int128)> to_le_bytes(int128 value) noexcept;

} // namespace int128
} // namespace boost

from_le_bytes

Reconstructs a value from 16 bytes in little-endian order. The size requirements match from_be_bytes.

namespace boost {
namespace int128 {

template <typename T, typename ByteType, std::size_t N>
BOOST_INT128_HOST_DEVICE constexpr T from_le_bytes(const std::array<ByteType, N>& bytes) noexcept;

template <typename T, typename ByteType>
BOOST_INT128_HOST_DEVICE constexpr T from_le_bytes(const ByteType* bytes) noexcept;

} // namespace int128
} // namespace boost

to_ne_bytes

Returns the 16 bytes of the value in the native byte order, which is the object representation of the value. Delegates to to_le_bytes on a little-endian platform and to to_be_bytes on a big-endian one, so this is the only byte-array function whose result varies across platforms.

namespace boost {
namespace int128 {

template <typename ByteType = std::uint8_t>
BOOST_INT128_HOST_DEVICE constexpr std::array<ByteType, sizeof(uint128)> to_ne_bytes(uint128 value) noexcept;

template <typename ByteType = std::uint8_t>
BOOST_INT128_HOST_DEVICE constexpr std::array<ByteType, sizeof(int128)> to_ne_bytes(int128 value) noexcept;

} // namespace int128
} // namespace boost

from_ne_bytes

Reconstructs a value from 16 bytes in the native byte order. Delegates to from_le_bytes on a little-endian platform and to from_be_bytes on a big-endian one. The size requirements match from_be_bytes.

namespace boost {
namespace int128 {

template <typename T, typename ByteType, std::size_t N>
BOOST_INT128_HOST_DEVICE constexpr T from_ne_bytes(const std::array<ByteType, N>& bytes) noexcept;

template <typename T, typename ByteType>
BOOST_INT128_HOST_DEVICE constexpr T from_ne_bytes(const ByteType* bytes) noexcept;

} // namespace int128
} // namespace boost

Examples

Example 1. This example demonstrates the byte order conversion functions
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/int128/int128.hpp>
#include <boost/int128/byte_conversions.hpp>
#include <boost/int128/iostream.hpp>
#include <array>
#include <cstddef>
#include <cstdint>
#include <iomanip>
#include <iostream>

// Prints the bytes of an array in the order they are stored
template <typename Bytes>
void print_bytes(const char* label, const Bytes& bytes)
{
    std::cout << label;

    for (const auto byte : bytes)
    {
        std::cout << ' ' << std::hex << std::setfill('0') << std::setw(2) << static_cast<unsigned>(byte);
    }

    std::cout << std::dec << std::endl;
}

int main()
{
    using boost::int128::uint128;
    using boost::int128::int128;

    // The 16 bytes 01 02 ... 10 read as a big-endian value
    constexpr uint128 value {UINT64_C(0x0102030405060708), UINT64_C(0x090A0B0C0D0E0F10)};

    std::cout << "=== Byte arrays ===" << std::endl;

    // The byte order of the array is the requested one on every platform
    print_bytes("to_be_bytes:", boost::int128::to_be_bytes(value));
    print_bytes("to_le_bytes:", boost::int128::to_le_bytes(value));

    // Native order is whichever of the two matches the host, so this is the
    // one form whose output depends on the platform
    print_bytes("to_ne_bytes:", boost::int128::to_ne_bytes(value));

    std::cout << "\n=== Reading a value back out of bytes ===" << std::endl;

    constexpr std::array<std::uint8_t, sizeof(uint128)> wire
    {{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2C
    }};

    // The target type is given explicitly, and the byte count has to match
    std::cout << "from_be_bytes: " << boost::int128::from_be_bytes<uint128>(wire) << std::endl;
    std::cout << "from_le_bytes: " << boost::int128::from_le_bytes<uint128>(wire) << std::endl;

    // Everything is constexpr, so a whole round-trip can be checked at compile time
    static_assert(boost::int128::from_be_bytes<uint128>(boost::int128::to_be_bytes(value)) == value,
                  "Round trip through big-endian bytes");

    std::cout << "\n=== Signed values ===" << std::endl;

    // The two's complement bit pattern is what gets reversed, so negative
    // values need no special handling
    constexpr int128 negative {-300};

    print_bytes("to_be_bytes(-300):", boost::int128::to_be_bytes(negative));
    std::cout << "from_be_bytes:     " << boost::int128::from_be_bytes<int128>(boost::int128::to_be_bytes(negative)) << std::endl;

    std::cout << "\n=== Whole value conversions ===" << std::endl;

    // to_be and to_le produce a value whose object representation is in the
    // requested order, which is what a memcpy into a packet buffer wants.
    // The value itself is only meaningful again after the matching from_be / from_le.
    const auto big_endian_image {boost::int128::to_be(value)};

    print_bytes("object representation of to_be(value):", boost::int128::to_ne_bytes(big_endian_image));
    std::cout << "from_be recovers: " << boost::int128::from_be(big_endian_image) << std::endl;
    std::cout << "value:            " << value << std::endl;

    // Any byte-like element type can be requested, which is convenient when the
    // surrounding buffer is not made of std::uint8_t
    const auto as_char {boost::int128::to_le_bytes<char>(value)};
    std::cout << "\nfrom_le_bytes over a char buffer: " << boost::int128::from_le_bytes<uint128>(as_char.data()) << std::endl;

    return 0;
}

Output (on a little-endian host, where the to_ne_bytes lines match to_le_bytes):

=== Byte arrays ===
to_be_bytes: 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10
to_le_bytes: 10 0f 0e 0d 0c 0b 0a 09 08 07 06 05 04 03 02 01
to_ne_bytes: 10 0f 0e 0d 0c 0b 0a 09 08 07 06 05 04 03 02 01

=== Reading a value back out of bytes ===
from_be_bytes: 300
from_le_bytes: 58491224111394833235396041148664381440

=== Signed values ===
to_be_bytes(-300): ff ff ff ff ff ff ff ff ff ff ff ff ff ff fe d4
from_be_bytes:     -300

=== Whole value conversions ===
object representation of to_be(value): 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10
from_be recovers: 1339673755198158349044581307228491536
value:            1339673755198158349044581307228491536

from_le_bytes over a char buffer: 1339673755198158349044581307228491536