From 8f5d31d0718e825e9b290b4abd32695d7533469e Mon Sep 17 00:00:00 2001 From: Joshua Scott Date: Fri, 26 Oct 2018 14:42:00 +0100 Subject: [PATCH] pclass: Implement BitInteger and other bit utilities --- .../ki/pclass/types/IntegralPrimitiveType.h | 8 +- include/ki/util/BitStream.h | 9 +- include/ki/util/BitTypes.h | 209 ++++++++++++++++++ test/src/unit-bitstream.cpp | 24 +- 4 files changed, 230 insertions(+), 20 deletions(-) create mode 100644 include/ki/util/BitTypes.h diff --git a/include/ki/pclass/types/IntegralPrimitiveType.h b/include/ki/pclass/types/IntegralPrimitiveType.h index 682dc41..833153d 100644 --- a/include/ki/pclass/types/IntegralPrimitiveType.h +++ b/include/ki/pclass/types/IntegralPrimitiveType.h @@ -8,24 +8,24 @@ namespace pclass template struct PrimitiveTypeWriter< ValueT, - typename std::enable_if::value>::type + typename std::enable_if::value>::type > { static void write_to(BitStream &stream, const ValueT &value) { - stream.write(value, sizeof(ValueT) * 8); + stream.write(value, bitsizeof::value); } }; template struct PrimitiveTypeReader< ValueT, - typename std::enable_if::value>::type + typename std::enable_if::value>::type > { static void read_from(BitStream &stream, ValueT &value) { - value = stream.read(sizeof(ValueT) * 8); + value = stream.read(bitsizeof::value); } }; } diff --git a/include/ki/util/BitStream.h b/include/ki/util/BitStream.h index e5388f1..7baf141 100644 --- a/include/ki/util/BitStream.h +++ b/include/ki/util/BitStream.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include "ki/util/BitTypes.h" #define KI_BITSTREAM_DEFAULT_BUFFER_SIZE 0x2000 @@ -73,9 +74,9 @@ namespace ki */ template < typename IntegerT, - typename = std::enable_if::value> + typename = std::enable_if::value> > - IntegerT read(const uint8_t bits) + IntegerT read(const uint8_t bits = bitsizeof::value) { IntegerT value = 0; @@ -116,9 +117,9 @@ namespace ki */ template < typename IntegerT, - typename = std::enable_if::value> + typename = std::enable_if::value> > - void write(IntegerT value, const uint8_t bits) + void write(IntegerT value, const uint8_t bits = bitsizeof::value) { // Iterate until we've written all of the bits auto unwritten_bits = bits; diff --git a/include/ki/util/BitTypes.h b/include/ki/util/BitTypes.h new file mode 100644 index 0000000..36db30d --- /dev/null +++ b/include/ki/util/BitTypes.h @@ -0,0 +1,209 @@ +#pragma once +#include +#include + +namespace ki +{ + /** + * A helper utility that provides the most suitable primitive type + * to store an N bit integer with the appropriate signedness. + */ + template + struct bits + { + // Do not allow 0 bits. + static_assert(N > 0, "N must be greater than 0."); + + // Do not allow a bit length > 64 as there is no + // type to adequately represent these values. + static_assert(N <= 64, "N must be less than or equal to 64."); + + /** + * The integer type that can most efficiently store N bits + * with signedness. + */ + using int_type = typename std::conditional< + N <= 8, + int8_t, + typename std::conditional< + N <= 16, + int16_t, + typename std::conditional< + N <= 32, + int32_t, + int64_t + >::type + >::type + >::type; + + /** + * The integer type that can most efficiently store N bits + * without signedness. + */ + using uint_type = typename std::conditional< + N <= 8, + uint8_t, + typename std::conditional< + N <= 16, + uint16_t, + typename std::conditional< + N <= 32, + uint32_t, + uint64_t + >::type + >::type + >::type; + }; + + /** + * Represents an integer of N bits. + */ + template + struct BitInteger + { + /** + * The type used to internally store the N-bit integer. + */ + using type = typename std::conditional< + Unsigned, + typename bits::uint_type, + typename bits::int_type + >::type; + + BitInteger() + { + m_value = 0; + } + + BitInteger(BitInteger &cp) + { + m_value = cp.m_value; + } + + BitInteger(const type value) + { + m_value = value; + } + + BitInteger &operator =(const type rhs) + { + m_value = rhs; + return *this; + } + + BitInteger &operator +=(const type rhs) + { + m_value += rhs; + return *this; + } + + BitInteger &operator -=(const type rhs) + { + m_value -= rhs; + return *this; + } + + BitInteger &operator *=(const type rhs) + { + m_value *= rhs; + return *this; + } + + BitInteger &operator /=(const type rhs) + { + m_value /= rhs; + return *this; + } + + BitInteger &operator |=(const type rhs) + { + m_value |= rhs; + return *this; + } + + BitInteger &operator &=(const type rhs) + { + m_value &= rhs; + return *this; + } + + BitInteger &operator ++() + { + m_value += 1; + return *this; + } + + BitInteger &operator --() + { + m_value -= 1; + return *this; + } + + BitInteger operator ++(int increment) + { + auto copy(*this); + ++(*this); + return copy; + } + + BitInteger operator --(int increment) + { + auto copy(*this); + --(*this); + return copy; + } + + operator type() const + { + return m_value; + } + private: + type m_value : N; + }; + + /** + * Represents a signed integer of N bits. + */ + template + using bi = BitInteger; + + /** + * Represents an unsigned integer of N bits. + */ + template + using bui = BitInteger; + + /** + * A utility to calculate the bitsize of a type. + */ + template + struct bitsizeof + { + /** + * The number of bits a type will occupy if written to a BitStream. + * This does not reflect the size of the type in memory. + */ + static constexpr size_t value = sizeof(T) * 8; + }; + + template + struct bitsizeof> + { + static constexpr size_t value = N; + }; + + /** + * Determines whether a given type is integral. + */ + template + struct is_integral : std::false_type {}; + + template + struct is_integral< + T, + typename std::enable_if::value>::type + > : std::true_type {}; + + template + struct is_integral> : std::true_type {}; +} \ No newline at end of file diff --git a/test/src/unit-bitstream.cpp b/test/src/unit-bitstream.cpp index 507da83..a1eafde 100644 --- a/test/src/unit-bitstream.cpp +++ b/test/src/unit-bitstream.cpp @@ -18,8 +18,8 @@ #define KI_TEST_BITSTREAM_U32 0x0A090807 #define KI_TEST_BITSTREAM_U64 0x1211100F0E0D0C0BL -#define KI_TEST_WRITE_BUI(n) bit_stream->write(KI_TEST_BITSTREAM_BUI##n, n) -#define KI_TEST_READ_BUI(n) bit_stream->read(n) == KI_TEST_BITSTREAM_BUI##n +#define KI_TEST_WRITE_BUI(n) bit_stream->write>(KI_TEST_BITSTREAM_BUI##n) +#define KI_TEST_READ_BUI(n) bit_stream->read>() == KI_TEST_BITSTREAM_BUI##n using namespace ki; @@ -117,11 +117,11 @@ TEST_CASE("BitStream Functionality", "[bit-stream]") SECTION("Writing values with a size greater than 8 bits") { // Write some values - bit_stream->write(KI_TEST_BITSTREAM_U8, 8); - bit_stream->write(KI_TEST_BITSTREAM_U16, 16); - bit_stream->write(KI_TEST_BITSTREAM_U24, 24); - bit_stream->write(KI_TEST_BITSTREAM_U32, 32); - bit_stream->write(KI_TEST_BITSTREAM_U64, 64); + bit_stream->write(KI_TEST_BITSTREAM_U8); + bit_stream->write(KI_TEST_BITSTREAM_U16); + bit_stream->write>(KI_TEST_BITSTREAM_U24); + bit_stream->write(KI_TEST_BITSTREAM_U32); + bit_stream->write(KI_TEST_BITSTREAM_U64); // Make sure tell is reporting the right position auto position = bit_stream->tell(); @@ -197,15 +197,15 @@ TEST_CASE("BitStream Functionality", "[bit-stream]") sample.read((char *)bit_stream->data(), size); // Read the values and check they are what we are expecting - if (bit_stream->read(8) != KI_TEST_BITSTREAM_U8) + if (bit_stream->read() != KI_TEST_BITSTREAM_U8) FAIL(); - if (bit_stream->read(16) != KI_TEST_BITSTREAM_U16) + if (bit_stream->read() != KI_TEST_BITSTREAM_U16) FAIL(); - if (bit_stream->read(24) != KI_TEST_BITSTREAM_U24) + if (bit_stream->read>() != KI_TEST_BITSTREAM_U24) FAIL(); - if (bit_stream->read(32) != KI_TEST_BITSTREAM_U32) + if (bit_stream->read() != KI_TEST_BITSTREAM_U32) FAIL(); - if (bit_stream->read(64) != KI_TEST_BITSTREAM_U64) + if (bit_stream->read() != KI_TEST_BITSTREAM_U64) FAIL(); SUCCEED();