diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bb245ce --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "iostream": "cpp" + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..81118b5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2023, DeltaRazero +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6d49edd --- /dev/null +++ b/README.md @@ -0,0 +1,138 @@ + +# numio-cpp + + +
+ + C++17 + + + Latest release tag version + + + BSD 3-Clause license +
+ +numio-cpp is a C++17 header-only library that provides a set of template classes for flexible and platform-agnostic integer and float data I/O in a customizable and endian-safe manner. + + +## Getting Started + +Copy the files from the `include` directory into your project's `include` directory. + +You can then include `numio.hpp` or `numio/native.hpp` if you are targetting a system that has the same endianness as your current system. + + +## Usage + +The `NumIO` namespace provides two main template classes: `IntIO` for integer data I/O and `FloatIO` for floating-point data I/O. + +Following is a short description of features. + + +### Unpacking and Packing from Vectors + +```cpp +std::vector data_bytes = {0x01, 0x02, 0x03, 0x04}; +int unpacked_value = NumIO::IntIO::unpack(data_bytes); + +std::vector data_bytes; +NumIO::IntIO::pack(1234, data_bytes); +``` + +### Reading/Writing from Streams + +```cpp +std::ifstream input_file("input.bin", std::ios::binary); +std::int32_t value = NumIO::IntIO::read(input_file); + +std::ofstream output_file("output.bin", std::ios::binary); +NumIO::IntIO::write(value, output_file); +``` + +### Endianness + +The `ENDIANNESS_V` template parameter is used to specify the byte order of the data when (un)packing. The data is written correctly regardless of the system's native endianness. Expects a value from the enum class `NumIO::Endian`, which defines the following values: + +* `Endian::LITTLE`: Specifies little-endian byte order, where the least significant byte is stored first (common on x86 and x86-64 architectures). +* `Endian::BIG`: Specifies big-endian byte order, where the most significant byte is stored first (common on some older architectures like Motorola 68k and in network protocols). +* `Endian::NATIVE`: Uses the byte order of the **current system determined at compile-time**. +* `Endian::NETWORK`: Equivalent to `Endian::BIG` and is commonly used for network protocol data where big-endian byte order is prevalent. + +> [!IMPORTANT] +> #### Ensuring Data Portability when Cross-Compiling +> +> When cross-compiling, it's crucial to specify the target system's endianness explicitly, since it is not possible to detect the target system's endianness at compile-time. Defining the system endianness explicitly ensures that data is processed correctly on target systems with a different endianness than the current system that is compiling. +> +> You can use either of these macros: +> +> * `NUMIO_SYSTEM_ENDIANNESS_V`: Sets the endianness based on macro variable name or integer value. Useful in particular when using CMake's [CMAKE\_\\_BYTE_ORDER](https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_BYTE_ORDER.html) or equivalent. Takes one of the following values: +> * `LITTLE_ENDIAN` or as integer value `1234` +> * `BIG ENDIAN` or as integer value `4321` +> * `NUMIO_IS_SYSTEM_LITTLE_ENDIAN_V`: Set the endianness based on a boolean value. + +### Predefined Types + +The `numio/std.hpp` header provides standardized aliases for common data types. It allows you to work with integer and floating-point data without the need to specify custom bit widths or alignments. Here's a quick overview of the provided aliases: + +#### Integer Types + +| **Length** | **Signed Type** | **Unsigned Type** | +|------------|-----------------|-------------------| +| 8-bit | `NumIO::i8_IO` | `NumIO::u8_IO` | +| 16-bit | `NumIO::i16_IO` | `NumIO::u16_IO` | +| 24-bit | `NumIO::i24_IO` | `NumIO::u24_IO` | +| 32-bit | `NumIO::i32_IO` | `NumIO::u32_IO` | +| 64-bit | `NumIO::i64_IO` | `NumIO::u64_IO` | + +#### Floating-Point Types + +| **Precision** | **Type** | +|-------------------|-----------------| +| 32-bit (`float`) | `NumIO::f32_IO` | +| 64-bit (`double`) | `NumIO::f64_IO` | + +### Custom Integer Formats + +In addition to the default integer types supported in C++, NumIO supports custom integer formats with specified bit widths and alignment. This allows you to work with non-standard integer representations. To work with custom integer formats, you can use the IntIO class and provide the necessary template parameters: + +```cpp +template +class IntIO; +``` +* `INT_T`: This will be the container to store the value in. +* `N_BITS`: Specifies the data to (un)pack as an integer with a given amount of bits. +* `ALIGNED_V`: Specifies if the data to (un)pack is aligned to match up with the amount of bytes as used by the container type `INT_T`. + +Example with a 12-bit integer: + +```cpp +// Define I/O for unsigned 12-bit integer and aligned to 4 bytes +using uint12_IO = NumIO::IntIO; + +std::vector data_bytes = {0x00, 0x00, 0x12, 0x34}; // Represents the custom 12-bit integer value 0x123 +uint32_t unpacked_value = uint12_IO::unpack(data_bytes); +``` + +### Customize Defaults + +You can customize default behaviour by defining the following macros before including `numio.hpp`: + +* `NUMIO_DEFAULT_ENDIAN_V`: Set the default endianness for (un)packing data when not explicitly setting the method template parameter `ENDIANNESS_V` (default is `NumIO::Endian::LITTLE`). +* `NUMIO_DEFAULT_ALIGN_V`: Set the default byte alignment for (un)packing data when not explicitly setting the class template parameter `ALIGNED_V` (default is `false`). + + +## License + +© 2023 DeltaRazero. All rights reserved. + +All included scripts, modules, etc. are licensed under the terms of the [BSD 3-Clause license](https://github.com/deltarazero/numio-cpp/LICENSE), unless stated otherwise in the respective files. diff --git a/include/numio.hpp b/include/numio.hpp new file mode 100644 index 0000000..d84547e --- /dev/null +++ b/include/numio.hpp @@ -0,0 +1,480 @@ +#ifndef NUMIO_H +#define NUMIO_H + +// **************************************************************************** + +#include +#include +#include +#include +#include +#include + +// **************************************************************************** + +/// +/// @brief Flexible C++17 set of template classes for platform-agnostic integer and float data I/O. +/// +/// @param NUMIO_DEFAULT_ENDIAN_V `NumIO::Endian` macro parameter setting the default endianness for (un)packing data +/// when not explicitly specified by the user. Defaults to `NumIO::Endian::LITTLE`. +/// @param NUMIO_DEFAULT_ALIGN_V Boolean macro parameter setting the default byte alignment for (un)packing data when +/// not explicitly specified by the user. Defaults to `false`. +/// @param NUMIO_SYSTEM_ENDIANNESS_V Integer macro parameter defining the target system endianness. Defaults to the +/// endianness of the compiling system when not specified. +/// Possible values are: +/// - `LITTLE_ENDIAN` or as integer value `1234` +/// - `BIG_ENDIAN` or as integer value `4321` +/// @param NUMIO_IS_SYSTEM_LITTLE_ENDIAN_V Boolean macro parameter alternative to `NUMIO_SYSTEM_ENDIANNESS_V` for +/// defining the target system endianness. +/// +namespace NumIO +{ + namespace { + // Quick and dirty constexpr ceil function for positive numbers, since ceil is officially not + // constexpr before C++23 but GCC implemented it anyways already + constexpr static int __positive_ceil(float f) + { + int i = static_cast(f); + if (f != static_cast(i)) { + i++; + } + return i; + }; + + // Compile-time way to check the endianness of the system that is executing the compiler. + // It's impossible to determine the endianness of the *target* system, so it's necessary to + // define NUMIO_SYSTEM_ENDIANNESS_V or NUMIO_IS_SYSTEM_LITTLE_ENDIAN_V when cross-compiling. + constexpr static bool __IS_SYSTEM_LITTLE_ENDIAN = [] + { + // There's some GCC and Clang/LLVM header that sets these, but are not defined on MSVC. Therefore + // define these macros temporarily ourselves if not defined + #if !(defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN)) + #define LITTLE_ENDIAN 1234 + #define BIG_ENDIAN 4321 + #define NUMIO_UNDEFINE_ENDIANNESS + #endif + + #if defined(NUMIO_SYSTEM_ENDIANNESS_V) + #if NUMIO_SYSTEM_ENDIANNESS_V == LITTLE_ENDIAN + return true; + #elif NUMIO_SYSTEM_ENDIANNESS_V == BIG_ENDIAN + return false; + #else + #error "NUMIO_SYSTEM_ENDIANNESS_V contains an unsupported value!" + return false; + #endif + #elif defined(NUMIO_IS_SYSTEM_LITTLE_ENDIAN_V) + static_assert(std::is_same::value, "NUMIO_IS_SYSTEM_LITTLE_ENDIAN_V must be a bool type!"); + return NUMIO_IS_SYSTEM_LITTLE_ENDIAN_V; + #else + #ifndef NUMIO_IGNORE_AUTO_ENDIAN + #pragma message(\ + "NUMIO_SYSTEM_ENDIANNESS_V or NUMIO_IS_SYSTEM_LITTLE_ENDIAN_V was not set. The endianness of the current system is used instead."\ + "This message can be surpressed by including \"numio/native.hpp\" or by defining NUMIO_IGNORE_AUTO_ENDIAN."\ + ) + #endif + const int value = 1; + return static_cast(value) == 1; + #endif + + #ifdef NUMIO_UNDEFINE_ENDIANNESS + #undef LITTLE_ENDIAN + #undef BIG_ENDIAN + #endif + }(); + } + + /// + /// @brief Endian modes supported by NumIO. + /// + enum class Endian + { + LITTLE = 0, + BIG = 1, + NATIVE = !__IS_SYSTEM_LITTLE_ENDIAN, + NETWORK = BIG, + }; + + #ifndef NUMIO_DEFAULT_ENDIAN_V + #define NUMIO_DEFAULT_ENDIAN_V Endian::LITTLE + #endif + #ifndef NUMIO_DEFAULT_ALIGN_V + #define NUMIO_DEFAULT_ALIGN_V false + #endif + + /// + /// @brief Template class for doing integer data I/O. + /// + /// @tparam INT_T Integer type to (un)pack. + /// @tparam N_BITS Specifies the data to (un)pack as an integer with a given amount of bits. `INT_T` will become + /// the container to store the value in. + /// @tparam ALIGNED_V Specifies if the data to (un)pack is aligned to match up with the amount of bytes as used by + /// the container type `INT_T`. + /// + template + class IntIO + { + static_assert(std::is_integral_v, "Template parameter INT_T must be an integer type!"); + + + // :: PRIVATE ATTRIBUTES & HELPER FUNCTIONS :: // + private: + + constexpr static short _N_CONTAINER_BITS = []{ + auto bits = std::numeric_limits::digits; + if (std::is_signed_v) + bits++; + return bits; + }(); + static_assert(_N_CONTAINER_BITS >= N_BITS, "N_BITS cannot be bigger than the amount of bits that integer container INT_T can store!"); + + constexpr static short _N_DATA_BYTES = __positive_ceil(N_BITS / 8.0); + + // For padding bytes + constexpr static short _N_ALIGN_BYTES = []{ + if constexpr (N_BITS > 8) + { + // constexpr short N_ALIGN_BYTES = (_N_CONTAINER_BITS - N_BITS) % 16 / 8; + constexpr short N_ALIGN_BYTES = (_N_CONTAINER_BITS - N_BITS) / 8; + return ALIGNED_V + ? N_ALIGN_BYTES + : 0; + } + else + return 0; + }(); + + constexpr static short _get_endianness_offset(Endian endianness) + { + return (endianness == Endian::BIG) ^ !__IS_SYSTEM_LITTLE_ENDIAN + ? N_IO_BYTES - 1 + : 0; + } + + + // :: PUBLIC ATTRIBUTES :: // + public: + + /// + /// @brief The amount of bytes used for the packed data. + /// + constexpr static int N_IO_BYTES = _N_DATA_BYTES + _N_ALIGN_BYTES; + + + // :: UNPACKING FUNCTIONS :: // + public: + + /// + /// @brief Unpacks an integer from a vector of bytes. + /// + /// @tparam ENDIANNESS_V Defines the endianness of the data to process. + /// @param bytes Vector of bytes to read from. + /// @param offset Offset in bytes to extract from of the vector. + /// @return Integer value. + /// + template + static INT_T unpack(std::vector& bytes, const unsigned int offset=0) + { + INT_T result = 0; + + constexpr auto endianness_offset = _get_endianness_offset(ENDIANNESS_V); + + // Copy the input bytes into the result + if constexpr (endianness_offset) // Reversed order + { + for (short i=0; i<_N_DATA_BYTES; i++) + result |= static_cast(bytes[endianness_offset-i+offset]) << (i * 8); + } + else + { + for (short i=0; i<_N_DATA_BYTES; i++) + result |= static_cast(bytes[i+offset]) << (i * 8); + } + + // Bitmask to clear out unwanted/garbage data + constexpr unsigned int VALUE_MASK = (1LL << N_BITS) - 1; + result &= VALUE_MASK; + + if constexpr (N_BITS % _N_CONTAINER_BITS != 0 && std::is_signed_v) + { + // Sign extend the result number if number should be negative + constexpr unsigned int msb = endianness_offset ? _N_ALIGN_BYTES : _N_DATA_BYTES - 1; + constexpr std::uint8_t SIGN_BIT_MASK = (1LL << ((N_BITS % 8) + 7) % 8); + if (bytes[msb+offset] & SIGN_BIT_MASK) + result |= ~VALUE_MASK; + } + + return result; + } + + /// + /// @brief Unpacks an integer from a vector of bytes. + /// + /// @tparam ENDIANNESS_V Defines the endianness of the data to process. + /// @param bytes Vector of bytes to read from. + /// @param offset Offset in bytes to extract from of the vector. + /// @return Integer value. + /// + template + static INT_T unpack(std::vector& bytes, const unsigned int offset=0) + { return unpack(*reinterpret_cast*>(&bytes), offset); } + + + // :: PACKING FUNCTIONS :: // + public: + + /// + /// @brief Packs an integer from a vector of bytes. + /// + /// @tparam ENDIANNESS_V Defines the endianness of the data to process. + /// @param value Input integer value. + /// @param bytes Vector of bytes to write to. + /// + template + static void pack(INT_T value, std::vector& bytes) + { + // Extend vector for packed data + auto offset = bytes.size(); + bytes.resize(offset+N_IO_BYTES); + + // Create a bitmask to isolate the bits that we're interested in + constexpr unsigned int VALUE_MASK = (1LL << N_BITS) - 1; + value &= VALUE_MASK; + + constexpr auto endianness_offset = _get_endianness_offset(ENDIANNESS_V); + + // Copy the bits into the byte vector + if constexpr (endianness_offset) // Reversed order + { + for (short i=0; i<_N_DATA_BYTES; i++) + bytes[endianness_offset-i] = static_cast((value >> (i * 8)) & 0xFF); + } + else + { + for (short i=0; i((value >> (i * 8)) & 0xFF); + } + + return; + } + + /// + /// @brief Packs an integer from a vector of bytes. + /// + /// @tparam ENDIANNESS_V Defines the endianness of the data to process. + /// @param value Input integer value. + /// @param bytes Vector of bytes to write to. + /// + template + static void pack(INT_T value, std::vector& bytes) + { pack(value, *reinterpret_cast*>(&bytes)); } + + + // :: I/O FUNCTIONS :: // + public: + + /// + /// @brief Reads an integer from a binary stream. + /// + /// @tparam ENDIANNESS_V Defines the endianness of the data to process. + /// @param s Binary stream to read from. + /// @return Integer value. + /// + template + static INT_T read(std::istream& s) + { + std::vector buffer(N_IO_BYTES); + s.read(reinterpret_cast(buffer.data()), N_IO_BYTES); + return unpack(buffer, 0); + } + + /// + /// @brief Writes an integer to a binary stream. + /// + /// @tparam ENDIANNESS_V Defines the endianness of the data to process. + /// @param value Input integer value. + /// @param s Binary stream to write to. + /// + template + static void write(INT_T value, std::ostream& s) + { + std::vector buffer; + buffer.reserve(N_IO_BYTES); + pack(value, buffer); + s.write(reinterpret_cast(&buffer[0]), N_IO_BYTES); + return; + } + }; + + + /// + /// @brief Template class for doing float data I/O. + /// + /// @tparam FLOAT_T Float type to (un)pack. + /// + template + class FloatIO + { + static_assert(std::is_floating_point_v, "Template parameter FLOAT_T must be a float type!"); + + + // :: PRIVATE ATTRIBUTES & HELPER FUNCTIONS :: + private: + + constexpr static short _N_DATA_BYTES = sizeof(FLOAT_T); + + constexpr static short _get_endianness_offset(Endian endianness) + { + return (endianness == Endian::BIG) ^ !__IS_SYSTEM_LITTLE_ENDIAN + ? _N_DATA_BYTES - 1 + : 0; + } + + + // :: PUBLIC ATTRIBUTES :: // + public: + + /// + /// @brief The amount of bytes used for the packed data. + /// + const static int N_IO_BYTES = _N_DATA_BYTES; + + + // :: UNPACKING FUNCTIONS :: // + public: + + /// + /// @brief Unpacks a float from a vector of bytes. + /// + /// @tparam ENDIANNESS_V Defines the endianness of the data to process. + /// @param bytes Vector of bytes to read from. + /// @param offset Offset in bytes to extract from of the vector. + /// @return Float value. + /// + template + static FLOAT_T unpack(std::vector& bytes, const unsigned int offset=0) + { + FLOAT_T result = 0; + std::uint8_t* result_bytes = reinterpret_cast(&result); + + constexpr auto endianness_offset = _get_endianness_offset(ENDIANNESS_V); + + // Copy the input bytes into the result + if constexpr (endianness_offset) + { + for (short i=0; i<_N_DATA_BYTES; i++) + result_bytes[i] = static_cast(bytes[endianness_offset-i+offset]); + } + else + { + for (short i=0; i<_N_DATA_BYTES; i++) + result_bytes[i] = static_cast(bytes[i+offset]); + } + + return result; + } + + /// + /// @brief Unpacks a float from a vector of bytes. + /// + /// @tparam ENDIANNESS_V Defines the endianness of the data to process. + /// @param bytes Vector of bytes to read from. + /// @param offset Offset in bytes to extract from of the vector. + /// @return Float value. + /// + template + static FLOAT_T unpack(std::vector& bytes, const unsigned int offset=0) + { return unpack(*reinterpret_cast*>(&bytes), offset); } + + + // :: PACKING FUNCTIONS :: // + public: + + /// + /// @brief Packs a float from a vector of bytes. + /// + /// @tparam ENDIANNESS_V Defines the endianness of the data to process. + /// @param value Input float value. + /// @param bytes Vector of bytes to write to. + /// + template + static void pack(FLOAT_T value, std::vector& bytes) + { + // Extend vector for packed data + auto offset = bytes.size(); + bytes.resize(offset+_N_DATA_BYTES); + + std::uint8_t* value_bytes = reinterpret_cast(&value); + + constexpr auto endianness_offset = _get_endianness_offset(ENDIANNESS_V); + + // Copy the bits into the byte vector + if constexpr (endianness_offset) + { + for (short i=0; i<_N_DATA_BYTES; i++) + bytes[endianness_offset-i+offset] = static_cast(value_bytes[i]); + } + else + { + for (short i=0; i<_N_DATA_BYTES; i++) + bytes[i+offset] = static_cast(value_bytes[i]); + } + + return; + } + + /// + /// @brief Packs a float from a vector of bytes. + /// + /// @tparam ENDIANNESS_V Defines the endianness of the data to process. + /// @param value Input float value. + /// @param bytes Vector of bytes to write to. + /// + template + static void pack(FLOAT_T value, std::vector& bytes) + { pack(value, *reinterpret_cast*>(&bytes)); } + + + // :: I/O FUNCTIONS :: // + public: + + /// + /// @brief Reads a float from a binary stream. + /// + /// @tparam ENDIANNESS_V Defines the endianness of the data to process. + /// @param s Binary stream to read from. + /// @return Float value. + /// + template + static FLOAT_T read(std::istream& s) + { + auto n_io_bytes_ = n_io_bytes(); + std::vector buffer(n_io_bytes_); + s.read(reinterpret_cast(buffer.data()), n_io_bytes_); + return unpack(buffer, 0); + } + + /// + /// @brief Writes an float to a binary stream. + /// + /// @tparam ENDIANNESS_V Defines the endianness of the data to process. + /// @param value Input float value. + /// @param s Binary stream to write to. + /// + template + static void write(FLOAT_T value, std::ostream& s) + { + auto n_io_bytes_ = n_io_bytes(); + std::vector buffer; + buffer.reserve(n_io_bytes_); + pack(value, buffer); + s.write(reinterpret_cast(&buffer[0]), n_io_bytes_); + return; + } + }; + +} + +// **************************************************************************** + +#endif /* NUMIO_H */ diff --git a/include/numio/native.hpp b/include/numio/native.hpp new file mode 100644 index 0000000..3292c43 --- /dev/null +++ b/include/numio/native.hpp @@ -0,0 +1,11 @@ +#ifndef NUMIO_NATIVE_H +#define NUMIO_NATIVE_H + +// **************************************************************************** + +#define NUMIO_IGNORE_AUTO_ENDIAN +#include "../numio.hpp" + +// **************************************************************************** + +#endif /* NUMIO_NATIVE_H */ diff --git a/include/numio/std.hpp b/include/numio/std.hpp new file mode 100644 index 0000000..5f1ce78 --- /dev/null +++ b/include/numio/std.hpp @@ -0,0 +1,29 @@ +#ifndef NUMIO_STD_H +#define NUMIO_STD_H + +// **************************************************************************** + +#include "../numio.hpp" +#include + +namespace NumIO +{ + using i8_IO = IntIO; + using i16_IO = IntIO; + using i24_IO = IntIO; + using i32_IO = IntIO; + using i64_IO = IntIO; + + using u8_IO = IntIO; + using u16_IO = IntIO; + using u24_IO = IntIO; + using u32_IO = IntIO; + using u64_IO = IntIO; + + using f32_IO = FloatIO; + using f64_IO = FloatIO; +} + +// **************************************************************************** + +#endif /* NUMIO_STD_H */ diff --git a/test/debug.cpp b/test/debug.cpp new file mode 100644 index 0000000..027e86b --- /dev/null +++ b/test/debug.cpp @@ -0,0 +1,493 @@ + +// TODO: Make a test pipeline used by a badge on the README displaying if succesful + +// **************************************************************************** + +#include +#include +#include +#include +#include + +#include "../include/numio/native.hpp" +using namespace NumIO; + +// **************************************************************************** + +// Debug program +int main() +{ + // Standard integer + { + // Little endian standard integer + { + #define TEST_ENDIANNESS Endian::LITTLE + #define TEST_INT_BITS 32 + #define TEST_ALIGN true + + using i32_IO = IntIO; + + // Positive + { + std::stringstream buffer; + buffer << (char)0x4F; // LSB + buffer << (char)0x1A; + buffer << (char)0xAD; + buffer << (char)0x05; // MSB + + auto value = IntIO::read(buffer); + assert(value == 95230543); + } + + // Negative + { + std::stringstream buffer; + buffer << (char)0xB1; // LSB + buffer << (char)0xE5; + buffer << (char)0x52; + buffer << (char)0xFA; // MSB + + auto value = IntIO::read(buffer); + assert(value == -95230543); + } + + #undef TEST_ENDIANNESS + #undef TEST_INT_BITS + #undef TEST_ALIGN + + // std::cout << std::bitset(value) << std::endl; + // std::cout << value << std::endl; + } + + // Big endian standard integer + { + #define TEST_ENDIANNESS Endian::BIG + #define TEST_INT_BITS 32 + #define TEST_ALIGN true + + using i32_IO = IntIO; + + // Positive + { + std::stringstream buffer; + buffer << (char)0x05; // MSB + buffer << (char)0xAD; + buffer << (char)0x1A; + buffer << (char)0x4F; // LSB + + auto value = i32_IO::read(buffer); + assert(value == 95230543); + } + + // Negative + { + std::stringstream buffer; + buffer << (char)0xFA; // MSB + buffer << (char)0x52; + buffer << (char)0xE5; + buffer << (char)0xB1; // LSB + + auto value = i32_IO::read(buffer); + assert(value == -95230543); + } + + #undef TEST_ENDIANNESS + #undef TEST_INT_BITS + #undef TEST_ALIGN + } + } + + // 24-bit integer + { + // Little endian 24-bit aligned integer + { + #define TEST_ENDIANNESS Endian::LITTLE + #define TEST_INT_BITS 24 + #define TEST_ALIGN true + + using i24a_IO = IntIO; + + // Positive integer + { + std::stringstream buffer; + buffer << (char)0b11111111; // LSB + buffer << (char)0b11111111; + buffer << (char)0b01111111; // MSB (value) + buffer << (char)0b00000000; // MSB (container) + + auto value = i24a_IO::read(buffer); + assert(value == 8388607); + } + + // Negative integer + { + std::stringstream buffer; + buffer << (char)0b11111111; // LSB + buffer << (char)0b11111111; + buffer << (char)0b11111111; // MSB (value) + buffer << (char)0b00000000; // MSB (container) + + auto value = i24a_IO::read(buffer); + assert(value == -1); + } + + // Garbage data in MSB + { + std::stringstream buffer; + buffer << (char)0x2B; // LSB + buffer << (char)0xF0; + buffer << (char)0x7F; // MSB (value) + buffer << (char)0x42; // MSB (container) + + auto value = i24a_IO::read(buffer); + assert(value == 8384555); + } + + #undef TEST_ENDIANNESS + #undef TEST_INT_BITS + #undef TEST_ALIGN + } + + // Big endian 24-bit aligned integer + { + #define TEST_ENDIANNESS Endian::BIG + #define TEST_INT_BITS 24 + #define TEST_ALIGN true + + using i24a_IO = IntIO; + + // Positive integer + { + std::stringstream buffer; + buffer << (char)0b00000000; // MSB (container) + buffer << (char)0b01111111; // MSB (value) + buffer << (char)0b11111111; + buffer << (char)0b11111111; // LSB + + auto value = i24a_IO::read(buffer); + assert(value == 8388607); + } + + // Negative integer + { + std::stringstream buffer; + buffer << (char)0b00000000; // MSB (container) + buffer << (char)0b11111111; // MSB (value) + buffer << (char)0b11111111; + buffer << (char)0b11111111; // LSB + + auto value = i24a_IO::read(buffer); + assert(value == -1); + } + + // Garbage data in MSB (container) + { + std::stringstream buffer; + buffer << (char)0x42; // MSB (container) + buffer << (char)0x7F; // MSB (value) + buffer << (char)0xF0; + buffer << (char)0x2B; // LSB + + auto value = i24a_IO::read(buffer); + assert(value == 8384555); + } + + #undef TEST_ENDIANNESS + #undef TEST_INT_BITS + #undef TEST_ALIGN + } + + // Little endian 24-bit non-aligned integer + { + #define TEST_ENDIANNESS Endian::LITTLE + #define TEST_INT_BITS 24 + #define TEST_ALIGN false + + using i24a_IO = IntIO; + + // Positive integer + { + std::stringstream buffer; + buffer << (char)0b11111111; // LSB + buffer << (char)0b11111111; + buffer << (char)0b01111111; // MSB + buffer << (char)0b00000000; // Padding + + auto value = i24a_IO::read(buffer); + assert(value == 8388607); + } + + // Negative integer + { + std::stringstream buffer; + buffer << (char)0b11111111; // LSB + buffer << (char)0b11111111; + buffer << (char)0b11111111; // MSB + buffer << (char)0b00000000; // Padding + + auto value = i24a_IO::read(buffer); + assert(value == -1); + } + + // Garbage data in padding + { + std::stringstream buffer; + buffer << (char)0x2B; // LSB + buffer << (char)0xF0; + buffer << (char)0x7F; // MSB + buffer << (char)0x42; // Padding + + auto value = i24a_IO::read(buffer); + assert(value == 8384555); + } + + #undef TEST_ENDIANNESS + #undef TEST_INT_BITS + #undef TEST_ALIGN + } + + // Big endian 24-bit non-aligned integer + { + #define TEST_ENDIANNESS Endian::BIG + #define TEST_INT_BITS 24 + #define TEST_ALIGN false + + using i24a_IO = IntIO; + + // Positive integer + { + std::stringstream buffer; + buffer << (char)0b01111111; // MSB + buffer << (char)0b11111111; + buffer << (char)0b11111111; // LSB + buffer << (char)0b00000000; // Padding + + auto value = i24a_IO::read(buffer); + assert(value == 8388607); + } + + // Negative integer + { + std::stringstream buffer; + buffer << (char)0b11111111; // MSB + buffer << (char)0b11111111; + buffer << (char)0b11111111; // LSB + buffer << (char)0b00000000; // Padding + + auto value = i24a_IO::read(buffer); + assert(value == -1); + } + + // Garbage data in padding + { + std::stringstream buffer; + buffer << (char)0x7F; // MSB + buffer << (char)0xF0; + buffer << (char)0x2B; // LSB + buffer << (char)0x42; // Padding + + auto value = i24a_IO::read(buffer); + assert(value == 8384555); + } + + #undef TEST_ENDIANNESS + #undef TEST_INT_BITS + #undef TEST_ALIGN + } + } + + // 13-bit integer + { + // Little endian 13-bit aligned integer + { + #define TEST_ENDIANNESS Endian::LITTLE + #define TEST_INT_BITS 13 + #define TEST_ALIGN true + + using i13a_IO = IntIO; + + // Positive integer + { + std::stringstream buffer; + buffer << (char)0b11111111; // LSB + buffer << (char)0b00001111; // MSB + buffer << (char)0b00000000; // Padding + buffer << (char)0b00000000; // Padding + + auto value = i13a_IO::read(buffer); + assert(value == 4095); + } + + // Negative integer + { + std::stringstream buffer; + buffer << (char)0b11111111; // LSB + buffer << (char)0b00011111; // MSB + buffer << (char)0b00000000; // Padding + buffer << (char)0b00000000; // Padding + + auto value = i13a_IO::read(buffer); + assert(value == -1); + } + + // Garbage data in MSB + { + std::stringstream buffer; + buffer << (char)0b11111111; // LSB + buffer << (char)0b01011111; // MSB + buffer << (char)0b00000000; // Padding + buffer << (char)0b00000000; // Padding + + auto value = i13a_IO::read(buffer); + assert(value == -1); + } + + #undef TEST_ENDIANNESS + #undef TEST_INT_BITS + #undef TEST_ALIGN + } + + // Big endian 13-bit aligned integer + { + #define TEST_ENDIANNESS Endian::BIG + #define TEST_INT_BITS 13 + #define TEST_ALIGN true + + using i13a_IO = IntIO; + + // Positive integer + { + std::stringstream buffer; + buffer << (char)0b00000000; // Padding + buffer << (char)0b00000000; // Padding + buffer << (char)0b00001111; // MSB + buffer << (char)0b11111111; // LSB + + auto value = i13a_IO::read(buffer); + assert(value == 4095); + } + + // Negative integer + { + std::stringstream buffer; + buffer << (char)0b00000000; // Padding + buffer << (char)0b00000000; // Padding + buffer << (char)0b00011111; // MSB + buffer << (char)0b11111111; // LSB + + auto value = i13a_IO::read(buffer); + assert(value == -1); + } + + // Garbage data in MSB + { + std::stringstream buffer; + buffer << (char)0b00000000; // Padding + buffer << (char)0b00000000; // Padding + buffer << (char)0b01011111; // MSB + buffer << (char)0b11111111; // LSB + + auto value = i13a_IO::read(buffer); + assert(value == -1); + } + + #undef TEST_ENDIANNESS + #undef TEST_INT_BITS + #undef TEST_ALIGN + } + } + + // Float + { + // Float + { + using f32_IO = FloatIO; + + // Little endian + { + #define TEST_ENDIANNESS Endian::LITTLE + + std::stringstream buffer; + buffer << (char)0x52; // LSB + buffer << (char)0x06; + buffer << (char)0x9E; + buffer << (char)0x3F; // MSB + + auto value = f32_IO::read(buffer); + float comparison = 1.23456789; + assert(value == comparison); + + #undef TEST_ENDIANNESS + } + + // Big endian + { + #define TEST_ENDIANNESS Endian::BIG + + std::stringstream buffer; + buffer << (char)0x3F; // MSB + buffer << (char)0x9E; + buffer << (char)0x06; + buffer << (char)0x52; // LSB + + auto value = f32_IO::read(buffer); + float comparison = 1.23456789; + assert(value == comparison); + + #undef TEST_ENDIANNESS + } + } + + // Double + { + using f64_IO = FloatIO; + + // Little endian + { + #define TEST_ENDIANNESS Endian::LITTLE + + std::stringstream buffer; + buffer << (char)0x1B; // LSB + buffer << (char)0xDE; + buffer << (char)0x83; + buffer << (char)0x42; + buffer << (char)0xCA; + buffer << (char)0xC0; + buffer << (char)0xF3; + buffer << (char)0x3F; // MSB + + auto value = f64_IO::read(buffer); + double comparison = 1.23456789; + assert(value == comparison); + + #undef TEST_ENDIANNESS + } + + // Big endian + { + #define TEST_ENDIANNESS Endian::BIG + + std::stringstream buffer; + buffer << (char)0x3F; // MSB + buffer << (char)0xF3; + buffer << (char)0xC0; + buffer << (char)0xCA; + buffer << (char)0x42; + buffer << (char)0x83; + buffer << (char)0xDE; + buffer << (char)0x1B; // LSB + + auto value = f64_IO::read(buffer); + double comparison = 1.23456789; + assert(value == comparison); + + #undef TEST_ENDIANNESS + } + } + } + + // std::cout << std::bitset<32>(value) << std::endl; + // std::cout << value << std::endl; + + return 0; +}