diff --git a/pvDataApp/misc/Makefile b/pvDataApp/misc/Makefile index c95263f..e56dd89 100644 --- a/pvDataApp/misc/Makefile +++ b/pvDataApp/misc/Makefile @@ -9,6 +9,12 @@ INC += serialize.h INC += bitSet.h INC += byteBuffer.h +LIBSRCS += bitSet.cpp + +LIBRARY=pvMisc + +pvMisc_LIBS += Com + include $(TOP)/configure/RULES #---------------------------------------- # ADD RULES AFTER THIS LINE diff --git a/pvDataApp/misc/bitSet.cpp b/pvDataApp/misc/bitSet.cpp new file mode 100644 index 0000000..30d05de --- /dev/null +++ b/pvDataApp/misc/bitSet.cpp @@ -0,0 +1,373 @@ +#include "bitSet.h" + +namespace epics { namespace pvData { + + BitSet::BitSet() : words(0), wordsLength(0), wordsInUse(0) { + initWords(BITS_PER_WORD); + } + + BitSet::BitSet(epicsUInt32 nbits) : words(0), wordsLength(0), wordsInUse(0) { + initWords(nbits); + } + + BitSet::~BitSet() { + delete words; + } + + void BitSet::initWords(epicsUInt32 nbits) { + epicsUInt32 length = (nbits <= 0) ? 1 : wordIndex(nbits-1) + 1; + if (words) delete words; + words = new epicsUInt64[length]; + bzero(words, sizeof(epicsUInt64)*length); + wordsLength = length; + } + + void BitSet::recalculateWordsInUse() { + // wordsInUse is unsigned + if (wordsInUse == 0) + return; + + // Traverse the bitset until a used word is found + epicsUInt32 i; + for (i = wordsInUse-1; i >= 0; i--) + if (words[i] != 0) + break; + + wordsInUse = i+1; // The new logical size + } + + void BitSet::ensureCapacity(epicsUInt32 wordsRequired) { + if (wordsLength < wordsRequired) { + + // create and copy + epicsUInt64* newwords = new epicsUInt64[wordsRequired]; + bzero(newwords, sizeof(epicsUInt64)*wordsRequired); + memcpy(newwords, words, sizeof(epicsUInt64)*wordsLength); + if (words) delete words; + words = newwords; + wordsLength = wordsRequired; + } + } + + void BitSet::expandTo(epicsUInt32 wordIndex) { + epicsUInt32 wordsRequired = wordIndex+1; + if (wordsInUse < wordsRequired) { + ensureCapacity(wordsRequired); + wordsInUse = wordsRequired; + } + } + + void BitSet::flip(epicsUInt32 bitIndex) { + + epicsUInt32 wordIdx = wordIndex(bitIndex); + expandTo(wordIdx); + + words[wordIdx] ^= (((epicsUInt64)1) << (bitIndex % BITS_PER_WORD)); + + recalculateWordsInUse(); + } + + void BitSet::set(epicsUInt32 bitIndex) { + + epicsUInt32 wordIdx = wordIndex(bitIndex); + expandTo(wordIdx); + + words[wordIdx] |= (((epicsUInt64)1) << (bitIndex % BITS_PER_WORD)); + } + + void BitSet::clear(epicsUInt32 bitIndex) { + + epicsUInt32 wordIdx = wordIndex(bitIndex); + if (wordIdx >= wordsInUse) + return; + + words[wordIdx] &= ~(((epicsUInt64)1) << (bitIndex % BITS_PER_WORD)); + + recalculateWordsInUse(); + } + + void BitSet::set(epicsUInt32 bitIndex, bool value) { + if (value) + set(bitIndex); + else + clear(bitIndex); + } + + bool BitSet::get(epicsUInt32 bitIndex) const { + epicsUInt32 wordIdx = wordIndex(bitIndex); + return ((wordIdx < wordsInUse) + && ((words[wordIdx] & (((epicsUInt64)1) << (bitIndex % BITS_PER_WORD))) != 0)); + } + + void BitSet::clear() { + while (wordsInUse > 0) + words[--wordsInUse] = 0; + } + + epicsUInt32 BitSet::numberOfTrailingZeros(epicsUInt64 i) { + // HD, Figure 5-14 + epicsUInt32 x, y; + if (i == 0) return 64; + epicsUInt32 n = 63; + y = (epicsUInt32)i; if (y != 0) { n = n -32; x = y; } else x = (epicsUInt32)(i>>32); + y = x <<16; if (y != 0) { n = n -16; x = y; } + y = x << 8; if (y != 0) { n = n - 8; x = y; } + y = x << 4; if (y != 0) { n = n - 4; x = y; } + y = x << 2; if (y != 0) { n = n - 2; x = y; } + return n - ((x << 1) >> 31); + } + + epicsUInt32 BitSet::bitCount(epicsUInt64 i) { + // HD, Figure 5-14 + i = i - ((i >> 1) & 0x5555555555555555LL); + i = (i & 0x3333333333333333LL) + ((i >> 2) & 0x3333333333333333LL); + i = (i + (i >> 4)) & 0x0f0f0f0f0f0f0f0fLL; + i = i + (i >> 8); + i = i + (i >> 16); + i = i + (i >> 32); + return (epicsUInt32)(i & 0x7f); + } + + epicsInt32 BitSet::nextSetBit(epicsUInt32 fromIndex) const { + + epicsUInt32 u = wordIndex(fromIndex); + if (u >= wordsInUse) + return -1; + + epicsUInt64 word = words[u] & (WORD_MASK << (fromIndex % BITS_PER_WORD)); + + while (true) { + if (word != 0) + return (u * BITS_PER_WORD) + numberOfTrailingZeros(word); + if (++u == wordsInUse) + return -1; + word = words[u]; + } + } + + epicsInt32 BitSet::nextClearBit(epicsUInt32 fromIndex) const { + // Neither spec nor implementation handle bitsets of maximal length. + + epicsUInt32 u = wordIndex(fromIndex); + if (u >= wordsInUse) + return fromIndex; + + epicsUInt64 word = ~words[u] & (WORD_MASK << (fromIndex % BITS_PER_WORD)); + + while (true) { + if (word != 0) + return (u * BITS_PER_WORD) + numberOfTrailingZeros(word); + if (++u == wordsInUse) + return wordsInUse * BITS_PER_WORD; + word = ~words[u]; + } + } + + bool BitSet::isEmpty() const { + return (wordsInUse == 0); + } + + epicsUInt32 BitSet::cardinality() const { + epicsUInt32 sum = 0; + for (epicsUInt32 i = 0; i < wordsInUse; i++) + sum += bitCount(words[i]); + return sum; + } + + BitSet& BitSet::operator&=(const BitSet& set) { + + while (wordsInUse > set.wordsInUse) + words[--wordsInUse] = 0; + + // Perform logical AND on words in common + for (epicsUInt32 i = 0; i < wordsInUse; i++) + words[i] &= set.words[i]; + + recalculateWordsInUse(); + + return *this; + } + + BitSet& BitSet::operator|=(const BitSet& set) { + + epicsUInt32 wordsInCommon; + if (wordsInUse < set.wordsInUse) { + wordsInCommon = wordsInUse; + //ensureCapacity(set.wordsInUse); + //wordsInUse = set.wordsInUse; + } + else + wordsInCommon = set.wordsInUse; + + // Perform logical OR on words in common + epicsUInt32 i = 0; + for (; i < wordsInCommon; i++) + words[i] |= set.words[i]; + + // TODO what to do if BitSets are not the same size !!! + + // recalculateWordsInUse() is not needed + + return *this; + } + + BitSet& BitSet::operator^=(const BitSet& set) { + + epicsUInt32 wordsInCommon; + if (wordsInUse < set.wordsInUse) { + wordsInCommon = wordsInUse; + //ensureCapacity(set.wordsInUse); + //wordsInUse = set.wordsInUse; + } + else + wordsInCommon = set.wordsInUse; + + // Perform logical XOR on words in common + epicsUInt32 i = 0; + for (; i < wordsInCommon; i++) + words[i] ^= set.words[i]; + + // TODO what to do if BitSets are not the same size !!! + + recalculateWordsInUse(); + + return *this; + } + + BitSet& BitSet::operator-=(const BitSet& set) { + + epicsUInt32 wordsInCommon; + if (wordsInUse < set.wordsInUse) { + wordsInCommon = wordsInUse; + //ensureCapacity(set.wordsInUse); + //wordsInUse = set.wordsInUse; + } + else + wordsInCommon = set.wordsInUse; + + // Perform logical (a & !b) on words in common + epicsUInt32 i = 0; + for (; i < wordsInCommon; i++) + words[i] &= ~set.words[i]; + + recalculateWordsInUse(); + + return *this; + } + + BitSet& BitSet::operator=(const BitSet &set) { + // Check for self-assignment! + if (this == &set) + return *this; + + // we ensure that words array size is adequate (and not wordsInUse to ensure capacity to the future) + if (wordsLength < set.wordsLength) + { + if (words) delete words; + words = new epicsUInt64[set.wordsLength]; + wordsLength = set.wordsLength; + } + memcpy(words, set.words, sizeof(epicsUInt64)*set.wordsInUse); + wordsInUse = set.wordsInUse; + + return *this; + } + + void BitSet::or_and(const BitSet& set1, const BitSet& set2) { + epicsUInt32 inUse = (set1.wordsInUse < set2.wordsInUse) ? set1.wordsInUse : set2.wordsInUse; + + ensureCapacity(inUse); + wordsInUse = inUse; + + // Perform logical AND on words in common + for (epicsUInt32 i = 0; i < inUse; i++) + words[i] |= (set1.words[i] & set2.words[i]); + + // recalculateWordsInUse()... + } + + bool BitSet::operator==(const BitSet &set) const + { + if (this == &set) + return true; + + if (wordsInUse != set.wordsInUse) + return false; + + // Check words in use by both BitSets + for (epicsUInt32 i = 0; i < wordsInUse; i++) + if (words[i] != set.words[i]) + return false; + + return true; + } + + void BitSet::toString(StringBuilder buffer) { toString(buffer, 0); } + + void BitSet::toString(StringBuilder buffer, int indentLevel) const + { + *buffer += '{'; + epicsInt32 i = nextSetBit(0); + char tmp[30]; + if (i != -1) { + sprintf(tmp,"%d",i); *buffer += tmp; + for (i = nextSetBit(i+1); i >= 0; i = nextSetBit(i+1)) { + epicsInt32 endOfRun = nextClearBit(i); + do { *buffer += ", "; sprintf(tmp,"%d",i); *buffer += tmp; } while (++i < endOfRun); + } + } + *buffer += '}'; + } + + + /* + +void serialize(ByteBuffer buffer, SerializableControl flusher) { + + final int n = wordsInUse; + if (n == 0) { + SerializeHelper.writeSize(0, buffer, flusher); + return; + } + int len = 8 * (n-1); + for (long x = words[n - 1]; x != 0; x >>>= 8) + len++; + + SerializeHelper.writeSize(len, buffer, flusher); + flusher.ensureBuffer(len); + + for (int i = 0; i < n - 1; i++) + buffer.putLong(words[i]); + + for (long x = words[n - 1]; x != 0; x >>>= 8) + buffer.put((byte) (x & 0xff)); +} + +public void deserialize(ByteBuffer buffer, DeserializableControl control) { + + final int bytes = SerializeHelper.readSize(buffer, control); // in bytes + + wordsInUse = (bytes + 7) / 8; + if (wordsInUse > words.length) + words = new long[wordsInUse]; + + if (wordsInUse == 0) + return; + + control.ensureData(bytes); + + int i = 0; + final int longs = bytes / 8; + while (i < longs) + words[i++] = buffer.getLong(); + + for (int j = i; j < wordsInUse; j++) + words[j] = 0; + + for (int remaining = (bytes - longs * 8), j = 0; j < remaining; j++) + words[i] |= (buffer.get() & 0xffL) << (8 * j); + +} + */ + +}}; diff --git a/pvDataApp/misc/bitSet.h b/pvDataApp/misc/bitSet.h index 2d43818..a2eed4b 100644 --- a/pvDataApp/misc/bitSet.h +++ b/pvDataApp/misc/bitSet.h @@ -1,10 +1,307 @@ /* bitSet.h */ #ifndef BITSET_H #define BITSET_H +#include +#include +//#include "byteBuffer.h" +//#include "serialize.h" namespace epics { namespace pvData { - class BitSet; - // must be defined and implemented + // TODO !!! + typedef unsigned long long epicsUInt64; + typedef std::string * StringBuilder; + + /** + * This class implements a vector of bits that grows as needed. Each + * component of the bit set has a {@code bool} value. The + * bits of a {@code BitSet} are indexed by nonnegative integers. + * Individual indexed bits can be examined, set, or cleared. One + * {@code BitSet} may be used to modify the contents of another + * {@code BitSet} through logical AND, logical inclusive OR, and + * logical exclusive OR operations. + * + *

