diff --git a/deps/ox/src/ox/mc/intops.hpp b/deps/ox/src/ox/mc/intops.hpp index a141971a..5d37b7ab 100644 --- a/deps/ox/src/ox/mc/intops.hpp +++ b/deps/ox/src/ox/mc/intops.hpp @@ -13,20 +13,23 @@ namespace ox::mc { +template +static constexpr auto Bits = sizeof(T) << 3; + /** * Returns highest bit other than possible signed bit. * Bit numbering starts at 0. */ template -[[nodiscard]] constexpr auto highestBit(I val) noexcept { - constexpr auto Bits = sizeof(I) * 8; +[[nodiscard]] constexpr std::size_t highestBit(I val) noexcept { + auto shiftStart = sizeof(I) * 8 - 1; // find most significant non-sign indicator bit - std::size_t highestBit = Bits; + std::size_t highestBit = 0; // start at one bit lower if signed if constexpr(ox::is_signed) { - --highestBit; + --shiftStart; } - for (int i = Bits - 1; i > -1; --i) { + for (int i = shiftStart; i > -1; --i) { const auto bitValue = (val >> i) & 1; if (bitValue) { highestBit = i; @@ -36,11 +39,12 @@ template return highestBit; } +static_assert(highestBit(int8_t(0b10000000)) == 0); static_assert(highestBit(1) == 0); static_assert(highestBit(2) == 1); static_assert(highestBit(4) == 2); static_assert(highestBit(8) == 3); -static_assert(highestBit(1 << 31) == 31); +static_assert(highestBit(uint64_t(1) << 31) == 31); static_assert(highestBit(uint64_t(1) << 63) == 63); struct McInt { @@ -50,28 +54,31 @@ struct McInt { }; template -McInt encodeInteger(I val) noexcept { +[[nodiscard]] McInt encodeInteger(I input) noexcept { McInt out; + // move input to uint64_t to allow consistent bit manipulation, and to avoid + // overflow concerns + uint64_t val = *reinterpret_cast>*>(&input); if (val) { // bits needed to represent number factoring in space possibly // needed for signed bit const auto bits = highestBit(val) + 1 + (ox::is_signed ? 1 : 0); // bytes needed to store value std::size_t bytes = bits / 8 + (bits % 8 != 0); - auto bitsAvailable = bytes * 8; + const auto bitsAvailable = bytes * 8; // bits available to integer value + const auto bitsNeeded = bits + bytes; // factor in bits needed for bytesIndicator (does not affect bytesIndicator) // bits for integer + bits neded to represent bytes > bits available - if (bits + bytes > bitsAvailable && bytes != 9) { + if (bitsNeeded > bitsAvailable && bytes != 9) { ++bytes; - bitsAvailable = bytes * 8; } - const auto bytesIndicator = onMask(bytes - 1); // << (7 - bytes); + const auto bytesIndicator = onMask(bytes - 1); // move sign bit if constexpr(ox::is_signed) { if (val < 0) { - val ^= int64_t(1) << (sizeof(I) * 8 - 1); - val |= int64_t(1) << (bitsAvailable - 1); + *reinterpret_cast(&val) ^= uint64_t(1) << (sizeof(I) * 8 - 1); + *reinterpret_cast(&val) |= uint64_t(1) << (bitsAvailable - 1); } } // ensure we are copying from little endian represenstation diff --git a/deps/ox/src/ox/mc/test/tests.cpp b/deps/ox/src/ox/mc/test/tests.cpp index 6cb80611..c75800f6 100644 --- a/deps/ox/src/ox/mc/test/tests.cpp +++ b/deps/ox/src/ox/mc/test/tests.cpp @@ -142,11 +142,12 @@ std::map tests = { constexpr auto check = [](McInt val, std::vector &&expected) { if (val.length != expected.size()) { + std::cout << "val.length: " << val.length << ", expected: " << expected.size() << '\n'; return OxError(1); } for (std::size_t i = 0; i < expected.size(); i++) { if (expected[i] != val.data[i]) { - std::cout << static_cast(val.data[i]) << '\n'; + std::cout << i << ": " << static_cast(val.data[i]) << '\n'; return OxError(1); } } @@ -165,14 +166,23 @@ std::map tests = { return OxError(0); }; - oxAssert(check(encodeInteger(int64_t(1)), {0b0010}), "Encode 1 fail"); - oxAssert(check(encodeInteger(int64_t(2)), {0b0100}), "Encode 2 fail"); - oxAssert(check(encodeInteger(int64_t(3)), {0b0110}), "Encode 3 fail"); - oxAssert(check(encodeInteger(int64_t(4)), {0b1000}), "Encode 4 fail"); - oxAssert(check(encodeInteger(int64_t(128)), {0b0001, 0b10}), "Encode 128 fail"); - oxAssert(check(encodeInteger(int64_t(129)), {0b0101, 0b10}), "Encode 129 fail"); - oxAssert(check(encodeInteger(int64_t(130)), {0b1001, 0b10}), "Encode 130 fail"); - oxAssert(check(encodeInteger(int64_t(131)), {0b1101, 0b10}), "Encode 131 fail"); + oxAssert(check(encodeInteger(int64_t(1)), {0b00000010}), "Encode 1 fail"); + oxAssert(check(encodeInteger(int64_t(2)), {0b00000100}), "Encode 2 fail"); + oxAssert(check(encodeInteger(int64_t(3)), {0b00000110}), "Encode 3 fail"); + oxAssert(check(encodeInteger(int64_t(4)), {0b00001000}), "Encode 4 fail"); + oxAssert(check(encodeInteger(int64_t(128)), {0b00000001, 0b10}), "Encode 128 fail"); + oxAssert(check(encodeInteger(int64_t(129)), {0b00000101, 0b10}), "Encode 129 fail"); + oxAssert(check(encodeInteger(int64_t(130)), {0b00001001, 0b10}), "Encode 130 fail"); + oxAssert(check(encodeInteger(int64_t(131)), {0b00001101, 0b10}), "Encode 131 fail"); + + oxAssert(check(encodeInteger(int64_t(-1)), {255, 255, 255, 255, 255, 255, 255, 255, 255}), "Encode -1 fail"); + oxAssert(check(encodeInteger(int64_t(-2)), {255, 254, 255, 255, 255, 255, 255, 255, 255}), "Encode -2 fail"); + oxAssert(check(encodeInteger(int64_t(-3)), {255, 253, 255, 255, 255, 255, 255, 255, 255}), "Encode -3 fail"); + oxAssert(check(encodeInteger(int64_t(-4)), {255, 252, 255, 255, 255, 255, 255, 255, 255}), "Encode -4 fail"); + oxAssert(check(encodeInteger(int64_t(-128)), {255, 128, 255, 255, 255, 255, 255, 255, 255}), "Encode -128 fail"); + oxAssert(check(encodeInteger(int64_t(-129)), {255, 127, 255, 255, 255, 255, 255, 255, 255}), "Encode -129 fail"); + oxAssert(check(encodeInteger(int64_t(-130)), {255, 126, 255, 255, 255, 255, 255, 255, 255}), "Encode -130 fail"); + oxAssert(check(encodeInteger(int64_t(-131)), {255, 125, 255, 255, 255, 255, 255, 255, 255}), "Encode -131 fail"); oxAssert(check(encodeInteger(uint64_t(1)), {0b0010}), "Encode 1 fail"); oxAssert(check(encodeInteger(uint64_t(2)), {0b0100}), "Encode 2 fail"); diff --git a/deps/ox/src/ox/mc/write.hpp b/deps/ox/src/ox/mc/write.hpp index 7904e4f6..94f2eea2 100644 --- a/deps/ox/src/ox/mc/write.hpp +++ b/deps/ox/src/ox/mc/write.hpp @@ -142,7 +142,6 @@ Error MetalClawWriter::appendInteger(I val) { if (val) { if (m_buffIt + sizeof(I) < m_buffLen) { LittleEndian leVal = val; - mc::encodeInteger(val); // bits needed to represent number factoring in space possibly needed // for signed bit const auto bits = mc::highestBit(val) + (ox::is_signed ? 1 : 0) / 8;