Constexpr for unaligned_type (#1555)

This commit is contained in:
Roland Reichwein 2026-08-21 12:27:32 +02:00 committed by GitHub
parent 7966ba86df
commit 266b0f99c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 847 additions and 49 deletions

View File

@ -50,15 +50,22 @@ namespace etl
//***************************************************************************
template <typename TDestination, typename TSource>
ETL_NODISCARD
typename etl::enable_if< !(etl::is_integral<TDestination>::value && etl::is_integral<TSource>::value) && (sizeof(TDestination) == sizeof(TSource))
&& etl::is_trivially_copyable<TSource>::value && etl::is_trivially_copyable<TDestination>::value,
TDestination>::type bit_cast(const TSource& source) ETL_NOEXCEPT
#if ETL_USING_BUILTIN_BIT_CAST
ETL_CONSTEXPR14
#endif
typename etl::enable_if< !(etl::is_integral<TDestination>::value && etl::is_integral<TSource>::value) && (sizeof(TDestination) == sizeof(TSource))
&& etl::is_trivially_copyable<TSource>::value && etl::is_trivially_copyable<TDestination>::value,
TDestination>::type bit_cast(const TSource& source) ETL_NOEXCEPT
{
#if ETL_USING_BUILTIN_BIT_CAST
return __builtin_bit_cast(TDestination, source);
#else
TDestination destination;
memcpy(&destination, &source, sizeof(TDestination));
return destination;
#endif
}
//***************************************************************************

View File

@ -68,6 +68,10 @@ SOFTWARE.
#if !defined(ETL_USING_BUILTIN_IS_CONSTANT_EVALUATED)
#define ETL_USING_BUILTIN_IS_CONSTANT_EVALUATED 1
#endif
#if !defined(ETL_USING_BUILTIN_BIT_CAST)
#define ETL_USING_BUILTIN_BIT_CAST 1
#endif
#endif
#if defined(ETL_USE_BUILTIN_MEM_FUNCTIONS) // Set all of them to be true if not
@ -132,6 +136,10 @@ SOFTWARE.
#define ETL_USING_BUILTIN_IS_CONSTANT_EVALUATED __has_builtin(__builtin_is_constant_evaluated)
#endif
#if !defined(ETL_USING_BUILTIN_BIT_CAST)
#define ETL_USING_BUILTIN_BIT_CAST __has_builtin(__builtin_bit_cast)
#endif
#if !defined(ETL_USING_BUILTIN_MEMCPY)
#define ETL_USING_BUILTIN_MEMCPY __has_builtin(__builtin_memcpy)
#endif
@ -174,6 +182,13 @@ SOFTWARE.
#if !defined(ETL_USING_BUILTIN_IS_TRIVIALLY_COPYABLE)
#define ETL_USING_BUILTIN_IS_TRIVIALLY_COPYABLE 1
#endif
// MSVC has supported __builtin_bit_cast since VS2019 16.6. __has_builtin is not
// used for Microsoft (see above) to avoid a VS2022 intellisense issue, so the
// intrinsic is enabled here directly for the no-STL configuration.
#if !defined(ETL_USING_BUILTIN_BIT_CAST) && (_MSC_VER >= 1926)
#define ETL_USING_BUILTIN_BIT_CAST 1
#endif
#endif
// The default. Set to 0, if not already set.
@ -245,6 +260,17 @@ SOFTWARE.
#define ETL_USING_BUILTIN_BUILTIN_IS_CPP_TRIVIALLY_RELOCATABLE 0
#endif
#if !defined(ETL_USING_BUILTIN_BIT_CAST)
#define ETL_USING_BUILTIN_BIT_CAST 0
#endif
// __builtin_bit_cast is only of use to the ETL from C++11 onwards.
// Disable it for older standards, so that the generated code for those users is unchanged.
#if ETL_USING_BUILTIN_BIT_CAST && !ETL_USING_CPP11
#undef ETL_USING_BUILTIN_BIT_CAST
#define ETL_USING_BUILTIN_BIT_CAST 0
#endif
namespace etl
{
namespace traits
@ -268,6 +294,7 @@ namespace etl
static ETL_CONSTANT bool using_builtin_is_virtual_base_of = (ETL_USING_BUILTIN_IS_VIRTUAL_BASE_OF == 1);
static ETL_CONSTANT bool using_builtin_is_trivially_relocatable = (ETL_USING_BUILTIN_IS_TRIVIALLY_RELOCATABLE == 1);
static ETL_CONSTANT bool using_builtin_builtin_is_cpp_trivially_relocatable = (ETL_USING_BUILTIN_BUILTIN_IS_CPP_TRIVIALLY_RELOCATABLE == 1);
static ETL_CONSTANT bool using_builtin_bit_cast = (ETL_USING_BUILTIN_BIT_CAST == 1);
} // namespace traits
} // namespace etl

View File

