Created
November 24, 2021 22:18
-
-
Save z-ninja/d189449fea85c9e43e949c151c0ff68b to your computer and use it in GitHub Desktop.
Endian conversion, byte swap for big and little endian.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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