By default, all bits in the set initially have the value + * {@code false}. + * + *

Every bit set has a current size, which is the number of bits + * of space currently in use by the bit set. Note that the size is + * related to the implementation of a bit set, so it may change with + * implementation. The length of a bit set relates to logical length + * of a bit set and is defined independently of implementation. + * + *

A {@code BitSet} is not safe for multithreaded use without + * external synchronization. + * + * Based on Java implementation. + */ + class BitSet /*: public Serializable*/ { + public: + + /** + * Creates a new bit set. All bits are initially {@code false}. + */ + BitSet(); + + /** + * Creates a bit set whose initial size is large enough to explicitly + * represent bits with indices in the range {@code 0} through + * {@code nbits-1}. All bits are initially {@code false}. + * + * @param nbits the initial size of the bit set + */ + BitSet(epicsUInt32 nbits); + + /** + * Destructor. + */ + virtual ~BitSet(); + + /** + * Sets the bit at the specified index to the complement of its + * current value. + * + * @param bitIndex the index of the bit to flip + */ + void flip(epicsUInt32 bitIndex); + + /** + * Sets the bit at the specified index to {@code true}. + * + * @param bitIndex a bit index + */ + void set(epicsUInt32 bitIndex); + + /** + * Sets the bit specified by the index to {@code false}. + * + * @param bitIndex the index of the bit to be cleared + */ + void clear(epicsUInt32 bitIndex); + + /** + * Sets the bit at the specified index to the specified value. + * + * @param bitIndex a bit index + * @param value a boolean value to set + */ + void set(epicsUInt32 bitIndex, bool value); + + /** + * Returns the value of the bit with the specified index. The value + * is {@code true} if the bit with the index {@code bitIndex} + * is currently set in this {@code BitSet}; otherwise, the result + * is {@code false}. + * + * @param bitIndex the bit index + * @return the value of the bit with the specified index + */ + bool get(epicsUInt32 bitIndex) const; + + /** + * Sets all of the bits in this BitSet to {@code false}. + */ + void clear(); + + /** + * Returns the index of the first bit that is set to {@code true} + * that occurs on or after the specified starting index. If no such + * bit exists then {@code -1} is returned. + * + *

To iterate over the {@code true} bits in a {@code BitSet}, + * use the following loop: + * + *

 {@code
+         * for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) {
+         *     // operate on index i here
+         * }}
+ * + * @param fromIndex the index to start checking from (inclusive) + * @return the index of the next set bit, or {@code -1} if there + * is no such bit + */ + epicsInt32 nextSetBit(epicsUInt32 fromIndex) const; + + /** + * Returns the index of the first bit that is set to {@code false} + * that occurs on or after the specified starting index. + * + * @param fromIndex the index to start checking from (inclusive) + * @return the index of the next clear bit + */ + epicsInt32 nextClearBit(epicsUInt32 fromIndex) const; + + /** + * Returns true if this {@code BitSet} contains no bits that are set + * to {@code true}. + * + * @return indicating whether this {@code BitSet} is empty + */ + bool isEmpty() const; + + /** + * Returns the number of bits set to {@code true} in this {@code BitSet}. + * + * @return the number of bits set to {@code true} in this {@code BitSet} + */ + epicsUInt32 cardinality() const; + + /** + * Performs a logical AND of this target bit set with the + * argument bit set. This bit set is modified so that each bit in it + * has the value {@code true} if and only if it both initially + * had the value {@code true} and the corresponding bit in the + * bit set argument also had the value {@code true}. + * + * @param set a bit set + */ + BitSet& operator&=(const BitSet& set); + + /** + * Performs a logical OR of this bit set with the bit set + * argument. This bit set is modified so that a bit in it has the + * value {@code true} if and only if it either already had the + * value {@code true} or the corresponding bit in the bit set + * argument has the value {@code true}. + * + * @param set a bit set + */ + BitSet& operator|=(const BitSet& set); + + /** + * Performs a logical XOR of this bit set with the bit set + * argument. This bit set is modified so that a bit in it has the + * value {@code true} if and only if one of the following + * statements holds: + * + * + * @param set a bit set + */ + BitSet& operator^=(const BitSet& set); + + /** + * Clears all of the bits in this {@code BitSet} whose corresponding + * bit is set in the specified {@code BitSet}. + * + * @param set the {@code BitSet} with which to mask this + * {@code BitSet} + */ + BitSet& operator-=(const BitSet& set); + + /** + * Assigment operator. + */ + BitSet& operator=(const BitSet &set); + + /** + * Perform AND operation on set1 and set2, + * and OR on result and this instance. + * @param set1 + * @param set2 + */ + void or_and(const BitSet& set1, const BitSet& set2); + + /** + * Comparison operator. + */ + bool operator==(const BitSet &set) const; + + void toString(StringBuilder buffer); + + void toString(StringBuilder buffer, int indentLevel) const; + + private: + + /* + * BitSets are packed into arrays of "words." Currently a word is + * a long, which consists of 64 bits, requiring 6 address bits. + * The choice of word size is determined purely by performance concerns. + */ + static const epicsUInt32 ADDRESS_BITS_PER_WORD = 6; + static const epicsUInt32 BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD; + static const epicsUInt32 BIT_INDEX_MASK = BITS_PER_WORD - 1; + + /** Used to shift left or right for a partial word mask */ + static const epicsUInt64 WORD_MASK = ~((epicsUInt64)0); + + /** The internal field corresponding to the serialField "bits". */ + epicsUInt64* words; + + /** The internal field corresponding to the size of words[] array. */ + epicsUInt32 wordsLength; + + /** The number of words in the logical size of this BitSet. */ + epicsUInt32 wordsInUse; + + + private: + + /** + * Given a bit index, return word index containing it. + */ + static inline epicsUInt32 wordIndex(epicsUInt32 bitIndex) { + return bitIndex >> ADDRESS_BITS_PER_WORD; + } + + /** + * Creates a new word array. + */ + void initWords(epicsUInt32 nbits); + + /** + * Sets the field wordsInUse to the logical size in words of the bit set. + * WARNING: This method assumes that the number of words actually in use is + * less than or equal to the current value of wordsInUse! + */ + void recalculateWordsInUse(); + + /** + * Ensures that the BitSet can hold enough words. + * @param wordsRequired the minimum acceptable number of words. + */ + void ensureCapacity(epicsUInt32 wordsRequired); + + /** + * Ensures that the BitSet can accommodate a given wordIndex, + * temporarily violating the invariants. The caller must + * restore the invariants before returning to the user, + * possibly using recalculateWordsInUse(). + * @param wordIndex the index to be accommodated. + */ + void expandTo(epicsUInt32 wordIndex); + + /** + * Returns the number of zero bits following the lowest-order ("rightmost") + * one-bit in the two's complement binary representation of the specified + * long value. Returns 64 if the specified value has no + * one-bits in its two's complement representation, in other words if it is + * equal to zero. + * + * @return the number of zero bits following the lowest-order ("rightmost") + * one-bit in the two's complement binary representation of the + * specified long value, or 64 if the value is equal + * to zero. + */ + static epicsUInt32 numberOfTrailingZeros(epicsUInt64 i); + + /** + * Returns the number of one-bits in the two's complement binary + * representation of the specified long value. This function is + * sometimes referred to as the population count. + * + * @return the number of one-bits in the two's complement binary + * representation of the specified long value. + */ + static epicsUInt32 bitCount(epicsUInt64 i); + + }; }} #endif /* BITSET_H */ + + + diff --git a/pvDataApp/test/Makefile b/pvDataApp/test/Makefile index dfc1863..eb4060b 100644 --- a/pvDataApp/test/Makefile +++ b/pvDataApp/test/Makefile @@ -22,6 +22,10 @@ PROD_HOST += testSimpleStructure testSimpleStructure_SRCS += testSimpleStructure.cpp testSimpleStructure_LIBS += pvFactory +PROD_HOST += testBitSet +testBitSet_SRCS += testBitSet.cpp +testBitSet_LIBS += pvMisc Com + include $(TOP)/configure/RULES #---------------------------------------- # ADD RULES AFTER THIS LINE diff --git a/pvDataApp/test/testBitSet.cpp b/pvDataApp/test/testBitSet.cpp new file mode 100644 index 0000000..e294a19 --- /dev/null +++ b/pvDataApp/test/testBitSet.cpp @@ -0,0 +1,143 @@ +/* testBitSet.cpp */ +/* Author: Matej Sekoranja Date: 2010.10.18 */ + +#include +#include +#include +#include +#include +#include "BitSet.h" + + +#include + +using namespace epics::pvData; + +void testGetSetClearFlip() { + printf("testGetSetClearFlip... "); + + // empty + BitSet* b1 = new BitSet(); + assert(b1->isEmpty()); + assert(b1->cardinality() == 0); + // to string check + std::string str; b1->toString(&str); + assert(str == "{}"); + + // one + b1->set(3); + assert(b1->get(3)); + assert(!b1->isEmpty()); + assert(b1->cardinality() == 1); + // to string check + str.clear(); b1->toString(&str); + assert(str == "{3}"); + + // grow + b1->set(66); + b1->set(67); + b1->set(68); + assert(b1->cardinality() == 4); + str.clear(); b1->toString(&str); + assert(str == "{3, 66, 67, 68}"); + + // clear one + b1->clear(67); + assert(b1->cardinality() == 3); + str.clear(); b1->toString(&str); + assert(str == "{3, 66, 68}"); + + // flip + b1->flip(66); + b1->flip(130); + assert(b1->cardinality() == 3); + str.clear(); b1->toString(&str); + assert(str == "{3, 68, 130}"); + + // flip + b1->set(130, false); + b1->set(4, true); + assert(b1->cardinality() == 3); + str.clear(); b1->toString(&str); + assert(str == "{3, 4, 68}"); + + // clear all + b1->clear(); + assert(b1->isEmpty()); + assert(b1->cardinality() == 0); + str.clear(); b1->toString(&str); + assert(str == "{}"); + + delete b1; + printf("PASSED\n"); + +} + +void testOperators() { + printf("testOperators... "); + + BitSet b1; + assert(b1 == b1); + BitSet b2; + assert(b1 == b2); + + b1.set(1); + assert(!(b1 == b2)); + + // different internal length, but the same + b2.set(100); + b2.set(1); + b2.flip(100); + assert(b1 == b2); + + // OR test + b1.set(65); + b1.set(106); + b2.set(105); + b1 |= b2; + std::string str; b1.toString(&str); + assert(str == "{1, 65, 105, 106}"); + + // AND test + b1.set(128); + b1 &= b2; + assert(b1 == b2); + + // XOR test + b1.set(128); + b1 ^= b2; + assert(b1.cardinality() == 1 && b1.get(128) == true); + + // a AND (NOT b) + b2 -= b1; + str.clear(); b2.toString(&str); + assert(str == "{1, 105}"); + b1.set(1); + b2 -= b1; + str.clear(); b2.toString(&str); + assert(b2.cardinality() == 1 && b2.get(105) == true); + + // assign + b1 = b2; + assert(b1 == b2); + + // or_and + b1.clear(); b1.set(2); + b2.clear(); b2.set(66); b2.set(128); + BitSet b3; b3.set(128); b3.set(520); + b1.or_and(b2, b3); + str.clear(); b1.toString(&str); + assert(str == "{2, 128}"); + + printf("PASSED\n"); + +} + + +int main(int argc,char *argv[]) +{ + testGetSetClearFlip(); + testOperators(); + return(0); +} +