@ -46,10 +46,7 @@ SOFTWARE.
#include "iterator.h"
#include "type_traits.h"
#if ETL_USING_CPP20 && ETL_USING_STL
#include <bit>
#endif
#include <stdint.h>
#include <string.h>
namespace etl
@ -102,12 +99,14 @@ namespace etl
//*************************************************************************
/// Default constructor
//*************************************************************************
unaligned_type_common() {}
ETL_CONSTEXPR14 unaligned_type_common()
{
}
//*************************************************************************
/// Size of the storage.
//*************************************************************************
size_t size() const
ETL_CONSTEXPR14 size_t size() const
{
return Size_;
}
@ -115,7 +114,7 @@ namespace etl
//*************************************************************************
/// Pointer to the beginning of the storage.
//*************************************************************************
pointer data()
ETL_CONSTEXPR14 pointer data()
{
return get_storage();
}
@ -123,7 +122,7 @@ namespace etl
//*************************************************************************
/// Const pointer to the beginning of the storage.
//*************************************************************************
const_pointer data() const
ETL_CONSTEXPR14 const_pointer data() const
{
return get_storage();
}
@ -245,7 +244,7 @@ namespace etl
//*************************************************************************
/// Get a pointer to the storage.
//*************************************************************************
pointer get_storage()
ETL_CONSTEXPR14 pointer get_storage()
{
return static_cast<derived_type*>(this)->storage;
}
@ -253,7 +252,7 @@ namespace etl
//*************************************************************************
/// Get a const pointer to the storage.
//*************************************************************************
const_pointer get_storage() const
ETL_CONSTEXPR14 const_pointer get_storage() const
{
return static_cast<const derived_type*>(this)->storage;
}
@ -277,7 +276,7 @@ namespace etl
protected:
//*******************************
unaligned_type_storage()
ETL_CONSTEXPR14 unaligned_type_storage()
: storage()
{
}
@ -326,6 +325,52 @@ namespace etl
};
ETL_END_PACKED
//*************************************************************************
/// The shift, in bits, that positions byte 'index' of a value of 'Size'
/// bytes, for the target endianness.
//*************************************************************************
template <size_t Size, int Endian>
ETL_CONSTEXPR size_t byte_shift(size_t index)
{
return ((Endian == ETL_ENDIAN_LITTLE) ? index : (Size - 1U - index)) * 8U;
}
#if ETL_USING_BUILTIN_BIT_CAST
//*************************************************************************
/// Unsigned integer type of a given byte size, used as a constexpr-capable
/// 'proxy' for extracting/inserting the bytes of a floating point value.
//*************************************************************************
template <size_t Size_>
struct uint_of_size;
template <>
struct uint_of_size<2U>
{
typedef uint16_t type;
};
template <>
struct uint_of_size<4U>
{
typedef uint32_t type;
};
template <>
struct uint_of_size<8U>
{
typedef uint64_t type;
};
//*************************************************************************
/// Whether the bit_cast fast path may be used for a type of 'Size' bytes.
/// It requires a same-size unsigned integer proxy type to exist.
//*************************************************************************
template <size_t Size>
struct use_bit_cast : etl::conditional<(Size == 2U) || (Size == 4U) || (Size == 8U), etl::true_type, etl::false_type>
{
};
#endif
//*************************************************************************
/// Unaligned copy
//*************************************************************************
@ -335,6 +380,10 @@ namespace etl
//*************************************************************************
/// Unaligned copy
/// For integrals.
/// Bytes are extracted/inserted directly in the target endianness using
/// shifts, rather than a native-endianness memcpy + conditional reverse.
/// This makes the conversion independent of the host's endianness and
/// usable in a constexpr context (C++14 and later).
//*************************************************************************
template <size_t Size_, int Endian_>
ETL_PACKED_CLASS(unaligned_copy)<Size_, Endian_, true>
@ -347,44 +396,42 @@ namespace etl
//*******************************
template <typename T>
static void copy_value_to_store(const T& value, pointer store)
static ETL_CONSTEXPR14 void copy_value_to_store(const T& value, pointer store)
{
memcpy(store, &value, Size_);
// Note: 'etl::unsigned_type' is used rather than 'etl::make_unsigned', as the
// latter is not defined for 'bool'.
typedef typename etl::unsigned_type<T>::type unsigned_t;
#if ETL_HAS_CONSTEXPR_ENDIANNESS
if ETL_IF_CONSTEXPR (Endian_ != etl::endianness::value())
#else
if (Endian_ != etl::endianness::value())
#endif
unsigned_t uvalue = static_cast<unsigned_t>(value);
for (size_t i = 0UL; i < Size_; ++i)
{
etl::reverse(store, store + Size_);
store[i] = static_cast<storage_type>(uvalue >> private_unaligned_type::byte_shift<Size_, Endian_>(i));
}
}
//*******************************
template <typename T>
static void copy_store_to_value(const_pointer store, T & value)
static ETL_CONSTEXPR14 void copy_store_to_value(const_pointer store, T & value)
{
memcpy(&value, store, Size_);
typedef typename etl::unsigned_type<T>::type unsigned_t;
#if ETL_HAS_CONSTEXPR_ENDIANNESS
if ETL_IF_CONSTEXPR (Endian_ != etl::endianness::value())
#else
if (Endian_ != etl::endianness::value())
#endif
unsigned_t uvalue = unsigned_t(0);
for (size_t i = 0UL; i < Size_; ++i)
{
value = etl::reverse_bytes(value);
uvalue = static_cast<unsigned_t>(uvalue | (static_cast<unsigned_t>(store[i]) << private_unaligned_type::byte_shift<Size_, Endian_>(i)));
}
value = static_cast<T>(uvalue);
}
//*******************************
static void copy_store_to_store(const_pointer src, int endian_src, pointer dst)
static ETL_CONSTEXPR14 void copy_store_to_store(const_pointer src, int endian_src, pointer dst)
{
memcpy(dst, src, Size_);
if (Endian_ != endian_src)
for (size_t i = 0UL; i < Size_; ++i)
{
etl::reverse(dst, dst + Size_);
dst[i] = (Endian_ == endian_src) ? src[i] : src[Size_ - 1U - i];
}
}
};
@ -404,8 +451,28 @@ namespace etl
typedef typename private_unaligned_type::unaligned_type_storage< Size_>::const_pointer const_pointer;
//*******************************
// The bit_cast fast path is only valid when a same-size unsigned integer
// proxy type exists (currently 2, 4 or 8 bytes, e.g. a 16-bit float, float,
// or double). Other sizes (e.g. 80/96/128-bit 'long double') fall back to
// memcpy.
//*******************************
#if ETL_USING_BUILTIN_BIT_CAST
template <typename T>
static void copy_value_to_store(const T& value, pointer store)
static ETL_CONSTEXPR14 void do_copy_value_to_store(const T& value, pointer store, etl::true_type)
{
typedef typename private_unaligned_type::uint_of_size<sizeof(T)>::type uint_t;
uint_t uvalue = etl::bit_cast<uint_t>(value);
for (size_t i = 0UL; i < Size_; ++i)
{
store[i] = static_cast<storage_type>(uvalue >> private_unaligned_type::byte_shift<Size_, Endian_>(i));
}
}
#endif
template <typename T>
static void do_copy_value_to_store(const T& value, pointer store, etl::false_type)
{
memcpy(store, &value, Size_);
@ -421,7 +488,41 @@ namespace etl
//*******************************
template <typename T>
static void copy_store_to_value(const_pointer store, T & value)
static
#if ETL_USING_BUILTIN_BIT_CAST
ETL_CONSTEXPR14
#endif
void
copy_value_to_store(const T& value, pointer store)
{
#if ETL_USING_BUILTIN_BIT_CAST
typedef typename private_unaligned_type::use_bit_cast<sizeof(T)>::type use_bit_cast_t;
#else
typedef etl::false_type use_bit_cast_t;
#endif
do_copy_value_to_store(value, store, use_bit_cast_t());
}
//*******************************
#if ETL_USING_BUILTIN_BIT_CAST
template <typename T>
static ETL_CONSTEXPR14 void do_copy_store_to_value(const_pointer store, T & value, etl::true_type)
{
typedef typename private_unaligned_type::uint_of_size<sizeof(T)>::type uint_t;
uint_t uvalue = uint_t(0);
for (size_t i = 0UL; i < Size_; ++i)
{
uvalue = static_cast<uint_t>(uvalue | (static_cast<uint_t>(store[i]) << private_unaligned_type::byte_shift<Size_, Endian_>(i)));
}
value = etl::bit_cast<T>(uvalue);
}
#endif
template <typename T>
static void do_copy_store_to_value(const_pointer store, T & value, etl::false_type)
{
memcpy(&value, store, Size_);
@ -436,13 +537,32 @@ namespace etl
}
//*******************************
static void copy_store_to_store(const_pointer src, int endian_src, pointer dst)
template <typename T>
static
#if ETL_USING_BUILTIN_BIT_CAST
ETL_CONSTEXPR14
#endif
void
copy_store_to_value(const_pointer store, T & value)
{
memcpy(dst, src, Size_);
#if ETL_USING_BUILTIN_BIT_CAST
typedef typename private_unaligned_type::use_bit_cast<sizeof(T)>::type use_bit_cast_t;
#else
typedef etl::false_type use_bit_cast_t;
#endif
do_copy_store_to_value(store, value, use_bit_cast_t());
}
if (Endian_ != endian_src)
//*******************************
// This is pure byte manipulation (copy + optional reversal), with no
// floating point arithmetic involved, so it is always constexpr-capable
// (C++14 and later), regardless of bit_cast/builtin availability.
//*******************************
static ETL_CONSTEXPR14 void copy_store_to_store(const_pointer src, int endian_src, pointer dst)
{
for (size_t i = 0UL; i < Size_; ++i)
{
etl::reverse(dst, dst + Size_);
dst[i] = (Endian_ == endian_src) ? src[i] : src[Size_ - 1U - i];
}
}
};
@ -486,7 +606,7 @@ namespace etl
//*************************************************************************
/// Construct from a value.
//*************************************************************************
unaligned_type(T value)
ETL_CONSTEXPR14 unaligned_type(T value)
{
unaligned_copy::copy_value_to_store(value, this->storage);
}
@ -501,6 +621,10 @@ namespace etl
//*************************************************************************
/// Construct from an address and buffer size.
/// \note 'buffer_size' must be greater than or equal to 'sizeof(T)'.
/// This is a precondition, checked by ETL_ASSERT. If the checks are
/// disabled (e.g. ETL_NO_CHECKS) then passing a smaller buffer size
/// results in a read beyond the end of the buffer.
//*************************************************************************
unaligned_type(const void* address, size_t buffer_size)
{
@ -509,10 +633,38 @@ namespace etl
etl::copy_n(reinterpret_cast<const unsigned char*>(address), sizeof(T), this->storage);
}
//*************************************************************************
/// Construct from a byte buffer.
/// Unlike the 'const void*' overload above, this does not require a
/// reinterpret_cast, so it is usable in a constexpr context (C++14 and
/// later), allowing compile time decoding of a byte buffer with an
/// explicit endianness.
//*************************************************************************
ETL_CONSTEXPR14 unaligned_type(const unsigned char* address)
{
etl::copy_n(address, sizeof(T), this->storage);
}
//*************************************************************************
/// Construct from a byte buffer and buffer size.
/// See the note on the 'const unsigned char*' overload above regarding
/// constexpr usability.
/// \note 'buffer_size' must be greater than or equal to 'sizeof(T)'.
/// This is a precondition, checked by ETL_ASSERT. If the checks are
/// disabled (e.g. ETL_NO_CHECKS) then passing a smaller buffer size
/// results in a read beyond the end of the buffer.
//*************************************************************************
ETL_CONSTEXPR14 unaligned_type(const unsigned char* address, size_t buffer_size)
{
ETL_ASSERT(sizeof(T) <= buffer_size, ETL_ERROR(etl::unaligned_type_buffer_size));
etl::copy_n(address, sizeof(T), this->storage);
}
//*************************************************************************
/// Copy constructor
//*************************************************************************
unaligned_type(const unaligned_type<T, Endian>& other)
ETL_CONSTEXPR14 unaligned_type(const unaligned_type<T, Endian>& other)
{
unaligned_copy::copy_store_to_store(other.data(), Endian, this->storage);
}
@ -521,7 +673,7 @@ namespace etl
/// Copy constructor
//*************************************************************************
template <int Endian_Other>
unaligned_type(const unaligned_type<T, Endian_Other>& other)
ETL_CONSTEXPR14 unaligned_type(const unaligned_type<T, Endian_Other>& other)
{
unaligned_copy::copy_store_to_store(other.data(), Endian_Other, this->storage);
}
@ -529,7 +681,7 @@ namespace etl
//*************************************************************************
/// Assignment operator
//*************************************************************************
unaligned_type& operator=(T value)
ETL_CONSTEXPR14 unaligned_type& operator=(T value)
{
unaligned_copy::copy_value_to_store(value, this->storage);
@ -539,7 +691,7 @@ namespace etl
//*************************************************************************
/// Assignment operator.
//*************************************************************************
unaligned_type& operator=(const unaligned_type<T, Endian_>& other)
ETL_CONSTEXPR14 unaligned_type& operator=(const unaligned_type<T, Endian_>& other)
{
unaligned_copy::copy_store_to_store(other.data(), Endian_, this->storage);
@ -550,7 +702,7 @@ namespace etl
/// Assignment operator from other endianness.
//*************************************************************************
template <int Endian_Other>
unaligned_type& operator=(const unaligned_type<T, Endian_Other>& other)
ETL_CONSTEXPR14 unaligned_type& operator=(const unaligned_type<T, Endian_Other>& other)
{
unaligned_copy::copy_store_to_store(other.data(), Endian_Other, this->storage);
@ -560,7 +712,7 @@ namespace etl
//*************************************************************************
/// Conversion operator
//*************************************************************************
operator T() const
ETL_CONSTEXPR14 operator T() const
{
T value = T();
@ -572,7 +724,7 @@ namespace etl
//*************************************************************************
/// Get the value.
//*************************************************************************
T value() const
ETL_CONSTEXPR14 T value() const
{
T value = T();

View File

@ -34,9 +34,69 @@ SOFTWARE.
#include "etl/private/diagnostic_useless_cast_push.h"
#include <array>
#include <cstring>
namespace
{
//***************************************************************************
/// Representation level helpers.
/// 'memcpy' is used rather than 'etl::bit_cast' so that these work in every
/// configuration, including those without a bit_cast builtin.
//***************************************************************************
uint32_t bits_of(float value)
{
uint32_t bits = 0U;
std::memcpy(&bits, &value, sizeof(bits));
return bits;
}
//***************************************************************************
float float_from_bits(uint32_t bits)
{
float value = 0.0f;
std::memcpy(&value, &bits, sizeof(value));
return value;
}
//***************************************************************************
uint64_t bits_of(double value)
{
uint64_t bits = 0U;
std::memcpy(&bits, &value, sizeof(bits));
return bits;
}
//***************************************************************************
double double_from_bits(uint64_t bits)
{
double value = 0.0;
std::memcpy(&value, &bits, sizeof(value));
return value;
}
#if ETL_USING_CPP14 && ETL_USING_BUILTIN_BIT_CAST
//***************************************************************************
/// Encodes 'bits' as a floating point value in the storage of 'TUnaligned',
/// then decodes it back, returning the resulting bit pattern.
/// Used to check that the constexpr bit_cast path is representation
/// preserving.
//***************************************************************************
template <typename TUnaligned, typename TFloat, typename TBits>
constexpr TBits round_trip_bits(TBits bits)
{
return etl::bit_cast<TBits>(TUnaligned(etl::bit_cast<TFloat>(bits)).value());
}
//***************************************************************************
/// The storage byte at 'index' when 'bits' is encoded in 'TUnaligned'.
//***************************************************************************
template <typename TUnaligned, typename TFloat, typename TBits>
constexpr unsigned char storage_byte_of(TBits bits, size_t index)
{
return TUnaligned(etl::bit_cast<TFloat>(bits)).data()[index];
}
#endif
SUITE(test_unaligned_type)
{
//*************************************************************************
@ -175,6 +235,490 @@ namespace
CHECK_THROW(etl::be_uint32_t be_v3(buffer.data(), buffer.size() - 1), etl::unaligned_type_buffer_size);
}
//*************************************************************************
/// The 'const unsigned char*' overloads are distinct from the
/// 'const void*' ones above, so check their buffer size contract too.
/// Note that the size check is an ETL_ASSERT, so this only holds when
/// checks are enabled. See the note on the constructor.
//*************************************************************************
TEST(test_construction_from_byte_buffer_size_boundary)
{
const std::array<unsigned char, 5> buffer = {0x12, 0x34, 0x56, 0x78, 0x9A};
// Exactly the required number of bytes.
etl::le_uint32_t le_exact(buffer.data(), sizeof(uint32_t));
etl::be_uint32_t be_exact(buffer.data(), sizeof(uint32_t));
CHECK_EQUAL(uint32_t(0x78563412), le_exact.value());
CHECK_EQUAL(uint32_t(0x12345678), be_exact.value());
// More bytes than required. The surplus is ignored.
etl::le_uint32_t le_larger(buffer.data(), buffer.size());
etl::be_uint32_t be_larger(buffer.data(), buffer.size());
CHECK_EQUAL(uint32_t(0x78563412), le_larger.value());
CHECK_EQUAL(uint32_t(0x12345678), be_larger.value());
// One byte short.
CHECK_THROW(etl::le_uint32_t le_short(buffer.data(), sizeof(uint32_t) - 1U), etl::unaligned_type_buffer_size);
CHECK_THROW(etl::be_uint32_t be_short(buffer.data(), sizeof(uint32_t) - 1U), etl::unaligned_type_buffer_size);
// Empty buffer.
CHECK_THROW(etl::le_uint32_t le_empty(buffer.data(), 0U), etl::unaligned_type_buffer_size);
CHECK_THROW(etl::be_uint32_t be_empty(buffer.data(), 0U), etl::unaligned_type_buffer_size);
// The widest type is checked too, as it has the largest storage.
CHECK_THROW(etl::le_uint64_t le_wide(buffer.data(), buffer.size()), etl::unaligned_type_buffer_size);
CHECK_THROW(etl::be_uint64_t be_wide(buffer.data(), buffer.size()), etl::unaligned_type_buffer_size);
// Single byte types accept a single byte buffer.
etl::le_uint8_t le_byte(buffer.data(), 1U);
etl::be_uint8_t be_byte(buffer.data(), 1U);
CHECK_EQUAL(uint8_t(0x12), le_byte.value());
CHECK_EQUAL(uint8_t(0x12), be_byte.value());
CHECK_THROW(etl::le_uint8_t le_byte_empty(buffer.data(), 0U), etl::unaligned_type_buffer_size);
CHECK_THROW(etl::be_uint8_t be_byte_empty(buffer.data(), 0U), etl::unaligned_type_buffer_size);
}
//*************************************************************************
// The following tests demonstrate the 'decode' direction: given a raw byte
// buffer (e.g. as received from a file, network socket or memory-mapped
// device), interpret it as an explicitly little/big endian unaligned_type
// and read back the correctly decoded native value.
//*************************************************************************
TEST(test_decode_buffer_uint16)
{
// The same two bytes, interpreted with an explicit endianness.
const std::array<uint8_t, 2> buffer = {0x12, 0x34};
etl::le_uint16_t le_v(buffer.data(), buffer.size());
etl::be_uint16_t be_v(buffer.data(), buffer.size());
CHECK_EQUAL(uint16_t(0x3412), le_v.value());
CHECK_EQUAL(uint16_t(0x1234), be_v.value());
}
//*************************************************************************
// 'bool' has no 'make_unsigned' equivalent, so check that it is supported.
TEST(test_bool)
{
etl::unaligned_type<bool, etl::endian::little> le_v(true);
etl::unaligned_type<bool, etl::endian::big> be_v(false);
CHECK_EQUAL(true, le_v.value());
CHECK_EQUAL(false, be_v.value());
le_v = false;
be_v = true;
CHECK_EQUAL(false, le_v.value());
CHECK_EQUAL(true, be_v.value());
}
//*************************************************************************
TEST(test_decode_buffer_int32)
{
const std::array<uint8_t, 4> buffer = {0x12, 0x34, 0x56, 0x78};
etl::le_int32_t le_v(buffer.data(), buffer.size());
etl::be_int32_t be_v(buffer.data(), buffer.size());
CHECK_EQUAL(int32_t(0x78563412), le_v.value());
CHECK_EQUAL(int32_t(0x12345678), be_v.value());
}
//*************************************************************************
TEST(test_decode_buffer_uint64)
{
const std::array<uint8_t, 8> buffer = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
etl::le_uint64_t le_v(buffer.data(), buffer.size());
etl::be_uint64_t be_v(buffer.data(), buffer.size());
CHECK_EQUAL(uint64_t(0xEFCDAB8967452301), le_v.value());
CHECK_EQUAL(uint64_t(0x0123456789ABCDEF), be_v.value());
}
//*************************************************************************
/// The decode tests above cover a subset of the widths only, and no
/// negative or extreme values. These cover every signed and unsigned
/// width at its boundary values, in both directions.
//*************************************************************************
template <typename TLittle, typename TBig, typename TValue>
void check_boundary_value(TValue value)
{
ETL_STATIC_ASSERT(sizeof(TValue) == TLittle::Size, "Mismatched size");
ETL_STATIC_ASSERT(sizeof(TValue) == TBig::Size, "Mismatched size");
const TLittle le_v(value);
const TBig be_v(value);
// The two encodings are byte reversals of each other.
for (size_t i = 0U; i < sizeof(TValue); ++i)
{
CHECK_EQUAL(int(le_v.data()[i]), int(be_v.data()[sizeof(TValue) - 1U - i]));
}
// Decoding the encoded bytes returns the original value.
CHECK_EQUAL(value, TLittle(le_v.data(), sizeof(TValue)).value());
CHECK_EQUAL(value, TBig(be_v.data(), sizeof(TValue)).value());
}
//*************************************************************************
TEST(test_integral_boundary_values)
{
check_boundary_value<etl::le_int8_t, etl::be_int8_t, int8_t>(0);
check_boundary_value<etl::le_int8_t, etl::be_int8_t, int8_t>(-1);
check_boundary_value<etl::le_int8_t, etl::be_int8_t, int8_t>(etl::integral_limits<int8_t>::min);
check_boundary_value<etl::le_int8_t, etl::be_int8_t, int8_t>(etl::integral_limits<int8_t>::max);
check_boundary_value<etl::le_uint8_t, etl::be_uint8_t, uint8_t>(0U);
check_boundary_value<etl::le_uint8_t, etl::be_uint8_t, uint8_t>(etl::integral_limits<uint8_t>::max);
check_boundary_value<etl::le_int16_t, etl::be_int16_t, int16_t>(0);
check_boundary_value<etl::le_int16_t, etl::be_int16_t, int16_t>(-1);
check_boundary_value<etl::le_int16_t, etl::be_int16_t, int16_t>(etl::integral_limits<int16_t>::min);
check_boundary_value<etl::le_int16_t, etl::be_int16_t, int16_t>(etl::integral_limits<int16_t>::max);
check_boundary_value<etl::le_uint16_t, etl::be_uint16_t, uint16_t>(0U);
check_boundary_value<etl::le_uint16_t, etl::be_uint16_t, uint16_t>(etl::integral_limits<uint16_t>::max);
check_boundary_value<etl::le_int32_t, etl::be_int32_t, int32_t>(0);
check_boundary_value<etl::le_int32_t, etl::be_int32_t, int32_t>(-1);
check_boundary_value<etl::le_int32_t, etl::be_int32_t, int32_t>(etl::integral_limits<int32_t>::min);
check_boundary_value<etl::le_int32_t, etl::be_int32_t, int32_t>(etl::integral_limits<int32_t>::max);
check_boundary_value<etl::le_uint32_t, etl::be_uint32_t, uint32_t>(0U);
check_boundary_value<etl::le_uint32_t, etl::be_uint32_t, uint32_t>(etl::integral_limits<uint32_t>::max);
check_boundary_value<etl::le_int64_t, etl::be_int64_t, int64_t>(0);
check_boundary_value<etl::le_int64_t, etl::be_int64_t, int64_t>(-1);
check_boundary_value<etl::le_int64_t, etl::be_int64_t, int64_t>(etl::integral_limits<int64_t>::min);
check_boundary_value<etl::le_int64_t, etl::be_int64_t, int64_t>(etl::integral_limits<int64_t>::max);
check_boundary_value<etl::le_uint64_t, etl::be_uint64_t, uint64_t>(0U);
check_boundary_value<etl::le_uint64_t, etl::be_uint64_t, uint64_t>(etl::integral_limits<uint64_t>::max);
}
//*************************************************************************
/// Explicit byte level checks for the widths that the decode tests above
/// do not cover, using negative values so that the sign bit is exercised.
//*************************************************************************
TEST(test_decode_buffer_negative_values)
{
// -2 == 0xFE, 0xFFFE, 0xFFFFFFFE, 0xFFFFFFFFFFFFFFFE.
const std::array<unsigned char, 8> buffer = {0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
CHECK_EQUAL(int8_t(-2), etl::le_int8_t(buffer.data(), buffer.size()).value());
CHECK_EQUAL(int8_t(-2), etl::be_int8_t(buffer.data(), buffer.size()).value());
CHECK_EQUAL(int16_t(-2), etl::le_int16_t(buffer.data(), buffer.size()).value());
CHECK_EQUAL(int32_t(-2), etl::le_int32_t(buffer.data(), buffer.size()).value());
CHECK_EQUAL(int64_t(-2), etl::le_int64_t(buffer.data(), buffer.size()).value());
// Big endian reads the same bytes in the opposite order.
CHECK_EQUAL(int16_t(-257), etl::be_int16_t(buffer.data(), buffer.size()).value());
CHECK_EQUAL(int32_t(-16777217), etl::be_int32_t(buffer.data(), buffer.size()).value());
CHECK_EQUAL(int64_t(-72057594037927937LL), etl::be_int64_t(buffer.data(), buffer.size()).value());
}
//*************************************************************************
TEST(test_decode_buffer_float)
{
// 1.5f in IEEE-754 single precision is 0x3FC00000.
const std::array<uint8_t, 4> le_buffer = {0x00, 0x00, 0xC0, 0x3F};
const std::array<uint8_t, 4> be_buffer = {0x3F, 0xC0, 0x00, 0x00};
etl::le_float_t le_v(le_buffer.data(), le_buffer.size());
etl::be_float_t be_v(be_buffer.data(), be_buffer.size());
CHECK_EQUAL(1.5f, le_v.value());
CHECK_EQUAL(1.5f, be_v.value());
}
//*************************************************************************
TEST(test_decode_buffer_double)
{
// 1.5 in IEEE-754 double precision is 0x3FF8000000000000.
const std::array<uint8_t, 8> le_buffer = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x3F};
const std::array<uint8_t, 8> be_buffer = {0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
etl::le_double_t le_v(le_buffer.data(), le_buffer.size());
etl::be_double_t be_v(be_buffer.data(), be_buffer.size());
CHECK_EQUAL(1.5, le_v.value());
CHECK_EQUAL(1.5, be_v.value());
}
//*************************************************************************
/// Representation level checks for 'float'.
/// Ordinary values such as 1.5f have a zero mantissa tail, a clear sign
/// bit and a mid range exponent, so they would still pass under several
/// plausible masking or byte ordering faults. These patterns exercise the
/// sign of zero, the infinities, a NaN payload, the subnormal range and
/// the normal range limits.
//*************************************************************************
TEST(test_float_representations)
{
static const uint32_t patterns[] = {
0x00000000U, // +0.0
0x80000000U, // -0.0
0x7F800000U, // +infinity
0xFF800000U, // -infinity
0x7FC0DEADU, // Quiet NaN with a payload.
0xFFC0DEADU, // Negative quiet NaN with a payload.
0x00000001U, // Smallest positive subnormal.
0x007FFFFFU, // Largest subnormal.
0x00800000U, // Smallest positive normal.
0x7F7FFFFFU, // Largest normal.
0xBFC00000U // -1.5
};
for (size_t i = 0U; i < (sizeof(patterns) / sizeof(patterns[0])); ++i)
{
const uint32_t bits = patterns[i];
etl::le_float_t le_v(float_from_bits(bits));
etl::be_float_t be_v(float_from_bits(bits));
// The storage must hold the exact IEEE-754 bytes, in the requested order.
for (size_t b = 0U; b < sizeof(float); ++b)
{
CHECK_EQUAL(int((bits >> (8U * b)) & 0xFFU), int(le_v.data()[b]));
CHECK_EQUAL(int((bits >> (8U * (sizeof(float) - 1U - b))) & 0xFFU), int(be_v.data()[b]));
}
// The value must survive the round trip bit for bit, including the
// sign of zero and the NaN payload.
CHECK_EQUAL(bits, bits_of(le_v.value()));
CHECK_EQUAL(bits, bits_of(be_v.value()));
// Decoding the raw bytes must give the same result as encoding them.
etl::le_float_t le_decoded(le_v.data(), le_v.size());
etl::be_float_t be_decoded(be_v.data(), be_v.size());
CHECK_EQUAL(bits, bits_of(le_decoded.value()));
CHECK_EQUAL(bits, bits_of(be_decoded.value()));
}
}
//*************************************************************************
/// Representation level checks for 'double'. See the note on the 'float'
/// test above.
//*************************************************************************
TEST(test_double_representations)
{
static const uint64_t patterns[] = {
0x0000000000000000ULL, // +0.0
0x8000000000000000ULL, // -0.0
0x7FF0000000000000ULL, // +infinity
0xFFF0000000000000ULL, // -infinity
0x7FF80000DEADBEEFULL, // Quiet NaN with a payload.
0xFFF80000DEADBEEFULL, // Negative quiet NaN with a payload.
0x0000000000000001ULL, // Smallest positive subnormal.
0x000FFFFFFFFFFFFFULL, // Largest subnormal.
0x0010000000000000ULL, // Smallest positive normal.
0x7FEFFFFFFFFFFFFFULL, // Largest normal.
0xBFF8000000000000ULL // -1.5
};
for (size_t i = 0U; i < (sizeof(patterns) / sizeof(patterns[0])); ++i)
{
const uint64_t bits = patterns[i];
etl::le_double_t le_v(double_from_bits(bits));
etl::be_double_t be_v(double_from_bits(bits));
for (size_t b = 0U; b < sizeof(double); ++b)
{
CHECK_EQUAL(int((bits >> (8U * b)) & 0xFFU), int(le_v.data()[b]));
CHECK_EQUAL(int((bits >> (8U * (sizeof(double) - 1U - b))) & 0xFFU), int(be_v.data()[b]));
}
CHECK_EQUAL(bits, bits_of(le_v.value()));
CHECK_EQUAL(bits, bits_of(be_v.value()));
etl::le_double_t le_decoded(le_v.data(), le_v.size());
etl::be_double_t be_decoded(be_v.data(), be_v.size());
CHECK_EQUAL(bits, bits_of(le_decoded.value()));
CHECK_EQUAL(bits, bits_of(be_decoded.value()));
}
}
//*************************************************************************
/// 'long double' is typically 80, 96 or 128 bits wide, so it has no same
/// sized unsigned integer proxy and falls back to the non bit_cast,
/// non constexpr path. That fallback still has to work.
//*************************************************************************
TEST(test_long_double_round_trip)
{
const long double value = 1.5L;
etl::le_long_double_t le_v(value);
etl::be_long_double_t be_v(value);
CHECK_EQUAL(sizeof(long double), le_v.size());
CHECK_EQUAL(sizeof(long double), be_v.size());
// The little and big endian storage must be byte reversals of each other.
for (size_t i = 0U; i < sizeof(long double); ++i)
{
CHECK_EQUAL(int(le_v.data()[i]), int(be_v.data()[sizeof(long double) - 1U - i]));
}
CHECK_EQUAL(value, le_v.value());
CHECK_EQUAL(value, be_v.value());
// Decoding the raw bytes must give the same result as encoding them.
etl::le_long_double_t le_decoded(le_v.data(), le_v.size());
etl::be_long_double_t be_decoded(be_v.data(), be_v.size());
CHECK_EQUAL(value, le_decoded.value());
CHECK_EQUAL(value, be_decoded.value());
}
#if ETL_HAS_CONSTEXPR_ENDIANNESS
//*************************************************************************
TEST(test_decode_buffer_host_order)
{
// Decoding a buffer known to be in host order must yield the same
// value regardless of what that host order actually is.
const uint32_t value = 0x12345678U;
const etl::host_uint32_t encoded(value);
etl::host_uint32_t decoded(encoded.data(), encoded.size());
CHECK_EQUAL(value, decoded.value());
}
#endif
#if ETL_USING_CPP14
//*************************************************************************
// Demonstrates that decoding a raw byte buffer with an explicit endianness
// is now usable at compile time (constexpr), via the 'const unsigned char*'
// constructor overloads - not just runtime CHECK_EQUAL.
//*************************************************************************
TEST(test_constexpr_decode_buffer_integral)
{
static ETL_CONSTANT unsigned char le_buffer[4] = {0x78, 0x56, 0x34, 0x12};
static ETL_CONSTANT unsigned char be_buffer[4] = {0x12, 0x34, 0x56, 0x78};
constexpr etl::le_uint32_t le_v(le_buffer, sizeof(le_buffer));
constexpr etl::be_uint32_t be_v(be_buffer, sizeof(be_buffer));
static_assert(le_v.value() == 0x12345678U, "le_uint32_t constexpr decode from buffer + size");
static_assert(be_v.value() == 0x12345678U, "be_uint32_t constexpr decode from buffer + size");
// Buffer-only overload (no explicit size).
constexpr etl::le_uint32_t le_v2(le_buffer);
constexpr etl::be_uint32_t be_v2(be_buffer);
static_assert(le_v2.value() == 0x12345678U, "le_uint32_t constexpr decode from buffer");
static_assert(be_v2.value() == 0x12345678U, "be_uint32_t constexpr decode from buffer");
// Prove the checks above are genuine compile-time facts, not just
// syntactically valid constexpr, by also exercising them at runtime.
CHECK_EQUAL(0x12345678U, le_v.value());
CHECK_EQUAL(0x12345678U, be_v.value());
}
#if ETL_USING_BUILTIN_BIT_CAST
//*************************************************************************
TEST(test_constexpr_decode_buffer_float)
{
// 1.5f in IEEE-754 single precision is 0x3FC00000.
static ETL_CONSTANT unsigned char le_buffer[4] = {0x00, 0x00, 0xC0, 0x3F};
static ETL_CONSTANT unsigned char be_buffer[4] = {0x3F, 0xC0, 0x00, 0x00};
constexpr etl::le_float_t le_v(le_buffer, sizeof(le_buffer));
constexpr etl::be_float_t be_v(be_buffer, sizeof(be_buffer));
// Bit patterns are compared, rather than the float values, to avoid
// '-Wfloat-equal'.
static_assert(etl::bit_cast<uint32_t>(le_v.value()) == 0x3FC00000U, "le_float_t constexpr decode from buffer");
static_assert(etl::bit_cast<uint32_t>(be_v.value()) == 0x3FC00000U, "be_float_t constexpr decode from buffer");
CHECK_EQUAL(1.5f, le_v.value());
CHECK_EQUAL(1.5f, be_v.value());
}
//*************************************************************************
/// The constexpr path bit_casts through a same sized unsigned integer.
/// Check, at compile time, that this is exact for the awkward
/// representations too, not just for ordinary values such as 1.5f.
//*************************************************************************
TEST(test_constexpr_float_representations)
{
// Encode, then decode, and require the bit pattern to be unchanged.
static_assert(round_trip_bits<etl::le_float_t, float>(0x00000000U) == 0x00000000U, "le +0.0");
static_assert(round_trip_bits<etl::le_float_t, float>(0x80000000U) == 0x80000000U, "le -0.0");
static_assert(round_trip_bits<etl::le_float_t, float>(0x7F800000U) == 0x7F800000U, "le +infinity");
static_assert(round_trip_bits<etl::le_float_t, float>(0xFF800000U) == 0xFF800000U, "le -infinity");
static_assert(round_trip_bits<etl::le_float_t, float>(0x7FC0DEADU) == 0x7FC0DEADU, "le NaN payload");
static_assert(round_trip_bits<etl::le_float_t, float>(0x00000001U) == 0x00000001U, "le smallest subnormal");
static_assert(round_trip_bits<etl::le_float_t, float>(0x007FFFFFU) == 0x007FFFFFU, "le largest subnormal");
static_assert(round_trip_bits<etl::le_float_t, float>(0x00800000U) == 0x00800000U, "le smallest normal");
static_assert(round_trip_bits<etl::le_float_t, float>(0x7F7FFFFFU) == 0x7F7FFFFFU, "le largest normal");
static_assert(round_trip_bits<etl::be_float_t, float>(0x80000000U) == 0x80000000U, "be -0.0");
static_assert(round_trip_bits<etl::be_float_t, float>(0xFF800000U) == 0xFF800000U, "be -infinity");
static_assert(round_trip_bits<etl::be_float_t, float>(0x7FC0DEADU) == 0x7FC0DEADU, "be NaN payload");
static_assert(round_trip_bits<etl::be_float_t, float>(0x00000001U) == 0x00000001U, "be smallest subnormal");
static_assert(round_trip_bits<etl::be_float_t, float>(0x7F7FFFFFU) == 0x7F7FFFFFU, "be largest normal");
// The stored bytes must be the IEEE-754 bytes, in the requested order.
static_assert(storage_byte_of<etl::le_float_t, float>(0x7FC0DEADU, 0U) == 0xAD, "le NaN byte 0");
static_assert(storage_byte_of<etl::le_float_t, float>(0x7FC0DEADU, 1U) == 0xDE, "le NaN byte 1");
static_assert(storage_byte_of<etl::le_float_t, float>(0x7FC0DEADU, 2U) == 0xC0, "le NaN byte 2");
static_assert(storage_byte_of<etl::le_float_t, float>(0x7FC0DEADU, 3U) == 0x7F, "le NaN byte 3");
static_assert(storage_byte_of<etl::be_float_t, float>(0x7FC0DEADU, 0U) == 0x7F, "be NaN byte 0");
static_assert(storage_byte_of<etl::be_float_t, float>(0x7FC0DEADU, 1U) == 0xC0, "be NaN byte 1");
static_assert(storage_byte_of<etl::be_float_t, float>(0x7FC0DEADU, 2U) == 0xDE, "be NaN byte 2");
static_assert(storage_byte_of<etl::be_float_t, float>(0x7FC0DEADU, 3U) == 0xAD, "be NaN byte 3");
// Prove the checks above are genuine compile time facts, not just
// syntactically valid constexpr, by also exercising them at runtime.
CHECK_EQUAL(0x80000000U, (round_trip_bits<etl::le_float_t, float>(0x80000000U)));
CHECK_EQUAL(0x7FC0DEADU, (round_trip_bits<etl::be_float_t, float>(0x7FC0DEADU)));
}
//*************************************************************************
/// As 'test_constexpr_float_representations', for the 8 byte proxy.
//*************************************************************************
TEST(test_constexpr_double_representations)
{
static_assert(round_trip_bits<etl::le_double_t, double>(0x0000000000000000ULL) == 0x0000000000000000ULL, "le +0.0");
static_assert(round_trip_bits<etl::le_double_t, double>(0x8000000000000000ULL) == 0x8000000000000000ULL, "le -0.0");
static_assert(round_trip_bits<etl::le_double_t, double>(0x7FF0000000000000ULL) == 0x7FF0000000000000ULL, "le +infinity");
static_assert(round_trip_bits<etl::le_double_t, double>(0xFFF0000000000000ULL) == 0xFFF0000000000000ULL, "le -infinity");
static_assert(round_trip_bits<etl::le_double_t, double>(0x7FF80000DEADBEEFULL) == 0x7FF80000DEADBEEFULL, "le NaN payload");
static_assert(round_trip_bits<etl::le_double_t, double>(0x0000000000000001ULL) == 0x0000000000000001ULL, "le smallest subnormal");
static_assert(round_trip_bits<etl::le_double_t, double>(0x000FFFFFFFFFFFFFULL) == 0x000FFFFFFFFFFFFFULL, "le largest subnormal");
static_assert(round_trip_bits<etl::le_double_t, double>(0x0010000000000000ULL) == 0x0010000000000000ULL, "le smallest normal");
static_assert(round_trip_bits<etl::le_double_t, double>(0x7FEFFFFFFFFFFFFFULL) == 0x7FEFFFFFFFFFFFFFULL, "le largest normal");
static_assert(round_trip_bits<etl::be_double_t, double>(0x8000000000000000ULL) == 0x8000000000000000ULL, "be -0.0");
static_assert(round_trip_bits<etl::be_double_t, double>(0xFFF0000000000000ULL) == 0xFFF0000000000000ULL, "be -infinity");
static_assert(round_trip_bits<etl::be_double_t, double>(0x7FF80000DEADBEEFULL) == 0x7FF80000DEADBEEFULL, "be NaN payload");
static_assert(round_trip_bits<etl::be_double_t, double>(0x0000000000000001ULL) == 0x0000000000000001ULL, "be smallest subnormal");
static_assert(round_trip_bits<etl::be_double_t, double>(0x7FEFFFFFFFFFFFFFULL) == 0x7FEFFFFFFFFFFFFFULL, "be largest normal");
static_assert(storage_byte_of<etl::le_double_t, double>(0x7FF80000DEADBEEFULL, 0U) == 0xEF, "le NaN byte 0");
static_assert(storage_byte_of<etl::le_double_t, double>(0x7FF80000DEADBEEFULL, 7U) == 0x7F, "le NaN byte 7");
static_assert(storage_byte_of<etl::be_double_t, double>(0x7FF80000DEADBEEFULL, 0U) == 0x7F, "be NaN byte 0");
static_assert(storage_byte_of<etl::be_double_t, double>(0x7FF80000DEADBEEFULL, 7U) == 0xEF, "be NaN byte 7");
CHECK_EQUAL(0x8000000000000000ULL, (round_trip_bits<etl::le_double_t, double>(0x8000000000000000ULL)));
CHECK_EQUAL(0x7FF80000DEADBEEFULL, (round_trip_bits<etl::be_double_t, double>(0x7FF80000DEADBEEFULL)));
}
#endif
#endif
//*************************************************************************
TEST(test_endianness)
{
@ -1122,6 +1666,74 @@ namespace
CHECK_EQUAL(value, uint16_t(be_from_host));
}
#endif
#if ETL_USING_CPP14
//*************************************************************************
// Demonstrates that integral endian conversion is now usable at compile
// time (constexpr), via genuine static_assert checks - not just runtime
// CHECK_EQUAL. The conversion is evaluated entirely by the compiler.
//*************************************************************************
TEST(test_constexpr_integral_endian_conversion)
{
// Constructing from a value and reading it back via value()/operator T()
// is constexpr for integral types, regardless of endianness.
constexpr etl::le_uint32_t le_v(0x12345678U);
constexpr etl::be_uint32_t be_v(0x12345678U);
static_assert(le_v.value() == 0x12345678U, "le_uint32_t constexpr value() round trip");
static_assert(be_v.value() == 0x12345678U, "be_uint32_t constexpr value() round trip");
static_assert(uint32_t(le_v) == 0x12345678U, "le_uint32_t constexpr operator T() round trip");
static_assert(uint32_t(be_v) == 0x12345678U, "be_uint32_t constexpr operator T() round trip");
// Verify the underlying byte order is also usable at compile time.
static_assert(le_v.data()[0] == 0x78, "le_uint32_t constexpr byte 0");
static_assert(le_v.data()[1] == 0x56, "le_uint32_t constexpr byte 1");
static_assert(le_v.data()[2] == 0x34, "le_uint32_t constexpr byte 2");
static_assert(le_v.data()[3] == 0x12, "le_uint32_t constexpr byte 3");
static_assert(be_v.data()[0] == 0x12, "be_uint32_t constexpr byte 0");
static_assert(be_v.data()[1] == 0x34, "be_uint32_t constexpr byte 1");
static_assert(be_v.data()[2] == 0x56, "be_uint32_t constexpr byte 2");
static_assert(be_v.data()[3] == 0x78, "be_uint32_t constexpr byte 3");
// Prove the checks above are genuine compile-time facts, not just
// syntactically valid constexpr, by also exercising them at runtime.
CHECK_EQUAL(0x12345678U, uint32_t(le_v));
CHECK_EQUAL(0x12345678U, uint32_t(be_v));
}
#endif
#if ETL_USING_CPP14 && ETL_USING_BUILTIN_BIT_CAST
//*************************************************************************
// Demonstrates that floating point endian conversion is now usable at
// compile time (constexpr) too, via etl::bit_cast (no std::bit_cast or
// STL dependency required).
//*************************************************************************
TEST(test_constexpr_float_endian_conversion)
{
constexpr etl::le_float_t le_v(1.5f);
constexpr etl::be_float_t be_v(1.5f);
// Bit patterns are compared, rather than the float values, to avoid
// '-Wfloat-equal'.
static_assert(etl::bit_cast<uint32_t>(le_v.value()) == 0x3FC00000U, "le_float_t constexpr value() round trip");
static_assert(etl::bit_cast<uint32_t>(be_v.value()) == 0x3FC00000U, "be_float_t constexpr value() round trip");
static_assert(etl::bit_cast<uint32_t>(float(le_v)) == 0x3FC00000U, "le_float_t constexpr operator T() round trip");
static_assert(etl::bit_cast<uint32_t>(float(be_v)) == 0x3FC00000U, "be_float_t constexpr operator T() round trip");
// LE and BE storage bytes must be the reverse of each other, checked at
// compile time.
static_assert(le_v.data()[0] == be_v.data()[3], "byte 0/3 mismatch");
static_assert(le_v.data()[1] == be_v.data()[2], "byte 1/2 mismatch");
static_assert(le_v.data()[2] == be_v.data()[1], "byte 2/1 mismatch");
static_assert(le_v.data()[3] == be_v.data()[0], "byte 3/0 mismatch");
CHECK_EQUAL(1.5f, float(le_v));
CHECK_EQUAL(1.5f, float(be_v));
}
#endif
}
} // namespace