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
+
+
+
+
+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;
+}