From 5d9d0e4d27391ef019945e77114efe7b61aff09f Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 20 Feb 2017 23:16:54 -0500 Subject: [PATCH 1/6] wrote method for reading quantitized float value; did not yet test --- src/common/bitstream.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/common/bitstream.h b/src/common/bitstream.h index 3f16708..39b6198 100644 --- a/src/common/bitstream.h +++ b/src/common/bitstream.h @@ -3,6 +3,7 @@ #include #include #include +#include #define BITS_TO_BYTES(bits) (((bits) + 7) / 8) @@ -394,6 +395,30 @@ class BitStream { readBytes((uint8_t*)str.data(), strLen * 2); } + void readQuantitizedFloat(float& data, const size_t numBits, const float max, const float min = 0.0f) { + long outBuf; + readBits((uint8_t*)&outBuf, numBits); + long field = static_cast(outBuf); + float range = max - min; + if(range < -1.0f) { + throw std::invalid_argument("quantitized float range - min value must never exceed max"); + } + else if(range == 0.0f || field == 0L) { + data = min; + } + else { + long fieldMax = (1 << numBits) - 1; + data = std::floor(static_cast(field) * range / fieldMax + 0.5f); + if(data < 0.0f) { + data = 0.0f; + } + else if(data > range) { + data = range; + } + data += min; + } + } + /** * Templated write functions. */ From ccb474b92e9842fb943bab9f7affbe297aa065fc Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 21 Feb 2017 09:09:39 -0500 Subject: [PATCH 2/6] wrote method for writing quantitized float value; wrote function for comparing float values; did not yet test --- src/common/bitstream.h | 27 ++++++++++++++++++++++++--- src/common/util.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/common/bitstream.h b/src/common/bitstream.h index 39b6198..e45774c 100644 --- a/src/common/bitstream.h +++ b/src/common/bitstream.h @@ -3,7 +3,6 @@ #include #include #include -#include #define BITS_TO_BYTES(bits) (((bits) + 7) / 8) @@ -400,7 +399,7 @@ class BitStream { readBits((uint8_t*)&outBuf, numBits); long field = static_cast(outBuf); float range = max - min; - if(range < -1.0f) { + if(range < -0.0f) { throw std::invalid_argument("quantitized float range - min value must never exceed max"); } else if(range == 0.0f || field == 0L) { @@ -408,7 +407,7 @@ class BitStream { } else { long fieldMax = (1 << numBits) - 1; - data = std::floor(static_cast(field) * range / fieldMax + 0.5f); + data = field * range / fieldMax; if(data < 0.0f) { data = 0.0f; } @@ -472,6 +471,28 @@ class BitStream { writeBytes((uint8_t*)str.data(), strLen * 2); } + void writeQuantitizedFloat(float& data, const size_t numBits, const float max, const float min = 0.0f) { + if(data < min) { + throw std::invalid_argument("quantitized float range - data is less than min value"); + } + float range = max - min; + long inBuf; + if(range < 0.0f) { + throw std::invalid_argument("quantitized float range - min value must never exceed max"); + } + else if(range == 0.0f || data == 0.0f) { + inBuf = 0L; + } + else { + long fieldMax = (1 << numBits) - 1; + inBuf = static_cast((data - min) * fieldMax / range); + if(inBuf > fieldMax) { + inBuf = fieldMax; + } + } + writeBits((uint8_t*)&inBuf, numBits); + } + std::vector& buf; private: diff --git a/src/common/util.cpp b/src/common/util.cpp index 921c308..612c1cc 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "bitstream.h" #include "util.h" @@ -79,3 +80,42 @@ std::vector hexToBytes(std::string hexStr) { return bytes; } + +/** + Compare two float values for relative equality or the direction of inequality. + @param a the first float + @param b the second float + @param error the margin of difference between parameter a and parameter b + @throw invalid_argument if error is greater than 4 * 1024 * 1024 + @return a value representing the equality or inequality of the two input numbers +*/ +const int compareFloatValues(const float a, const float b, const size_t error) { + /* + A simple decimal comparison might involve subtraction, fabs, and the result being less than a smaller decimal. + The method used, however, converts the IEEE float values into integers that can be lexicographically ordered. + Error becomes a count of "the maximum number of lexico-floats allowed between lexico-a and lexico-b." + To avoid encountering a NaN value, error can not risk being too big. + */ + if(error >= 4194304) { + throw std::invalid_argument("float comparison - error margin too big"); + } + + int aInt = *(int*)&a; + if(aInt < 0) { + aInt = 0x80000000 - aInt; //negative zero -> positive zero? + } + int bInt = *(int*)&b; + if(bInt < 0) { + bInt = 0x80000000 - bInt; //negative zero -> positive zero? + } + //borrows from Java's Comparator compare(T o1, T o2) model for return values. + if(std::abs(aInt - bInt) <= error) { + return 0; + } + else if(aInt + error < bInt) { + return -1; + } + else { + return 1; + } +} From 8aa1cb66a15d91c0fa4eda5743a6aab57158f14b Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 21 Feb 2017 23:34:31 -0500 Subject: [PATCH 3/6] added passing a read test and a read&write test for quantitized floats --- src/common/bitstream.h | 14 +++++++----- src/common/bitstream_test.cpp | 41 +++++++++++++++++++++++++++++++++++ src/common/util.cpp | 35 +++++++++++++++--------------- src/common/util.h | 2 ++ 4 files changed, 69 insertions(+), 23 deletions(-) diff --git a/src/common/bitstream.h b/src/common/bitstream.h index e45774c..65c8630 100644 --- a/src/common/bitstream.h +++ b/src/common/bitstream.h @@ -395,19 +395,18 @@ class BitStream { } void readQuantitizedFloat(float& data, const size_t numBits, const float max, const float min = 0.0f) { - long outBuf; + long outBuf = 0L; readBits((uint8_t*)&outBuf, numBits); - long field = static_cast(outBuf); float range = max - min; if(range < -0.0f) { throw std::invalid_argument("quantitized float range - min value must never exceed max"); } - else if(range == 0.0f || field == 0L) { + else if(range == 0.0f || outBuf == 0L) { data = min; } else { long fieldMax = (1 << numBits) - 1; - data = field * range / fieldMax; + data = outBuf * range / fieldMax; if(data < 0.0f) { data = 0.0f; } @@ -471,7 +470,7 @@ class BitStream { writeBytes((uint8_t*)str.data(), strLen * 2); } - void writeQuantitizedFloat(float& data, const size_t numBits, const float max, const float min = 0.0f) { + void writeQuantitizedFloat(const float& data, const size_t numBits, const float max, const float min = 0.0f) { if(data < min) { throw std::invalid_argument("quantitized float range - data is less than min value"); } @@ -486,7 +485,10 @@ class BitStream { else { long fieldMax = (1 << numBits) - 1; inBuf = static_cast((data - min) * fieldMax / range); - if(inBuf > fieldMax) { + if(inBuf < 0L) { + inBuf = 0L; + } + else if(inBuf > fieldMax) { inBuf = fieldMax; } } diff --git a/src/common/bitstream_test.cpp b/src/common/bitstream_test.cpp index 011f807..2ddd6ed 100644 --- a/src/common/bitstream_test.cpp +++ b/src/common/bitstream_test.cpp @@ -119,6 +119,44 @@ void testBitstreamReadBits() { } } +void testBitstreamReadQuantitizedFloat() { + static std::vector expectedBuf = std::vector({ + 0x6C, 0x2D, 0x76, 0x55, 0x35, 0xCA, 0x16 //6C2D7 65535 CA16 + }); + BitStream bitstream(expectedBuf); + + float x; + float y; + float z; + bitstream.readQuantitizedFloat(x, 20, 8192.0f); + bitstream.readQuantitizedFloat(y, 20, 8192.0f); + bitstream.readQuantitizedFloat(z, 16, 1024.0f); + assertEqual( compareFloatValues(x, 3674.85f, 100), 0); + assertEqual( compareFloatValues(y, 2726.79f, 100), 0); + assertEqual( compareFloatValues(z, 91.1576f, 100), 0); +} + +void testBitstreamWriteQuantitizedFloat() { + static std::vector expectedBuf = std::vector({ + 0x6C, 0x2D, 0x76, 0x55, 0x35, 0xCA, 0x16 //6C2D7 65535 CA16 + }); + BitStream expected_bitstream(expectedBuf); + float x; + float y; + float z; + expected_bitstream.readQuantitizedFloat(x, 20, 8192.0f); + expected_bitstream.readQuantitizedFloat(y, 20, 8192.0f); + expected_bitstream.readQuantitizedFloat(z, 16, 1024.0f); + + std::vector bitstreamBuf; + BitStream test_bitstream(bitstreamBuf); + test_bitstream.writeQuantitizedFloat(x, 20, 8192.0f); + test_bitstream.writeQuantitizedFloat(y, 20, 8192.0f); + test_bitstream.writeQuantitizedFloat(z, 16, 1024.0f); + + assertBuffersEqual(bitstreamBuf, expectedBuf); +} + void testBitstream() { testBitstreamWriteBitsBasic(); testBitstreamWriteBits(); @@ -128,4 +166,7 @@ void testBitstream() { // TODO: Test mixed bits and bytes write/read // TODO: Test primitive write/read // TODO: Test string write/read + + testBitstreamReadQuantitizedFloat(); + testBitstreamWriteQuantitizedFloat(); } diff --git a/src/common/util.cpp b/src/common/util.cpp index 612c1cc..5dc4a21 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -85,37 +85,38 @@ std::vector hexToBytes(std::string hexStr) { Compare two float values for relative equality or the direction of inequality. @param a the first float @param b the second float - @param error the margin of difference between parameter a and parameter b + @param epsilon the margin of difference between parameter a and parameter b @throw invalid_argument if error is greater than 4 * 1024 * 1024 @return a value representing the equality or inequality of the two input numbers */ -const int compareFloatValues(const float a, const float b, const size_t error) { +const int compareFloatValues(const float a, const float b, const size_t epsilon) { /* A simple decimal comparison might involve subtraction, fabs, and the result being less than a smaller decimal. - The method used, however, converts the IEEE float values into integers that can be lexicographically ordered. - Error becomes a count of "the maximum number of lexico-floats allowed between lexico-a and lexico-b." - To avoid encountering a NaN value, error can not risk being too big. + This method converts the IEEE float values into lexicographically-ordered two's-complement integers. + If (float)a > (float)b then (int)a > (int)b; but, precision concerns are lessened. + The error becomes a count of "the maximum number of lexico-numbers allowed between lexico-a and lexico-b." + To avoid encountering a NaN value, error can not risk being too big, however. */ - if(error >= 4194304) { + if(epsilon >= 4194304) { throw std::invalid_argument("float comparison - error margin too big"); } - int aInt = *(int*)&a; - if(aInt < 0) { - aInt = 0x80000000 - aInt; //negative zero -> positive zero? + int aint = *(int*)&a; + if(aint < 0) { + aint = 0x80000000 - aint; //conform negative scale to lexicographic order } - int bInt = *(int*)&b; - if(bInt < 0) { - bInt = 0x80000000 - bInt; //negative zero -> positive zero? + int bint = *(int*)&b; + if(bint < 0) { + bint = 0x80000000 - bint; //conform negative scale to lexicographic order } - //borrows from Java's Comparator compare(T o1, T o2) model for return values. - if(std::abs(aInt - bInt) <= error) { + //borrows from Java's Comparator compare(T o1, T o2) model for return values + if(std::abs(aint - bint) <= epsilon) { return 0; } - else if(aInt + error < bInt) { - return -1; + else if(aint + epsilon < bint) { + return 1; } else { - return 1; + return -1; } } diff --git a/src/common/util.h b/src/common/util.h index de58c7b..d8fa27e 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -13,3 +13,5 @@ std::size_t getTimeNanoseconds(); void utilSleep(size_t ms); std::vector hexToBytes(std::string hexStr); + +const int compareFloatValues(const float a, const float b, const size_t error); From 5fb01509a828cd9e9c45008c44b2bd9175899e76 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 22 Feb 2017 20:25:56 -0500 Subject: [PATCH 4/6] separated BitStream into a header file and source file; pruned superfluous #includes from other files and cleaned up header referencing --- src/common/bitstream.cpp | 392 ++++++++++++++++++++++++ src/common/bitstream.h | 407 +++---------------------- src/common/bitstream_test.cpp | 70 ++--- src/common/packet/pkt_test_control.cpp | 1 + src/common/packet/pkt_test_crypto.cpp | 1 + src/common/packet/pkt_test_game.cpp | 1 + src/common/test.cpp | 1 + src/common/test.h | 2 - src/common/util.cpp | 72 ++--- src/common/util.h | 14 +- 10 files changed, 517 insertions(+), 444 deletions(-) create mode 100644 src/common/bitstream.cpp diff --git a/src/common/bitstream.cpp b/src/common/bitstream.cpp new file mode 100644 index 0000000..b5d7a71 --- /dev/null +++ b/src/common/bitstream.cpp @@ -0,0 +1,392 @@ +#include +#include "bitstream.h" +#include "util.h" + +#define BITS_TO_BYTES(bits) (((bits) + 7) / 8) + + +BitStream::BitStream(std::vector& exisitingBuf) : + buf(exisitingBuf), + streamBitPos(0), + lastError(Error::NONE) { + +} + +size_t BitStream::getSizeBits() const { + return buf.size() * 8; +} + +size_t BitStream::getRemainingBits() const { + size_t sizeBits = getSizeBits(); + if(streamBitPos >= sizeBits) { + return 0; + } + else { + return sizeBits - streamBitPos; + } +} + +size_t BitStream::getRemainingBytes() const { + size_t usedBytes = BITS_TO_BYTES(streamBitPos); + if(usedBytes >= buf.size()) { + return 0; + } + else { + return buf.size() - usedBytes; + } +} + +uint8_t* BitStream::getHeadBytePtr() const { + return buf.data() + (streamBitPos / 8); +} + +std::vector::iterator BitStream::getHeadIterator() const { + return buf.begin() + (streamBitPos / 8); +} + +uint8_t* BitStream::getPosBytePtr(size_t bitPos) const { + return buf.data() + (bitPos / 8); +} + +size_t BitStream::getPos() const { + return streamBitPos; +} + +void BitStream::setPos(size_t pos) { + if(pos > buf.size() * 8) { + lastError = Error::INVALID_STREAM_POS; + return; + } + + streamBitPos = pos; +} + +void BitStream::deltaPos(int32_t delta) { + // TODO: Signedness is funky here. Figure out a better way. + int32_t newPos = (int32_t)streamBitPos + delta; + + if(newPos < 0 || newPos > buf.size() * 8) { + lastError = Error::INVALID_STREAM_POS; + return; + } + + streamBitPos = newPos; +} + +void BitStream::alignPos() { + size_t bitsIn = (streamBitPos % 8); + if(bitsIn != 0) { + deltaPos(8 - bitsIn); + } +} + +BitStream::Error BitStream::getLastError() { + return lastError; +} + +void BitStream::readBytes(uint8_t* outBuf, size_t numBytes, bool peek) { + if(numBytes == 0) { + return; + } + + // If the stream position is not aligned on a byte boundary, need to do bit reading + if((streamBitPos & 0x7) != 0) { + readBits(outBuf, numBytes * 8, peek); + return; + } + + size_t remainingBytes = getRemainingBytes(); + if(remainingBytes < numBytes) { + lastError = Error::READ_TOO_MUCH; + return; + } + + memcpy(outBuf, getHeadBytePtr(), numBytes); + + if(!peek) { + streamBitPos += numBytes * 8; + } +} + +bool BitStream::readBit(bool peek) { + size_t remainingBits = getRemainingBits(); + if(remainingBits < 1) { + lastError = Error::READ_TOO_MUCH; + return false; + } + + bool result = (*getHeadBytePtr() & (0x80 >> (streamBitPos & 0x7))) != 0; + + streamBitPos++; + + return result; +} + +void BitStream::readBits(uint8_t* outBuf, size_t numBits, bool peek) { + //NOTE: WILL CLOBBER any existing data in the bytes that get written to + if(numBits == 0) { + return; + } + + // If the stream position is aligned on a byte boundary and we are reading a quantity of bits divisble by 8, we can use faster byte reading + if((streamBitPos & 0x7) == 0 && (numBits & 0x7) == 0) { + readBytes(outBuf, numBits / 8, peek); + return; + } + + size_t remainingBits = getRemainingBits(); + if(remainingBits < numBits) { + lastError = Error::READ_TOO_MUCH; + return; + } + + size_t prevStreamBitPos = streamBitPos; + + unsigned char* srcPtr = buf.data(); + + size_t bitsToRead = numBits; + + while(true) { + size_t byteOffset = streamBitPos / 8; + size_t bitOffset = (streamBitPos & 0x7); + size_t bitsLeft = 8 - bitOffset; + + if(bitsLeft >= bitsToRead) { + // We have enough bits remaining in the current source byte to finish the read, so read them all into the current destination byte + // Shift the remaining bits right to be flush with the start of the current destination byte, and mask out the bits to the left + size_t bitGap = (bitsLeft - bitsToRead); + *outBuf = ((srcPtr[byteOffset] >> bitGap) & ((1 << bitsToRead) - 1)); + streamBitPos += bitsToRead; + break; + } + else { + // We don't have enough bits remaining in the current byte to finish the read, so read as much as we need into the current destination byte + // Shift the current source byte left to reserve a number of bits on the right to read from the next byte, and mask out the bits to the left + size_t bitsToWriteToSrc = std::min(bitsToRead, (size_t)8); + size_t bitsToReserve = bitsToWriteToSrc - bitsLeft; + + *outBuf = ((srcPtr[byteOffset] & ((1 << bitsLeft) - 1)) << bitsToReserve); + + // Read the rest of the bits we reserved room for in the destination byte from the next source byte + *outBuf |= (srcPtr[byteOffset + 1] >> (8 - bitsToReserve)); + + streamBitPos += bitsToWriteToSrc; + bitsToRead -= bitsToWriteToSrc; + + if(bitsToRead == 0) { + break; + } + + outBuf++; + } + } + + if(peek) { + streamBitPos = prevStreamBitPos; + } +} + +void BitStream::writeBytes(const uint8_t* data, size_t numBytes) { + if(numBytes == 0) { + return; + } + + // If the stream position is not aligned on a byte boundary, need to do bit writing + if((streamBitPos & 0x7) != 0) { + writeBits(data, numBytes * 8); + return; + } + + // Reserve space for the number of bytes we're going to write + size_t remainingBytes = getRemainingBytes(); + if(remainingBytes < numBytes) { + buf.resize(buf.size() + numBytes - remainingBytes); + } + + memcpy(getHeadBytePtr(), data, numBytes); + + streamBitPos += numBytes * 8; +} + +void BitStream::writeBit(bool value) { + size_t remainingBits = getRemainingBits(); + if(remainingBits < 1) { + buf.resize(buf.size() + 1); + } + + if(value) { + *getHeadBytePtr() |= (0x80 >> (streamBitPos & 0x7)); + } + else { + *getHeadBytePtr() &= ~(0x80 >> (streamBitPos & 0x7)); + } + + streamBitPos++; +} + +void BitStream::writeBits(const uint8_t* data, size_t numBits) { + if(numBits == 0) { + return; + } + + // If the stream position is aligned on a byte boundary and we are writing a quantity of bits divisble by 8, we can use faster byte writing + if((streamBitPos & 0x7) == 0 && (numBits & 0x7) == 0) { + writeBytes(data, numBits / 8); + return; + } + + // Reserve space for the number of bits we're going to write + size_t remainingBits = getRemainingBits(); + if(remainingBits < numBits) { + buf.resize(BITS_TO_BYTES(buf.size() * 8 + numBits - remainingBits)); + } + + unsigned char* dstPtr = buf.data(); + + size_t bitsToWrite = numBits; + + while(true) { + size_t byteOffset = streamBitPos / 8; + size_t bitOffset = (streamBitPos & 0x7); + size_t bitsLeft = 8 - bitOffset; + + if(bitsLeft >= bitsToWrite) { + // We have enough room in the current byte to fit all of the rest of the bits, so write them all from the current source byte + // Shift the remaining bits left to close the gap and be flush with the end of the stream + size_t bitGap = (bitsLeft - bitsToWrite); + dstPtr[byteOffset] |= (*data << bitGap); + streamBitPos += bitsToWrite; + break; + } + else { + // We don't have enough room for all of the remaining bits, so write as much as we need to from the current source byte + // Shift the current byte right to un-overlap the bits and be flush with the end of the stream + size_t bitsToWriteFromSrc = std::min(bitsToWrite, (size_t)8); + size_t bitsOverlapped = bitsToWriteFromSrc - bitsLeft; + + dstPtr[byteOffset] |= (*data >> bitsOverlapped); + + // Now write the rest of the bits remaining in the current source byte to the next destination byte + dstPtr[byteOffset + 1] |= (*data << (8 - bitsOverlapped)); + + streamBitPos += bitsToWriteFromSrc; + bitsToWrite -= bitsToWriteFromSrc; + + if(bitsToWrite == 0) { + break; + } + + data++; + } + } +} + +uint16_t BitStream::readStringLength() { + uint16_t strLen = 0; + readBits((uint8_t*)&strLen, 8); + if((strLen & 0x80) != 0) { + strLen &= 0x7F; + } + else { + // NOTE: Swapped byte ordering + strLen = (strLen << 8); + readBits((uint8_t*)&strLen, 8); + } + + return strLen; +} + +void BitStream::read(std::string& str) { + uint16_t strLen = readStringLength(); + + alignPos(); + + str.resize(strLen); + readBytes((uint8_t*)str.data(), strLen); +} + +void BitStream::read(std::wstring& str) { + uint16_t strLen = readStringLength(); + + alignPos(); + + str.resize(strLen); + readBytes((uint8_t*)str.data(), strLen * 2); +} + +void BitStream::readQuantitizedFloat(float& data, const size_t numBits, const float max, const float min) { + int epsilon = 100; + long outBuf = 0L; + readBits((uint8_t*)&outBuf, numBits); + float range = max - min; + if(compareFloats(range, 0.0f, epsilon) < 0) { + throw std::invalid_argument("quantitized float range - min value is greater than max value"); + } + else if(compareFloats(range, 0.0f, epsilon) == 0 || outBuf == 0L) { + data = min; + } + else { + long fieldMax = (1 << numBits) - 1; + data = outBuf * range / fieldMax; + if(compareFloats(data, 0.0f, epsilon) < 0) { + data = 0.0f; + } + else if(compareFloats(data, range, epsilon) > 0) { + data = range; + } + data += min; + } +} + +void BitStream::writeStringLength(uint16_t length) { + if(length < 128) { + uint8_t strLenWithFlag = 0x80 | length; + writeBytes((uint8_t*)&strLenWithFlag, 1); + } + else { + // NOTE: Swapped byte ordering + writeBytes(((uint8_t*)&length) + 1, 1); + writeBytes((uint8_t*)&length, 1); + } +} + +void BitStream::write(const std::string& str) { + const uint16_t strLen = (uint16_t)str.length(); + writeStringLength(strLen); + + alignPos(); + + writeBytes((uint8_t*)str.data(), strLen); +} + +void BitStream::write(const std::wstring& str) { + const uint16_t strLen = (uint16_t)str.length(); + writeStringLength(strLen); + + alignPos(); + + writeBytes((uint8_t*)str.data(), strLen * 2); +} + +void BitStream::writeQuantitizedFloat(const float& data, const size_t numBits, const float max, const float min) { + int epsilon = 100; + float range = max - min; + long inBuf; + if(compareFloats(range, 0.0f, epsilon) < 0) { + throw std::invalid_argument("quantitized float range - min value is greater than max value"); + } + else if(compareFloats(range, 0.0f, epsilon) == 0 || compareFloats(data, min, epsilon) <= 0) { + inBuf = 0L; + } + else { + long fieldMax = (1 << numBits) - 1; + inBuf = static_cast((data - min) * fieldMax / range); + if(inBuf < 0L) { + inBuf = 0L; + } + else if(inBuf > fieldMax) { + inBuf = fieldMax; + } + } + writeBits((uint8_t*)&inBuf, numBits); +} diff --git a/src/common/bitstream.h b/src/common/bitstream.h index 65c8630..e3cfc40 100644 --- a/src/common/bitstream.h +++ b/src/common/bitstream.h @@ -1,11 +1,7 @@ #pragma once -#include -#include #include -#define BITS_TO_BYTES(bits) (((bits) + 7) / 8) - /** * A stream serializer that reads/writes to an external buffer. * @@ -23,331 +19,97 @@ class BitStream { READ_TOO_MUCH }; - BitStream(std::vector& exisitingBuf) : - buf(exisitingBuf), - streamBitPos(0), - lastError(Error::NONE) { - - } + BitStream(std::vector&); /** * @return The total number of bits in the buffer */ - size_t getSizeBits() const { - return buf.size() * 8; - } + size_t getSizeBits() const; /** * @return The number of unread bits after the stream pos */ - size_t getRemainingBits() const { - size_t sizeBits = getSizeBits(); - if (streamBitPos >= sizeBits) { - return 0; - } else { - return sizeBits - streamBitPos; - } - } + size_t getRemainingBits() const; /** - * @return The number of COMPLETELY unread bytes after the stream pos - */ - size_t getRemainingBytes() const { - size_t usedBytes = BITS_TO_BYTES(streamBitPos); - if (usedBytes >= buf.size()) { - return 0; - } else { - return buf.size() - usedBytes; - } - } + * @return The number of COMPLETELY unread bytes after the stream pos + */ + size_t getRemainingBytes() const; /** * @return A pointer to the byte holding the stream pos */ - uint8_t* getHeadBytePtr() const { - return buf.data() + (streamBitPos / 8); - } + uint8_t* getHeadBytePtr() const; /** * @return An iterator to the byte holding the stream pos */ - std::vector::iterator getHeadIterator() const { - return buf.begin() + (streamBitPos / 8); - } + std::vector::iterator getHeadIterator() const; /** * @return A pointer to the byte holding the given bit pos */ - uint8_t* getPosBytePtr(size_t bitPos) const { - return buf.data() + (bitPos / 8); - } + uint8_t* getPosBytePtr(size_t) const; /** * @return The current stream pos */ - size_t getPos() const { - return streamBitPos; - } + size_t getPos() const; /** * Sets the current stream pos. */ - void setPos(size_t pos) { - if (pos > buf.size() * 8) { - lastError = Error::INVALID_STREAM_POS; - return; - } - - streamBitPos = pos; - } + void setPos(size_t); /** * Moves the stream pos by some delta. */ - void deltaPos(int32_t delta) { - // TODO: Signedness is funky here. Figure out a better way. - int32_t newPos = (int32_t)streamBitPos + delta; - - if (newPos < 0 || newPos > buf.size() * 8) { - lastError = Error::INVALID_STREAM_POS; - return; - } - - streamBitPos = newPos; - } + void deltaPos(int32_t); /** * Aligns the stream pos to the next highest byte boundary if necessary. */ - void alignPos() { - size_t bitsIn = (streamBitPos % 8); - if (bitsIn != 0) { - deltaPos(8 - bitsIn); - } - } + void alignPos(); /** * @return The error type if the stream has encountered a serialization error. */ - Error getLastError() { - return lastError; - } + Error getLastError(); /** * Reads a number of bytes. */ - void readBytes(uint8_t* outBuf, size_t numBytes, bool peek = false) { - if (numBytes == 0) { - return; - } - - // If the stream position is not aligned on a byte boundary, need to do bit reading - if ((streamBitPos & 0x7) != 0) { - readBits(outBuf, numBytes * 8, peek); - return; - } - - size_t remainingBytes = getRemainingBytes(); - if (remainingBytes < numBytes) { - lastError = Error::READ_TOO_MUCH; - return; - } - - memcpy(outBuf, getHeadBytePtr(), numBytes); - - if (!peek) { - streamBitPos += numBytes * 8; - } - } + void readBytes(uint8_t*, size_t, bool = false); /** * @return The boolean value of the next bit in the stream */ - bool readBit(bool peek = false) { - size_t remainingBits = getRemainingBits(); - if (remainingBits < 1) { - lastError = Error::READ_TOO_MUCH; - return false; - } - - bool result = (*getHeadBytePtr() & (0x80 >> (streamBitPos & 0x7))) != 0; - - streamBitPos++; - - return result; - } + bool readBit(bool = false); /** * Reads a number of bits. * NOTE: WILL clobber any existing data in the bytes that get written to */ - void readBits(uint8_t* outBuf, size_t numBits, bool peek = false) { - if (numBits == 0) { - return; - } - - // If the stream position is aligned on a byte boundary and we are reading a quantity of bits divisble by 8, we can use faster byte reading - if ((streamBitPos & 0x7) == 0 && (numBits & 0x7) == 0) { - readBytes(outBuf, numBits / 8, peek); - return; - } - - size_t remainingBits = getRemainingBits(); - if (remainingBits < numBits) { - lastError = Error::READ_TOO_MUCH; - return; - } - - size_t prevStreamBitPos = streamBitPos; - - unsigned char* srcPtr = buf.data(); - - size_t bitsToRead = numBits; - - while (true) { - size_t byteOffset = streamBitPos / 8; - size_t bitOffset = (streamBitPos & 0x7); - size_t bitsLeft = 8 - bitOffset; - - if (bitsLeft >= bitsToRead) { - // We have enough bits remaining in the current source byte to finish the read, so read them all into the current destination byte - // Shift the remaining bits right to be flush with the start of the current destination byte, and mask out the bits to the left - size_t bitGap = (bitsLeft - bitsToRead); - *outBuf = ((srcPtr[byteOffset] >> bitGap) & ((1 << bitsToRead) - 1)); - streamBitPos += bitsToRead; - break; - } else { - // We don't have enough bits remaining in the current byte to finish the read, so read as much as we need into the current destination byte - // Shift the current source byte left to reserve a number of bits on the right to read from the next byte, and mask out the bits to the left - size_t bitsToWriteToSrc = std::min(bitsToRead, (size_t)8); - size_t bitsToReserve = bitsToWriteToSrc - bitsLeft; - - *outBuf = ((srcPtr[byteOffset] & ((1 << bitsLeft) - 1)) << bitsToReserve); - - // Read the rest of the bits we reserved room for in the destination byte from the next source byte - *outBuf |= (srcPtr[byteOffset + 1] >> (8 - bitsToReserve)); - - streamBitPos += bitsToWriteToSrc; - bitsToRead -= bitsToWriteToSrc; - - if (bitsToRead == 0) { - break; - } - - outBuf++; - } - } - - if (peek) { - streamBitPos = prevStreamBitPos; - } - } + void readBits(uint8_t*, size_t, bool = false); /** * Writes a number of bytes. */ - void writeBytes(const uint8_t* data, size_t numBytes) { - if (numBytes == 0) { - return; - } - - // If the stream position is not aligned on a byte boundary, need to do bit writing - if ((streamBitPos & 0x7) != 0) { - writeBits(data, numBytes * 8); - return; - } - - // Reserve space for the number of bytes we're going to write - size_t remainingBytes = getRemainingBytes(); - if (remainingBytes < numBytes) { - buf.resize(buf.size() + numBytes - remainingBytes); - } - - memcpy(getHeadBytePtr(), data, numBytes); - - streamBitPos += numBytes * 8; - } + void writeBytes(const uint8_t*, size_t); /** * Writes a boolean value as a single bit. */ - void writeBit(bool value) { - size_t remainingBits = getRemainingBits(); - if (remainingBits < 1) { - buf.resize(buf.size() + 1); - } - - if (value) { - *getHeadBytePtr() |= (0x80 >> (streamBitPos & 0x7)); - } else { - *getHeadBytePtr() &= ~(0x80 >> (streamBitPos & 0x7)); - } - - streamBitPos++; - } + void writeBit(bool); /** * Writes a number of bits. */ - void writeBits(const uint8_t* data, size_t numBits) { - if (numBits == 0) { - return; - } - - // If the stream position is aligned on a byte boundary and we are writing a quantity of bits divisble by 8, we can use faster byte writing - if ((streamBitPos & 0x7) == 0 && (numBits & 0x7) == 0) { - writeBytes(data, numBits / 8); - return; - } - - // Reserve space for the number of bits we're going to write - size_t remainingBits = getRemainingBits(); - if (remainingBits < numBits) { - buf.resize(BITS_TO_BYTES(buf.size() * 8 + numBits - remainingBits)); - } - - unsigned char* dstPtr = buf.data(); - - size_t bitsToWrite = numBits; - - while (true) { - size_t byteOffset = streamBitPos / 8; - size_t bitOffset = (streamBitPos & 0x7); - size_t bitsLeft = 8 - bitOffset; - - if (bitsLeft >= bitsToWrite) { - // We have enough room in the current byte to fit all of the rest of the bits, so write them all from the current source byte - // Shift the remaining bits left to close the gap and be flush with the end of the stream - size_t bitGap = (bitsLeft - bitsToWrite); - dstPtr[byteOffset] |= (*data << bitGap); - streamBitPos += bitsToWrite; - break; - } else { - // We don't have enough room for all of the remaining bits, so write as much as we need to from the current source byte - // Shift the current byte right to un-overlap the bits and be flush with the end of the stream - size_t bitsToWriteFromSrc = std::min(bitsToWrite, (size_t)8); - size_t bitsOverlapped = bitsToWriteFromSrc - bitsLeft; - - dstPtr[byteOffset] |= (*data >> bitsOverlapped); - - // Now write the rest of the bits remaining in the current source byte to the next destination byte - dstPtr[byteOffset + 1] |= (*data << (8 - bitsOverlapped)); - - streamBitPos += bitsToWriteFromSrc; - bitsToWrite -= bitsToWriteFromSrc; - - if (bitsToWrite == 0) { - break; - } - - data++; - } - } - } + void writeBits(const uint8_t*, size_t); /** - * Templated read functions. - */ + * Templated read functions. + */ template void read(std::array& outBuf, bool peek = false) { static_assert(!std::is_pointer::value, "Object type must not be a pointer."); @@ -362,60 +124,21 @@ class BitStream { readBytes((uint8_t*)&object, sizeof(T), peek); } - uint16_t readStringLength() { - uint16_t strLen = 0; - readBits((uint8_t*)&strLen, 8); - if ((strLen & 0x80) != 0) { - strLen &= 0x7F; - } else { - // NOTE: Swapped byte ordering - strLen = (strLen << 8); - readBits((uint8_t*)&strLen, 8); - } - - return strLen; - } - - void read(std::string& str) { - uint16_t strLen = readStringLength(); - - alignPos(); - - str.resize(strLen); - readBytes((uint8_t*)str.data(), strLen); - } - - void read(std::wstring& str) { - uint16_t strLen = readStringLength(); + uint16_t readStringLength(); - alignPos(); + void read(std::string&); - str.resize(strLen); - readBytes((uint8_t*)str.data(), strLen * 2); - } + void read(std::wstring&); - void readQuantitizedFloat(float& data, const size_t numBits, const float max, const float min = 0.0f) { - long outBuf = 0L; - readBits((uint8_t*)&outBuf, numBits); - float range = max - min; - if(range < -0.0f) { - throw std::invalid_argument("quantitized float range - min value must never exceed max"); - } - else if(range == 0.0f || outBuf == 0L) { - data = min; - } - else { - long fieldMax = (1 << numBits) - 1; - data = outBuf * range / fieldMax; - if(data < 0.0f) { - data = 0.0f; - } - else if(data > range) { - data = range; - } - data += min; - } - } + /** + Read from buffer a float value as an integer with a 0-max range as a ratio to the float's min-max range. + @param data the float value to write to + @param numBits the number of bits to be occupied in the buffer, the max converted integer value + @param max the maximum float value + @param min the minimum float value, defaulting to 0.0f + @throw invalid_argument if min is greater than max + */ + void readQuantitizedFloat(float&, const size_t, const float, const float = 0.0f); /** * Templated write functions. @@ -441,59 +164,21 @@ class BitStream { writeBytes((const uint8_t*)&object, sizeof(T)); } - void writeStringLength(uint16_t length) { - if (length < 128) { - uint8_t strLenWithFlag = 0x80 | length; - writeBytes((uint8_t*)&strLenWithFlag, 1); - } else { - // NOTE: Swapped byte ordering - writeBytes(((uint8_t*)&length) + 1, 1); - writeBytes((uint8_t*)&length, 1); - } - } - - void write(const std::string& str) { - const uint16_t strLen = (uint16_t)str.length(); - writeStringLength(strLen); + void writeStringLength(uint16_t); - alignPos(); + void write(const std::string&); - writeBytes((uint8_t*)str.data(), strLen); - } + void write(const std::wstring&); - void write(const std::wstring& str) { - const uint16_t strLen = (uint16_t)str.length(); - writeStringLength(strLen); - - alignPos(); - - writeBytes((uint8_t*)str.data(), strLen * 2); - } - - void writeQuantitizedFloat(const float& data, const size_t numBits, const float max, const float min = 0.0f) { - if(data < min) { - throw std::invalid_argument("quantitized float range - data is less than min value"); - } - float range = max - min; - long inBuf; - if(range < 0.0f) { - throw std::invalid_argument("quantitized float range - min value must never exceed max"); - } - else if(range == 0.0f || data == 0.0f) { - inBuf = 0L; - } - else { - long fieldMax = (1 << numBits) - 1; - inBuf = static_cast((data - min) * fieldMax / range); - if(inBuf < 0L) { - inBuf = 0L; - } - else if(inBuf > fieldMax) { - inBuf = fieldMax; - } - } - writeBits((uint8_t*)&inBuf, numBits); - } + /** + Write to buffer a float value within a min-max range as an integer value within a 0-max range. + @param data the float to write to buffer + @param numBits the number of bits to be occupied in the buffer, the max converted integer value + @param max the maximum float value + @param min the minimum float value, defaulting to 0.0f + @throw invalid_argument if min is greater than max + */ + void writeQuantitizedFloat(const float&, const size_t, const float, const float = 0.0f); std::vector& buf; diff --git a/src/common/bitstream_test.cpp b/src/common/bitstream_test.cpp index 2ddd6ed..c790b62 100644 --- a/src/common/bitstream_test.cpp +++ b/src/common/bitstream_test.cpp @@ -1,6 +1,8 @@ +#include #include "bitstream.h" #include "log.h" #include "test.h" +#include "util.h" class PlanetsideBitstream { public: @@ -120,41 +122,41 @@ void testBitstreamReadBits() { } void testBitstreamReadQuantitizedFloat() { - static std::vector expectedBuf = std::vector({ - 0x6C, 0x2D, 0x76, 0x55, 0x35, 0xCA, 0x16 //6C2D7 65535 CA16 - }); - BitStream bitstream(expectedBuf); - - float x; - float y; - float z; - bitstream.readQuantitizedFloat(x, 20, 8192.0f); - bitstream.readQuantitizedFloat(y, 20, 8192.0f); - bitstream.readQuantitizedFloat(z, 16, 1024.0f); - assertEqual( compareFloatValues(x, 3674.85f, 100), 0); - assertEqual( compareFloatValues(y, 2726.79f, 100), 0); - assertEqual( compareFloatValues(z, 91.1576f, 100), 0); + static std::vector expectedBuf = std::vector({ + 0x6C, 0x2D, 0x76, 0x55, 0x35, 0xCA, 0x16 //6C2D7 65535 CA16 + }); + BitStream bitstream(expectedBuf); + + float x; + float y; + float z; + bitstream.readQuantitizedFloat(x, 20, 8192.0f); + bitstream.readQuantitizedFloat(y, 20, 8192.0f); + bitstream.readQuantitizedFloat(z, 16, 1024.0f); + assertEqual( compareFloats(x, 3674.85f, 100), 0); + assertEqual( compareFloats(y, 2726.79f, 100), 0); + assertEqual( compareFloats(z, 91.1576f, 100), 0); } void testBitstreamWriteQuantitizedFloat() { - static std::vector expectedBuf = std::vector({ - 0x6C, 0x2D, 0x76, 0x55, 0x35, 0xCA, 0x16 //6C2D7 65535 CA16 - }); - BitStream expected_bitstream(expectedBuf); - float x; - float y; - float z; - expected_bitstream.readQuantitizedFloat(x, 20, 8192.0f); - expected_bitstream.readQuantitizedFloat(y, 20, 8192.0f); - expected_bitstream.readQuantitizedFloat(z, 16, 1024.0f); - - std::vector bitstreamBuf; - BitStream test_bitstream(bitstreamBuf); - test_bitstream.writeQuantitizedFloat(x, 20, 8192.0f); - test_bitstream.writeQuantitizedFloat(y, 20, 8192.0f); - test_bitstream.writeQuantitizedFloat(z, 16, 1024.0f); - - assertBuffersEqual(bitstreamBuf, expectedBuf); + static std::vector expectedBuf = std::vector({ + 0x6C, 0x2D, 0x76, 0x55, 0x35, 0xCA, 0x16 //6C2D7 65535 CA16 + }); + BitStream expected_bitstream(expectedBuf); + float x; + float y; + float z; + expected_bitstream.readQuantitizedFloat(x, 20, 8192.0f); + expected_bitstream.readQuantitizedFloat(y, 20, 8192.0f); + expected_bitstream.readQuantitizedFloat(z, 16, 1024.0f); + + std::vector bitstreamBuf; + BitStream test_bitstream(bitstreamBuf); + test_bitstream.writeQuantitizedFloat(x, 20, 8192.0f); + test_bitstream.writeQuantitizedFloat(y, 20, 8192.0f); + test_bitstream.writeQuantitizedFloat(z, 16, 1024.0f); + + assertBuffersEqual(bitstreamBuf, expectedBuf); } void testBitstream() { @@ -167,6 +169,6 @@ void testBitstream() { // TODO: Test primitive write/read // TODO: Test string write/read - testBitstreamReadQuantitizedFloat(); - testBitstreamWriteQuantitizedFloat(); + testBitstreamReadQuantitizedFloat(); + testBitstreamWriteQuantitizedFloat(); } diff --git a/src/common/packet/pkt_test_control.cpp b/src/common/packet/pkt_test_control.cpp index 6ed136f..033509e 100644 --- a/src/common/packet/pkt_test_control.cpp +++ b/src/common/packet/pkt_test_control.cpp @@ -1,3 +1,4 @@ +#include #include #include "pkt_all.h" #include "pkt_test.h" diff --git a/src/common/packet/pkt_test_crypto.cpp b/src/common/packet/pkt_test_crypto.cpp index 1a23400..4766447 100644 --- a/src/common/packet/pkt_test_crypto.cpp +++ b/src/common/packet/pkt_test_crypto.cpp @@ -1,3 +1,4 @@ +#include #include #include "pkt_all.h" #include "pkt_test.h" diff --git a/src/common/packet/pkt_test_game.cpp b/src/common/packet/pkt_test_game.cpp index 8e7422d..3a8626a 100644 --- a/src/common/packet/pkt_test_game.cpp +++ b/src/common/packet/pkt_test_game.cpp @@ -1,3 +1,4 @@ +#include #include #include "pkt_all.h" #include "pkt_test.h" diff --git a/src/common/test.cpp b/src/common/test.cpp index 467d6f2..28227ba 100644 --- a/src/common/test.cpp +++ b/src/common/test.cpp @@ -1,3 +1,4 @@ +#include #include "test.h" void printAssertMessage() { diff --git a/src/common/test.h b/src/common/test.h index aee0873..8781879 100644 --- a/src/common/test.h +++ b/src/common/test.h @@ -1,7 +1,5 @@ #pragma once -#include "util.h" - #define assertBuffersEqual(actual, expected, ...) do {\ if (actual != expected) {\ std::cout << "Assert failed! File \"" << __FILE__ << "\" line " << __LINE__ << std::endl;\ diff --git a/src/common/util.cpp b/src/common/util.cpp index 5dc4a21..9671aa7 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -1,9 +1,5 @@ -#include #include -#include #include -#include -#include "bitstream.h" #include "util.h" uint32_t randomUnsignedInt() { @@ -81,42 +77,34 @@ std::vector hexToBytes(std::string hexStr) { return bytes; } -/** - Compare two float values for relative equality or the direction of inequality. - @param a the first float - @param b the second float - @param epsilon the margin of difference between parameter a and parameter b - @throw invalid_argument if error is greater than 4 * 1024 * 1024 - @return a value representing the equality or inequality of the two input numbers -*/ -const int compareFloatValues(const float a, const float b, const size_t epsilon) { - /* - A simple decimal comparison might involve subtraction, fabs, and the result being less than a smaller decimal. - This method converts the IEEE float values into lexicographically-ordered two's-complement integers. - If (float)a > (float)b then (int)a > (int)b; but, precision concerns are lessened. - The error becomes a count of "the maximum number of lexico-numbers allowed between lexico-a and lexico-b." - To avoid encountering a NaN value, error can not risk being too big, however. - */ - if(epsilon >= 4194304) { - throw std::invalid_argument("float comparison - error margin too big"); - } - - int aint = *(int*)&a; - if(aint < 0) { - aint = 0x80000000 - aint; //conform negative scale to lexicographic order - } - int bint = *(int*)&b; - if(bint < 0) { - bint = 0x80000000 - bint; //conform negative scale to lexicographic order - } - //borrows from Java's Comparator compare(T o1, T o2) model for return values - if(std::abs(aint - bint) <= epsilon) { - return 0; - } - else if(aint + epsilon < bint) { - return 1; - } - else { - return -1; - } +const int compareFloats(const float a, const float b, const size_t epsilon) { + /* + A simple decimal comparison might involve subtraction, fabs, and the result being less than a smaller decimal. + This method converts the IEEE float values into lexicographically-ordered two's-complement integers. + If (float)a > (float)b then (int)a > (int)b; but, precision concerns are lessened. + The error becomes a count of "the maximum number of lexico-numbers allowed between equal lexico-a and lexico-b." + To avoid encountering a NaN value, error can not risk being too big, however. + */ + if(epsilon >= 4194304) { + throw std::invalid_argument("float comparison - error margin too big"); + } + + int aint = *(int*)&a; + if(aint < 0) { + aint = 0x80000000 - aint; //conform negative scale to lexicographic order + } + int bint = *(int*)&b; + if(bint < 0) { + bint = 0x80000000 - bint; //conform negative scale to lexicographic order + } + //borrows from Java's Comparator compare(T o1, T o2) for return values + if(std::abs(aint - bint) <= epsilon) { + return 0; + } + else if(aint + epsilon < bint) { + return -1; + } + else { + return 1; + } } diff --git a/src/common/util.h b/src/common/util.h index d8fa27e..4420c02 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -1,9 +1,5 @@ #pragma once -#include -#include -#include "bitstream.h" - uint32_t randomUnsignedInt(); uint8_t randomUnsignedChar(); std::size_t getTimeSeconds(); @@ -14,4 +10,12 @@ void utilSleep(size_t ms); std::vector hexToBytes(std::string hexStr); -const int compareFloatValues(const float a, const float b, const size_t error); +/** +Compare two float values for relative equality or the direction of inequality. +@param a the first float +@param b the second float +@param epsilon the margin of difference between parameter a and parameter b +@throw invalid_argument if epsilon is greater than 4 * 1024 * 1024 +@return a negative integer, zero, or a positive integer as the first argument is <, ==, or > the second +*/ +const int compareFloats(const float a, const float b, const size_t error); From 0ce574c2e99355628070775f842945252d736283 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 23 Feb 2017 11:21:09 -0500 Subject: [PATCH 5/6] added two more tests for quantitized float limits; solved a casting problem with compareFloats; moved up test in readQuantitizedFloat --- src/common/bitstream.cpp | 6 ++--- src/common/bitstream_test.cpp | 46 +++++++++++++++++++++++++++++++++++ src/common/util.cpp | 9 ++++--- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/common/bitstream.cpp b/src/common/bitstream.cpp index b5d7a71..007d8a3 100644 --- a/src/common/bitstream.cpp +++ b/src/common/bitstream.cpp @@ -316,13 +316,13 @@ void BitStream::read(std::wstring& str) { void BitStream::readQuantitizedFloat(float& data, const size_t numBits, const float max, const float min) { int epsilon = 100; - long outBuf = 0L; - readBits((uint8_t*)&outBuf, numBits); float range = max - min; if(compareFloats(range, 0.0f, epsilon) < 0) { throw std::invalid_argument("quantitized float range - min value is greater than max value"); } - else if(compareFloats(range, 0.0f, epsilon) == 0 || outBuf == 0L) { + long outBuf = 0L; + readBits((uint8_t*)&outBuf, numBits); + if(compareFloats(range, 0.0f, epsilon) == 0 || outBuf == 0L) { data = min; } else { diff --git a/src/common/bitstream_test.cpp b/src/common/bitstream_test.cpp index c790b62..6229c01 100644 --- a/src/common/bitstream_test.cpp +++ b/src/common/bitstream_test.cpp @@ -159,6 +159,50 @@ void testBitstreamWriteQuantitizedFloat() { assertBuffersEqual(bitstreamBuf, expectedBuf); } +void testBitstreamReadQuantitizedFloatLimits() { + static std::vector expectedBuf = std::vector({ + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF //FFFF 0000 FFFF + }); + BitStream bitstream(expectedBuf); + + float a; + float b; + float c; + bitstream.readQuantitizedFloat(a, 16, 256.0f, -256.0f); + bitstream.readQuantitizedFloat(b, 16, 256.0f, -256.0f); + bitstream.readQuantitizedFloat(c, 16, 256.0f, -256.0f); + assertEqual(compareFloats(a, 256.0f, 100), 0); + assertEqual(compareFloats(b, -256.0f, 100), 0); + assertEqual(compareFloats(c, 256.0f, 100), 0); +} + +void testBitstreamWriteQuantitizedFloatLimits() { + static std::vector expectedBuf = std::vector({ + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF //FFFF 0000 FFFF + }); + BitStream test_bitstream(expectedBuf); + + float a = 260.5f; //too high + float b = -260.5f; //too low + std::vector bitstreamBuf; + BitStream bitstream(bitstreamBuf); + bitstream.writeQuantitizedFloat(a, 16, 256.0f, -256.0f); + bitstream.writeQuantitizedFloat(b, 16, 256.0f, -256.0f); + bitstream.writeQuantitizedFloat(a, 16, 256.0f, -256.0f); + assertBuffersEqual(bitstreamBuf, expectedBuf); + + bitstream.setPos(0); //rewind the stream + float ax; + float bx; + float cx; + bitstream.readQuantitizedFloat(ax, 16, 256.0f, -256.0f); + bitstream.readQuantitizedFloat(bx, 16, 256.0f, -256.0f); + bitstream.readQuantitizedFloat(cx, 16, 256.0f, -256.0f); + assertEqual(compareFloats(ax, 256.0f, 100), 0); + assertEqual(compareFloats(bx, -256.0f, 100), 0); + assertEqual(compareFloats(cx, 256.0f, 100), 0); +} + void testBitstream() { testBitstreamWriteBitsBasic(); testBitstreamWriteBits(); @@ -171,4 +215,6 @@ void testBitstream() { testBitstreamReadQuantitizedFloat(); testBitstreamWriteQuantitizedFloat(); + testBitstreamReadQuantitizedFloatLimits(); + testBitstreamWriteQuantitizedFloatLimits(); } diff --git a/src/common/util.cpp b/src/common/util.cpp index 9671aa7..cada2ae 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -89,19 +89,20 @@ const int compareFloats(const float a, const float b, const size_t epsilon) { throw std::invalid_argument("float comparison - error margin too big"); } - int aint = *(int*)&a; + int epsilon2 = *(int*)ε + int aint = *(int*)&a; if(aint < 0) { aint = 0x80000000 - aint; //conform negative scale to lexicographic order } - int bint = *(int*)&b; + int bint = *(int*)&b; if(bint < 0) { bint = 0x80000000 - bint; //conform negative scale to lexicographic order } //borrows from Java's Comparator compare(T o1, T o2) for return values - if(std::abs(aint - bint) <= epsilon) { + if(std::abs(aint - bint) <= epsilon2) { return 0; } - else if(aint + epsilon < bint) { + else if(aint + epsilon2 < bint) { return -1; } else { From 780fd22d2b5ddaac7b30eedc546c273e5be1a4fd Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 24 Feb 2017 15:13:55 -0500 Subject: [PATCH 6/6] requested changes for PR; ditched floats for doubles; simplified support function for comparison --- src/common/bitstream.cpp | 138 +++++++++++++++---------------- src/common/bitstream.h | 44 +++++----- src/common/bitstream_test.cpp | 149 ++++++++++++++++++++-------------- src/common/util.cpp | 41 ++++------ src/common/util.h | 10 +-- 5 files changed, 193 insertions(+), 189 deletions(-) diff --git a/src/common/bitstream.cpp b/src/common/bitstream.cpp index 007d8a3..06ec438 100644 --- a/src/common/bitstream.cpp +++ b/src/common/bitstream.cpp @@ -18,20 +18,18 @@ size_t BitStream::getSizeBits() const { size_t BitStream::getRemainingBits() const { size_t sizeBits = getSizeBits(); - if(streamBitPos >= sizeBits) { + if (streamBitPos >= sizeBits) { return 0; - } - else { + } else { return sizeBits - streamBitPos; } } size_t BitStream::getRemainingBytes() const { size_t usedBytes = BITS_TO_BYTES(streamBitPos); - if(usedBytes >= buf.size()) { + if (usedBytes >= buf.size()) { return 0; - } - else { + } else { return buf.size() - usedBytes; } } @@ -53,7 +51,7 @@ size_t BitStream::getPos() const { } void BitStream::setPos(size_t pos) { - if(pos > buf.size() * 8) { + if (pos > buf.size() * 8) { lastError = Error::INVALID_STREAM_POS; return; } @@ -65,7 +63,7 @@ void BitStream::deltaPos(int32_t delta) { // TODO: Signedness is funky here. Figure out a better way. int32_t newPos = (int32_t)streamBitPos + delta; - if(newPos < 0 || newPos > buf.size() * 8) { + if (newPos < 0 || newPos > buf.size() * 8) { lastError = Error::INVALID_STREAM_POS; return; } @@ -75,7 +73,7 @@ void BitStream::deltaPos(int32_t delta) { void BitStream::alignPos() { size_t bitsIn = (streamBitPos % 8); - if(bitsIn != 0) { + if (bitsIn != 0) { deltaPos(8 - bitsIn); } } @@ -85,32 +83,32 @@ BitStream::Error BitStream::getLastError() { } void BitStream::readBytes(uint8_t* outBuf, size_t numBytes, bool peek) { - if(numBytes == 0) { + if (numBytes == 0) { return; } // If the stream position is not aligned on a byte boundary, need to do bit reading - if((streamBitPos & 0x7) != 0) { + if ((streamBitPos & 0x7) != 0) { readBits(outBuf, numBytes * 8, peek); return; } size_t remainingBytes = getRemainingBytes(); - if(remainingBytes < numBytes) { + if (remainingBytes < numBytes) { lastError = Error::READ_TOO_MUCH; return; } memcpy(outBuf, getHeadBytePtr(), numBytes); - if(!peek) { + if (!peek) { streamBitPos += numBytes * 8; } } bool BitStream::readBit(bool peek) { size_t remainingBits = getRemainingBits(); - if(remainingBits < 1) { + if (remainingBits < 1) { lastError = Error::READ_TOO_MUCH; return false; } @@ -124,18 +122,18 @@ bool BitStream::readBit(bool peek) { void BitStream::readBits(uint8_t* outBuf, size_t numBits, bool peek) { //NOTE: WILL CLOBBER any existing data in the bytes that get written to - if(numBits == 0) { + if (numBits == 0) { return; } // If the stream position is aligned on a byte boundary and we are reading a quantity of bits divisble by 8, we can use faster byte reading - if((streamBitPos & 0x7) == 0 && (numBits & 0x7) == 0) { + if ((streamBitPos & 0x7) == 0 && (numBits & 0x7) == 0) { readBytes(outBuf, numBits / 8, peek); return; } size_t remainingBits = getRemainingBits(); - if(remainingBits < numBits) { + if (remainingBits < numBits) { lastError = Error::READ_TOO_MUCH; return; } @@ -146,20 +144,19 @@ void BitStream::readBits(uint8_t* outBuf, size_t numBits, bool peek) { size_t bitsToRead = numBits; - while(true) { + while (true) { size_t byteOffset = streamBitPos / 8; size_t bitOffset = (streamBitPos & 0x7); size_t bitsLeft = 8 - bitOffset; - if(bitsLeft >= bitsToRead) { + if (bitsLeft >= bitsToRead) { // We have enough bits remaining in the current source byte to finish the read, so read them all into the current destination byte // Shift the remaining bits right to be flush with the start of the current destination byte, and mask out the bits to the left size_t bitGap = (bitsLeft - bitsToRead); *outBuf = ((srcPtr[byteOffset] >> bitGap) & ((1 << bitsToRead) - 1)); streamBitPos += bitsToRead; break; - } - else { + } else { // We don't have enough bits remaining in the current byte to finish the read, so read as much as we need into the current destination byte // Shift the current source byte left to reserve a number of bits on the right to read from the next byte, and mask out the bits to the left size_t bitsToWriteToSrc = std::min(bitsToRead, (size_t)8); @@ -173,7 +170,7 @@ void BitStream::readBits(uint8_t* outBuf, size_t numBits, bool peek) { streamBitPos += bitsToWriteToSrc; bitsToRead -= bitsToWriteToSrc; - if(bitsToRead == 0) { + if (bitsToRead == 0) { break; } @@ -181,25 +178,25 @@ void BitStream::readBits(uint8_t* outBuf, size_t numBits, bool peek) { } } - if(peek) { + if (peek) { streamBitPos = prevStreamBitPos; } } void BitStream::writeBytes(const uint8_t* data, size_t numBytes) { - if(numBytes == 0) { + if (numBytes == 0) { return; } // If the stream position is not aligned on a byte boundary, need to do bit writing - if((streamBitPos & 0x7) != 0) { + if ((streamBitPos & 0x7) != 0) { writeBits(data, numBytes * 8); return; } // Reserve space for the number of bytes we're going to write size_t remainingBytes = getRemainingBytes(); - if(remainingBytes < numBytes) { + if (remainingBytes < numBytes) { buf.resize(buf.size() + numBytes - remainingBytes); } @@ -210,14 +207,13 @@ void BitStream::writeBytes(const uint8_t* data, size_t numBytes) { void BitStream::writeBit(bool value) { size_t remainingBits = getRemainingBits(); - if(remainingBits < 1) { + if (remainingBits < 1) { buf.resize(buf.size() + 1); } - if(value) { + if (value) { *getHeadBytePtr() |= (0x80 >> (streamBitPos & 0x7)); - } - else { + } else { *getHeadBytePtr() &= ~(0x80 >> (streamBitPos & 0x7)); } @@ -225,19 +221,19 @@ void BitStream::writeBit(bool value) { } void BitStream::writeBits(const uint8_t* data, size_t numBits) { - if(numBits == 0) { + if (numBits == 0) { return; } // If the stream position is aligned on a byte boundary and we are writing a quantity of bits divisble by 8, we can use faster byte writing - if((streamBitPos & 0x7) == 0 && (numBits & 0x7) == 0) { + if ((streamBitPos & 0x7) == 0 && (numBits & 0x7) == 0) { writeBytes(data, numBits / 8); return; } // Reserve space for the number of bits we're going to write size_t remainingBits = getRemainingBits(); - if(remainingBits < numBits) { + if (remainingBits < numBits) { buf.resize(BITS_TO_BYTES(buf.size() * 8 + numBits - remainingBits)); } @@ -245,20 +241,19 @@ void BitStream::writeBits(const uint8_t* data, size_t numBits) { size_t bitsToWrite = numBits; - while(true) { + while (true) { size_t byteOffset = streamBitPos / 8; size_t bitOffset = (streamBitPos & 0x7); size_t bitsLeft = 8 - bitOffset; - if(bitsLeft >= bitsToWrite) { + if (bitsLeft >= bitsToWrite) { // We have enough room in the current byte to fit all of the rest of the bits, so write them all from the current source byte // Shift the remaining bits left to close the gap and be flush with the end of the stream size_t bitGap = (bitsLeft - bitsToWrite); dstPtr[byteOffset] |= (*data << bitGap); streamBitPos += bitsToWrite; break; - } - else { + } else { // We don't have enough room for all of the remaining bits, so write as much as we need to from the current source byte // Shift the current byte right to un-overlap the bits and be flush with the end of the stream size_t bitsToWriteFromSrc = std::min(bitsToWrite, (size_t)8); @@ -272,7 +267,7 @@ void BitStream::writeBits(const uint8_t* data, size_t numBits) { streamBitPos += bitsToWriteFromSrc; bitsToWrite -= bitsToWriteFromSrc; - if(bitsToWrite == 0) { + if (bitsToWrite == 0) { break; } @@ -284,10 +279,9 @@ void BitStream::writeBits(const uint8_t* data, size_t numBits) { uint16_t BitStream::readStringLength() { uint16_t strLen = 0; readBits((uint8_t*)&strLen, 8); - if((strLen & 0x80) != 0) { + if ((strLen & 0x80) != 0) { strLen &= 0x7F; - } - else { + } else { // NOTE: Swapped byte ordering strLen = (strLen << 8); readBits((uint8_t*)&strLen, 8); @@ -314,24 +308,24 @@ void BitStream::read(std::wstring& str) { readBytes((uint8_t*)str.data(), strLen * 2); } -void BitStream::readQuantitizedFloat(float& data, const size_t numBits, const float max, const float min) { - int epsilon = 100; - float range = max - min; - if(compareFloats(range, 0.0f, epsilon) < 0) { - throw std::invalid_argument("quantitized float range - min value is greater than max value"); +void BitStream::readQuantitizedDouble(double& data, size_t numBits, float max, float min, double epsilon) { + double range = static_cast(max - min); + if (range + epsilon < 0.0) { + throw std::invalid_argument("quantitized double range - min value is greater than max value"); + } + else if (numBits == 0) { + throw std::invalid_argument("quantitized double size - no bits, no value"); } - long outBuf = 0L; + uint64_t outBuf = 0L; readBits((uint8_t*)&outBuf, numBits); - if(compareFloats(range, 0.0f, epsilon) == 0 || outBuf == 0L) { + if (range < epsilon || outBuf == 0L) { data = min; - } - else { - long fieldMax = (1 << numBits) - 1; + } else { + uint64_t fieldMax = (1 << numBits) - 1; data = outBuf * range / fieldMax; - if(compareFloats(data, 0.0f, epsilon) < 0) { - data = 0.0f; - } - else if(compareFloats(data, range, epsilon) > 0) { + if (data + epsilon < 0.0) { + data = 0.0; + } else if (data + epsilon > range) { data = range; } data += min; @@ -339,11 +333,10 @@ void BitStream::readQuantitizedFloat(float& data, const size_t numBits, const fl } void BitStream::writeStringLength(uint16_t length) { - if(length < 128) { + if (length < 128) { uint8_t strLenWithFlag = 0x80 | length; writeBytes((uint8_t*)&strLenWithFlag, 1); - } - else { + } else { // NOTE: Swapped byte ordering writeBytes(((uint8_t*)&length) + 1, 1); writeBytes((uint8_t*)&length, 1); @@ -368,23 +361,20 @@ void BitStream::write(const std::wstring& str) { writeBytes((uint8_t*)str.data(), strLen * 2); } -void BitStream::writeQuantitizedFloat(const float& data, const size_t numBits, const float max, const float min) { - int epsilon = 100; - float range = max - min; - long inBuf; - if(compareFloats(range, 0.0f, epsilon) < 0) { - throw std::invalid_argument("quantitized float range - min value is greater than max value"); - } - else if(compareFloats(range, 0.0f, epsilon) == 0 || compareFloats(data, min, epsilon) <= 0) { +void BitStream::writeQuantitizedDouble(const double& data, size_t numBits, float max, float min, double epsilon) { + double range = static_cast(max - min); + uint64_t inBuf; + if (range + epsilon < 0.0) { + throw std::invalid_argument("quantitized double range - min value is greater than max value"); + } else if (numBits == 0) { + throw std::invalid_argument("quantitized double size - no bits, no value"); + } else + if (std::fabs(range) < epsilon || data + epsilon <= min) { inBuf = 0L; - } - else { - long fieldMax = (1 << numBits) - 1; - inBuf = static_cast((data - min) * fieldMax / range); - if(inBuf < 0L) { - inBuf = 0L; - } - else if(inBuf > fieldMax) { + } else { + uint64_t fieldMax = (1 << numBits) - 1; + inBuf = static_cast(((data - min) * fieldMax / range)); + if (inBuf > fieldMax) { inBuf = fieldMax; } } diff --git a/src/common/bitstream.h b/src/common/bitstream.h index e3cfc40..116e640 100644 --- a/src/common/bitstream.h +++ b/src/common/bitstream.h @@ -19,7 +19,7 @@ class BitStream { READ_TOO_MUCH }; - BitStream(std::vector&); + BitStream(std::vector& exisitingBuf); /** * @return The total number of bits in the buffer @@ -59,12 +59,12 @@ class BitStream { /** * Sets the current stream pos. */ - void setPos(size_t); + void setPos(size_t pos); /** * Moves the stream pos by some delta. */ - void deltaPos(int32_t); + void deltaPos(int32_t delta); /** * Aligns the stream pos to the next highest byte boundary if necessary. @@ -79,33 +79,33 @@ class BitStream { /** * Reads a number of bytes. */ - void readBytes(uint8_t*, size_t, bool = false); + void readBytes(uint8_t* outBuf, size_t numBytes, bool peek = false); /** * @return The boolean value of the next bit in the stream */ - bool readBit(bool = false); + bool readBit(bool peek = false); /** * Reads a number of bits. * NOTE: WILL clobber any existing data in the bytes that get written to */ - void readBits(uint8_t*, size_t, bool = false); + void readBits(uint8_t* outBuf, size_t numBits, bool peek = false); /** * Writes a number of bytes. */ - void writeBytes(const uint8_t*, size_t); + void writeBytes(const uint8_t* data, size_t numBytes); /** * Writes a boolean value as a single bit. */ - void writeBit(bool); + void writeBit(bool value); /** * Writes a number of bits. */ - void writeBits(const uint8_t*, size_t); + void writeBits(const uint8_t* data, size_t numBits); /** * Templated read functions. @@ -126,19 +126,21 @@ class BitStream { uint16_t readStringLength(); - void read(std::string&); + void read(std::string& str); - void read(std::wstring&); + void read(std::wstring& str); /** - Read from buffer a float value as an integer with a 0-max range as a ratio to the float's min-max range. - @param data the float value to write to + Read from buffer a double value within a min-max float range as a value in a 0-[max] integer range. + @param data the double value to write to @param numBits the number of bits to be occupied in the buffer, the max converted integer value @param max the maximum float value @param min the minimum float value, defaulting to 0.0f + @param epsilon a comparable tolerable difference between decimal numbers, defaulting to 0.001 @throw invalid_argument if min is greater than max + @throw invalid_argument if the number of bits is zero */ - void readQuantitizedFloat(float&, const size_t, const float, const float = 0.0f); + void readQuantitizedDouble(double& data, size_t numBits, float max, float min = 0.0f, double epsilon = 0.001); /** * Templated write functions. @@ -164,21 +166,23 @@ class BitStream { writeBytes((const uint8_t*)&object, sizeof(T)); } - void writeStringLength(uint16_t); + void writeStringLength(uint16_t length); - void write(const std::string&); + void write(const std::string& str); - void write(const std::wstring&); + void write(const std::wstring& str); /** - Write to buffer a float value within a min-max range as an integer value within a 0-max range. - @param data the float to write to buffer + Write to buffer a double value within a min-max float range as a value in a 0-[max] integer range. + @param data the double to write to buffer @param numBits the number of bits to be occupied in the buffer, the max converted integer value @param max the maximum float value @param min the minimum float value, defaulting to 0.0f + @param epsilon a comparable tolerable difference between decimal numbers, defaulting to 0.001 @throw invalid_argument if min is greater than max + @throw invalid_argument if the number of bits is zero */ - void writeQuantitizedFloat(const float&, const size_t, const float, const float = 0.0f); + void writeQuantitizedDouble(const double& data, size_t numBits, float max, float min = 0.0f, double epsilon = 0.001); std::vector& buf; diff --git a/src/common/bitstream_test.cpp b/src/common/bitstream_test.cpp index 6229c01..522d09e 100644 --- a/src/common/bitstream_test.cpp +++ b/src/common/bitstream_test.cpp @@ -127,15 +127,15 @@ void testBitstreamReadQuantitizedFloat() { }); BitStream bitstream(expectedBuf); - float x; - float y; - float z; - bitstream.readQuantitizedFloat(x, 20, 8192.0f); - bitstream.readQuantitizedFloat(y, 20, 8192.0f); - bitstream.readQuantitizedFloat(z, 16, 1024.0f); - assertEqual( compareFloats(x, 3674.85f, 100), 0); - assertEqual( compareFloats(y, 2726.79f, 100), 0); - assertEqual( compareFloats(z, 91.1576f, 100), 0); + double x; + double y; + double z; + bitstream.readQuantitizedDouble(x, 20, 8192.0); + bitstream.readQuantitizedDouble(y, 20, 8192.0); + bitstream.readQuantitizedDouble(z, 16, 1024.0); + assertEqual(compareDecimals(x, 3674.85, 0.01), 0); + assertEqual(compareDecimals(y, 2726.7917, 0.01), 0); + assertEqual(compareDecimals(z, 91.1581, 0.01), 0); } void testBitstreamWriteQuantitizedFloat() { @@ -143,64 +143,93 @@ void testBitstreamWriteQuantitizedFloat() { 0x6C, 0x2D, 0x76, 0x55, 0x35, 0xCA, 0x16 //6C2D7 65535 CA16 }); BitStream expected_bitstream(expectedBuf); - float x; - float y; - float z; - expected_bitstream.readQuantitizedFloat(x, 20, 8192.0f); - expected_bitstream.readQuantitizedFloat(y, 20, 8192.0f); - expected_bitstream.readQuantitizedFloat(z, 16, 1024.0f); + double a = 3674.85; + double b = 2726.7917; + double c = 91.1581; std::vector bitstreamBuf; BitStream test_bitstream(bitstreamBuf); - test_bitstream.writeQuantitizedFloat(x, 20, 8192.0f); - test_bitstream.writeQuantitizedFloat(y, 20, 8192.0f); - test_bitstream.writeQuantitizedFloat(z, 16, 1024.0f); - + test_bitstream.writeQuantitizedDouble(a, 20, 8192.0); + test_bitstream.writeQuantitizedDouble(b, 20, 8192.0); + test_bitstream.writeQuantitizedDouble(c, 16, 1024.0); assertBuffersEqual(bitstreamBuf, expectedBuf); } void testBitstreamReadQuantitizedFloatLimits() { - static std::vector expectedBuf = std::vector({ - 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF //FFFF 0000 FFFF - }); - BitStream bitstream(expectedBuf); - - float a; - float b; - float c; - bitstream.readQuantitizedFloat(a, 16, 256.0f, -256.0f); - bitstream.readQuantitizedFloat(b, 16, 256.0f, -256.0f); - bitstream.readQuantitizedFloat(c, 16, 256.0f, -256.0f); - assertEqual(compareFloats(a, 256.0f, 100), 0); - assertEqual(compareFloats(b, -256.0f, 100), 0); - assertEqual(compareFloats(c, 256.0f, 100), 0); + static std::vector expectedBuf = std::vector({ + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF //FFFF 0000 FFFF + }); + BitStream bitstream(expectedBuf); + + double a; + double b; + double c; + bitstream.readQuantitizedDouble(a, 16, 256.0, -256.0); + bitstream.readQuantitizedDouble(b, 16, 256.0, -256.0); + bitstream.readQuantitizedDouble(c, 16, 256.0, -256.0); + assertEqual(compareDecimals(a, 256.0, 0.01), 0); + assertEqual(compareDecimals(b, -256.0, 0.01), 0); + assertEqual(compareDecimals(c, 256.0, 0.01), 0); } void testBitstreamWriteQuantitizedFloatLimits() { - static std::vector expectedBuf = std::vector({ - 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF //FFFF 0000 FFFF - }); - BitStream test_bitstream(expectedBuf); - - float a = 260.5f; //too high - float b = -260.5f; //too low - std::vector bitstreamBuf; - BitStream bitstream(bitstreamBuf); - bitstream.writeQuantitizedFloat(a, 16, 256.0f, -256.0f); - bitstream.writeQuantitizedFloat(b, 16, 256.0f, -256.0f); - bitstream.writeQuantitizedFloat(a, 16, 256.0f, -256.0f); - assertBuffersEqual(bitstreamBuf, expectedBuf); - - bitstream.setPos(0); //rewind the stream - float ax; - float bx; - float cx; - bitstream.readQuantitizedFloat(ax, 16, 256.0f, -256.0f); - bitstream.readQuantitizedFloat(bx, 16, 256.0f, -256.0f); - bitstream.readQuantitizedFloat(cx, 16, 256.0f, -256.0f); - assertEqual(compareFloats(ax, 256.0f, 100), 0); - assertEqual(compareFloats(bx, -256.0f, 100), 0); - assertEqual(compareFloats(cx, 256.0f, 100), 0); + static std::vector expectedBuf = std::vector({ + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF //FFFF 0000 FFFF + }); + BitStream test_bitstream(expectedBuf); + + double a = 260.5; //too high for 256.0 + double b = -260.5; //too low for -256.0 + std::vector bitstreamBuf; + BitStream bitstream(bitstreamBuf); + bitstream.writeQuantitizedDouble(a, 16, 256.0, -256.0); + bitstream.writeQuantitizedDouble(b, 16, 256.0, -256.0); + bitstream.writeQuantitizedDouble(a, 16, 256.0, -256.0); + assertBuffersEqual(bitstreamBuf, expectedBuf); //confirms limiting +} + +void testBitStreamWriteAndReadBackQuantitizedFloats() { + double a = 3674.85; + double b = 2726.79; + double c = 91.1421; + + std::vector bitstreamBuf; + BitStream bitstream(bitstreamBuf); + bitstream.writeQuantitizedDouble(a, 20, 8192.0); + bitstream.writeQuantitizedDouble(b, 20, 8192.0); + bitstream.writeQuantitizedDouble(c, 16, 1024.0); + + bitstream.setPos(0); //rewind the stream + double ax; + double bx; + double cx; + bitstream.readQuantitizedDouble(ax, 20, 8192.0); + bitstream.readQuantitizedDouble(bx, 20, 8192.0); + bitstream.readQuantitizedDouble(cx, 16, 1024.0); + assertEqual(compareDecimals(ax, a, 0.01), 0); + assertEqual(compareDecimals(bx, b, 0.01), 0); + assertEqual(compareDecimals(cx, c, 0.01), 0); +} + +void testBitStreamReadAndWriteQuantitizedFloats() { + static std::vector expectedBuf = std::vector({ + 0x6C, 0x2D, 0x76, 0x55, 0x35, 0xCA, 0x16 //6C2D7 65535 CA16 + }); + BitStream test_bitstream(expectedBuf); + + double a; + double b; + double c; + test_bitstream.readQuantitizedDouble(a, 20, 8192.0); + test_bitstream.readQuantitizedDouble(b, 20, 8192.0); + test_bitstream.readQuantitizedDouble(c, 16, 1024.0); + + std::vector bitstreamBuf; + BitStream bitstream(bitstreamBuf); + bitstream.writeQuantitizedDouble(a, 20, 8192.0); + bitstream.writeQuantitizedDouble(b, 20, 8192.0); + bitstream.writeQuantitizedDouble(c, 16, 1024.0); + assertBuffersEqual(bitstreamBuf, expectedBuf); } void testBitstream() { @@ -215,6 +244,8 @@ void testBitstream() { testBitstreamReadQuantitizedFloat(); testBitstreamWriteQuantitizedFloat(); - testBitstreamReadQuantitizedFloatLimits(); - testBitstreamWriteQuantitizedFloatLimits(); + testBitstreamReadQuantitizedFloatLimits(); + testBitstreamWriteQuantitizedFloatLimits(); + testBitStreamWriteAndReadBackQuantitizedFloats(); + testBitStreamReadAndWriteQuantitizedFloats(); } diff --git a/src/common/util.cpp b/src/common/util.cpp index cada2ae..95668f1 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -77,35 +77,22 @@ std::vector hexToBytes(std::string hexStr) { return bytes; } -const int compareFloats(const float a, const float b, const size_t epsilon) { - /* - A simple decimal comparison might involve subtraction, fabs, and the result being less than a smaller decimal. - This method converts the IEEE float values into lexicographically-ordered two's-complement integers. - If (float)a > (float)b then (int)a > (int)b; but, precision concerns are lessened. - The error becomes a count of "the maximum number of lexico-numbers allowed between equal lexico-a and lexico-b." - To avoid encountering a NaN value, error can not risk being too big, however. - */ - if(epsilon >= 4194304) { - throw std::invalid_argument("float comparison - error margin too big"); - } - - int epsilon2 = *(int*)ε - int aint = *(int*)&a; - if(aint < 0) { - aint = 0x80000000 - aint; //conform negative scale to lexicographic order - } - int bint = *(int*)&b; - if(bint < 0) { - bint = 0x80000000 - bint; //conform negative scale to lexicographic order - } - //borrows from Java's Comparator compare(T o1, T o2) for return values - if(std::abs(aint - bint) <= epsilon2) { +/** +Basic double comparison test using std::fabs. +May be repurposed for other numeric data types cautiously. +Models return values after Java Comparator's compare(T o1, T o2). +@param a the first number +@param b the second number +@param epsilon the minimal tolerable difference between a and b before they are no longer considered equal +@return a negative integer, zero, or a positive integer as the first argument is <, ==, or > the second +*/ +int compareDecimals(double a, double b, double epsilon) { + double epsilonFabs = std::fabs(epsilon); + if (std::fabs(a - b) < epsilonFabs) { return 0; - } - else if(aint + epsilon2 < bint) { + } else if (a + epsilonFabs < b) { return -1; - } - else { + } else { return 1; } } diff --git a/src/common/util.h b/src/common/util.h index 4420c02..c8a5bca 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -10,12 +10,4 @@ void utilSleep(size_t ms); std::vector hexToBytes(std::string hexStr); -/** -Compare two float values for relative equality or the direction of inequality. -@param a the first float -@param b the second float -@param epsilon the margin of difference between parameter a and parameter b -@throw invalid_argument if epsilon is greater than 4 * 1024 * 1024 -@return a negative integer, zero, or a positive integer as the first argument is <, ==, or > the second -*/ -const int compareFloats(const float a, const float b, const size_t error); +int compareDecimals(double a, double b, double epsilon);