Skip to content

Instantly share code, notes, and snippets.

@z-ninja
Created November 24, 2021 22:18
Show Gist options
  • Save z-ninja/d189449fea85c9e43e949c151c0ff68b to your computer and use it in GitHub Desktop.
Save z-ninja/d189449fea85c9e43e949c151c0ff68b to your computer and use it in GitHub Desktop.
Endian conversion, byte swap for big and little endian.
#include <cstdint>
#include <type_traits>
#if __cplusplus > 201703L
#include <bit>
namespace Endian {
template <typename T>
struct alignas(alignof(T)) type_bytes {
unsigned char u8[sizeof(T)];
};
class Endian {
private:
Endian() = delete;
using bytes = type_bytes<uint32_t>;
static constexpr uint32_t u32{0x01020200u};
static constexpr const bytes ub = std::bit_cast<bytes>(u32);
public:
static constexpr const unsigned char uc = ub.u8[0];
};
} // namespace Endian
#else
#include <cstring>
// there is no other way except runtime for std < c++20
//then using compiler macros, btw, taken from boost library
// https://github.com/boostorg/core/blob/develop/include/boost/core/bit.hpp#L524
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \
__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define BIT_NATIVE_INITIALIZER = little
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \
__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define BIT_NATIVE_INITIALIZER = big
#elif defined(__BYTE_ORDER__) && defined(__ORDER_PDP_ENDIAN__) && \
__BYTE_ORDER__ == __ORDER_PDP_ENDIAN__
#define BIT_NATIVE_INITIALIZER = pop;
#elif defined(__LITTLE_ENDIAN__)
#define BIT_NATIVE_INITIALIZER = little
#elif defined(__BIG_ENDIAN__)
#define BIT_NATIVE_INITIALIZER = big
#elif defined(_MSC_VER) || defined(__i386__) || defined(__x86_64__)
#define BIT_NATIVE_INITIALIZER = little
#else
#define BIT_NATIVE_INITIALIZER = 0x03
#endif
#endif
namespace Endian {
enum class endian : unsigned char {
little = 0x00,
big = 0x01,
pop = 0x02,
#if __cplusplus > 201703L
byte_order = Endian::uc
#else
byte_order BIT_NATIVE_INITIALIZER
#endif
};
#if __cplusplus > 201703L
namespace test {
constexpr const endian getEndianCPP20() {
if constexpr (std::endian::native == std::endian::little)
return endian::little;
else if constexpr (std::endian::native == std::endian::big)
return endian::big;
else
return endian::byte_order;
}
} // namespace test
/// check if we have a match
static_assert(test::getEndianCPP20() == endian::byte_order);
#endif
/// check if we have a match
static_assert(endian::byte_order == endian::little ||
endian::byte_order == endian::big ||
endian::byte_order == endian::pop,
"Unable to detect target endianess");
template <typename T>
constexpr T byte_swap(T value) noexcept {
if constexpr (sizeof(value) < 2) {
return value;
}
if constexpr (endian::byte_order == endian::big ||
endian::byte_order == endian::little) {
if constexpr (std::is_floating_point<T>::value) {
if constexpr (sizeof(T) == 4) {
#if __cplusplus > 201703L
using type = uint32_t;
type ret = std::bit_cast<type>(value);
ret = byte_swap<type>(ret);
value = std::bit_cast<T>(ret);
#else
using type = uint32_t;
type ret = 0;
std::memcpy(&ret, &value, sizeof(type));
ret = byte_swap<type>(ret);
std::memcpy(&value, &ret, sizeof(type));
#endif
} else if constexpr (sizeof(T) == 8) {
#if __cplusplus > 201703L
using type = uint64_t;
type ret = std::bit_cast<type>(value);
ret = byte_swap<type>(ret);
value = std::bit_cast<T>(ret);
#else
using type = uint64_t;
type ret = 0;
std::memcpy(&ret, &value, sizeof(type));
ret = byte_swap<type>(ret);
std::memcpy(&value, &ret, sizeof(type));
#endif
} /*else if constexpr (sizeof(T) == 16 && sizeof(__uint128_t) == 16)
{
#if __cplusplus > 201703L
using type128 = __uint128_t;
static_assert(sizeof(type128) == sizeof(T));
type128 ret = std::bit_cast<type128>(value);
ret = byte_swap<type128>(ret);
value = std::bit_cast<T>(ret);
return value;
#else
#endif
} */
return value;
} else {
if constexpr (sizeof(T) == 2) {
return ((value & 0x00FFu) << 8) | ((value & 0xFF00u) >> 8);
} else if constexpr (sizeof(T) == 4) {
return ((value & 0x000000FFu) << 24) |
((value & 0x0000FF00u) << 8) |
((value & 0x00FF0000u) >> 8) |
((value & 0xFF000000u) >> 24);
} else if constexpr (sizeof(T) == 8) {
return ((value & 0xFF00000000000000ull) >> 56) |
((value & 0x00FF000000000000ull) >> 40) |
((value & 0x0000FF0000000000ull) >> 24) |
((value & 0x000000FF00000000ull) >> 8) |
((value & 0x00000000FF000000ull) << 8) |
((value & 0x0000000000FF0000ull) << 24) |
((value & 0x000000000000FF00ull) << 40) |
((value & 0x00000000000000FFull) << 56);
} /*else if constexpr (sizeof(T) == 16) {
}*/
}
} else {
/// pop not implemented yet
}
}
namespace test {
static_assert(byte_swap<uint16_t>(0x0001u) == 0x0100u);
static_assert(byte_swap<int16_t>(0x0001) == 0x0100);
static_assert(byte_swap<int16_t>(byte_swap<int16_t>(-24546)) == -24546);
static_assert(byte_swap<uint32_t>(0x00000001u) == 0x01000000u);
static_assert(byte_swap<int32_t>(0x00000001) == 0x01000000);
static_assert(byte_swap<int32_t>(byte_swap<int32_t>(-2454346)) == -2454346);
static_assert(byte_swap<uint64_t>(0x0000000000000001ull) ==
0x0100000000000000ull);
static_assert(byte_swap<int64_t>(0x0000000000000001ll) == 0x0100000000000000ll);
static_assert(byte_swap<int64_t>(byte_swap<int64_t>(-245476346ll)) ==
-245476346ll);
#if __cplusplus > 201703L
static constexpr double d = 65536.0;
static constexpr double da = byte_swap<double>(d);
static constexpr type_bytes<double> db1 = std::bit_cast<type_bytes<double>>(d);
static constexpr type_bytes<double> db = std::bit_cast<type_bytes<double>>(da);
static_assert(d == byte_swap<double>(da));
// check double as oposite endian
static_assert(
(endian::byte_order == endian::little)
? (db.u8[0] == 0x40 && db.u8[1] == 0xf0 && db.u8[2] == 0x00)
: (endian::byte_order == endian::big)
? (db.u8[7] == 0x40 && db.u8[6] == 0xf0 && db.u8[5] == 0x00)
: true // pop not supported, so avoiding compile error because
// maybe user is not attempting to use byte_swap
);
// check double as native
static_assert(
(endian::byte_order == endian::little)
? (db1.u8[7] == 0x40 && db1.u8[6] == 0xf0 && db1.u8[5] == 0x00)
: (endian::byte_order == endian::big)
? (db1.u8[0] == 0x40 && db1.u8[1] == 0xf0 && db1.u8[2] == 0x00)
: true // pop not supported, so avoiding compile error since user is
// not attenting to use byte_swap
);
static constexpr float f = 65536.0f;
static constexpr float fa = byte_swap<float>(f);
static constexpr type_bytes<float> fb1 = std::bit_cast<type_bytes<float>>(f);
static constexpr type_bytes<float> fb = std::bit_cast<type_bytes<float>>(fa);
static_assert(f == byte_swap<float>(fa));
/// check float as opposite edian
static_assert(
(endian::byte_order == endian::little)
? (fb.u8[0] == 0x47 && fb.u8[1] == 0x80 && fb.u8[2] == 0x00 &&
fb.u8[3] == 0x00)
: (endian::byte_order == endian::big)
? (fb.u8[3] == 0x47 && fb.u8[2] == 0x80 && fb.u8[1] == 0x00 &&
fb.u8[0] == 0x00)
: true // pop not supported, so avoiding compile error because
// maybe user is not attempting to use byte_swap
);
/// check float as native
static_assert(
(endian::byte_order == endian::little)
? (fb1.u8[3] == 0x47 && fb1.u8[2] == 0x80 && fb1.u8[1] == 0x00 &&
fb1.u8[0] == 0x00)
: (endian::byte_order == endian::big)
? (fb1.u8[0] == 0x47 && fb1.u8[1] == 0x80 && fb1.u8[2] == 0x00 &&
fb1.u8[3] == 0x00)
: true // pop not supported, so avoiding compile error because
// maybe user is not attempting to use byte_swap
);
#endif
} // namespace test
// from host byte order to big endian
template <typename T>
constexpr T htoBE(T value) noexcept {
if constexpr (endian::byte_order == endian::big) {
return value;
} else {
if constexpr (sizeof(value) < 2) {
return value;
}
return byte_swap<T>(value);
}
}
// from host byte order to little endian
template <typename T>
constexpr T htoLE(T value) noexcept {
if constexpr (endian::byte_order == endian::little) {
return value;
} else {
if constexpr (sizeof(value) < 2) {
return value;
}
return byte_swap<T>(value);
}
}
} // namespace Endian
typedef Endian::endian endian;
#include <cassert>
#include <iostream>
#if __cplusplus <= 201703L
// runtime test for floats for std < c++20 since std::memcpy
// is used for floats for std < c++20
void runtime_test_floats();
#endif
int main(int argc, char* argv[]) {
#if __cplusplus <= 201703L
runtime_test_floats();
#endif
if constexpr (endian::byte_order == endian::little) {
std::cout << "Little Endian" << std::endl;
} else if constexpr (endian::byte_order == endian::big) {
std::cout << "Big Endian" << std::endl;
} else if constexpr (endian::byte_order == endian::pop) {
std::cout << "Pop Endian" << std::endl;
} else {
// unknown endian
std::cout << "Unknown Endian" << std::endl;
}
return 0;
}
#if __cplusplus <= 201703L
void runtime_test_floats() {
{
double d = 65536.0;
double d2 = Endian::byte_swap<double>(d);
double d3 = Endian::byte_swap<double>(d2);
assert(d != d2);
assert(d == d3);
unsigned char* db = reinterpret_cast<unsigned char*>(&d2);
unsigned char* db1 = reinterpret_cast<unsigned char*>(&d);
// check double as oposite at runtime
assert((endian::byte_order == endian::little)
? (db[0] == 0x40 && db[1] == 0xf0 && db[2] == 0x00)
: (endian::byte_order == endian::big)
? (db[7] == 0x40 && db[6] == 0xf0 && db[5] == 0x00)
: true);
// check double as native at runtime
assert((endian::byte_order == endian::little)
? (db1[7] == 0x40 && db1[6] == 0xf0 && db1[5] == 0x00)
: (endian::byte_order == endian::big)
? (db1[0] == 0x40 && db1[1] == 0xf0 && db1[2] == 0x00)
: true);
}
{
float f = 65536.0f;
float f2 = Endian::byte_swap<float>(f);
float f3 = Endian::byte_swap<float>(f2);
unsigned char* fb1 = reinterpret_cast<unsigned char*>(&f);
unsigned char* fb = reinterpret_cast<unsigned char*>(&f2);
assert(f != f2);
assert(f == f3);
// check float as oposite at runtime
assert((endian::byte_order == endian::little)
? (fb[0] == 0x47 && fb[1] == 0x80 && fb[2] == 0x00 &&
fb[3] == 0x00)
: (endian::byte_order == endian::big)
? (fb[3] == 0x47 && fb[2] == 0x80 && fb[1] == 0x00 &&
fb[0] == 0x00)
: true);
/// check float as native at runtime
assert((endian::byte_order == endian::little)
? (fb1[3] == 0x47 && fb1[2] == 0x80 && fb1[1] == 0x00 &&
fb1[0] == 0x00)
: (endian::byte_order == endian::big)
? (fb1[0] == 0x47 && fb1[1] == 0x80 && fb1[2] == 0x00 &&
fb1[3] == 0x00)
: true);
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment