>> Well, first thing I want to do
today is talk to you about BitVectors. So, I'm going to click on
BitVectors lecture notes. And they're up here. Yes. And click the first
slide, bits and bytes. Byte-- A byte consists of 8 bits. Bits are any base that have
a value of either 0 or 1. They can be represented
in hardware by differences in electromagnetic properties
and materials, a bit can-- So, the bits are the way you
can have a spec similarly of actual hardware to work with. In most modern computer
systems, the byte consisting of 8 bits is the smallest amount-- smallest piece of memory
that you can directly access through a programming language. And a byte consists of 8 bits. Now, remember that those 8 bits that's
a-- that is if you take any 8 bits and put them in there, that
represents a binary number. So, the smallest number
that can be represented in a byte is eight 0s which is 0. The largest number is eight 1s
which is 2 to the power of 8 minus 1 which is 256 minus 1 or 255. So, in our normal decimal conversational
world, a byte can represent a number with the value of anywhere
between 0 including 0 and-- up to and including 255,
thinking in base 10. Now, does other bases that
we often talked about, of course we just mentioned binary,
sometimes we talk about octal. Binary decimal and hexadecimal
are really the three that you'll probably run into the most. Hexadecimal is convenient mainly
because it uses 16 digits instead of the 10 digits that decimal uses
because it gives you 16 digits that-- those 16 values take two of the-- those 16 values in two different
places represent 2 to the power of 16 minus 1 different possible
values which is the same thing as 255. And so, the reason hexadecimal
is popular is that each hexadecimal digit
represents exactly half of a byte. And so, two hexadecimal
digits represent a full byte. So, 00 hex represents eight 0 bits. FFX represents eight 1 bits. Byte, by the way, is
also the size necessary to represent an ascii character. Let me call up something that
I'm assuming is on this machine. I'm looking for a calculator. [ Pause ] Well, I'll look for that later. On your windows machine, you almost
certainly will have a calculator and that calculator I'll show you when
I'll find it on here, I'll show you. You can set that into program or mode
and it's quite handy to experiment with decimals, hexadecimals
and binary notation. Let's get on with my slides here. So, a byte is the smallest directly
accessible entity as we're seeing. Byte consists of 8 bits, range of values
and 1 byte is 0 through 255 decimal or 00 through FF hexadecimal. A byte is the size of
an ascii character. C and C++ both provide access to
individual bits via bit operations and that's part of what we're
going to talk about today. And modern computers provide
hardware support for bit operations so that anything you can do at the bit
and with bitwise operations has very, very fast execution time and on the
order of one or two clock cycles of the CPU as opposed to, for
example, higher level arithmetic in the energy world requires invocation
of an algorithm typically which is going to consume a lot more time in one
or two clock cycles of the CPU. So, we'll get on to this. Let's say-- So we have--
These are bitwise operations and they are also logic operations. The first four of them are. There's AND, OR, exclusive OR,
and NOT as logical operations. The symbol for that as a bit operation,
the symbol for AND is ampersand, the symbol for OR is a vertical bar,
the symbol for exclusive OR is the caret and the symbol for NOT is the tilde. And the way any of those would
work is you can do Z equals X and Y and that means the-- whatever kind of
numbers it is, it's an integer type of number, it will be the number-- the binary number that X represents
bitwise ending with binary numbers that Y represents and that bitwise
AND will be calculated in parallel for each bit in the number. And similarly, you have the bitwise OR,
the bitwise exclusive OR and the NOT. And the left and right shift operators
are not really logical operators but you'll notice these--
that one is called left shift and this one is called the right shift. Those look just like your
input and output operators and in fact they are the same operator. The history of this is
these were important-- left and right shift is an important
thing to be able to do on bit, the bit level in a machine. And so these operations were defined
in C which of course C++ has inherited. And so-- But when you get into C++
where you can overload operators to mean different findings. And so, this left shift operator is
overloaded in the C++ at I/O library to mean output operator and
right shift is overloaded to mean the input operator. But there are origins-- their original
native definition is left shift and right shift. So, here are some examples
of bitwise operations. So first, I'm going to give some
examples of-- this is a left shift. If X is that, I'm giving you the
binary, the byte in terms of bits there, and the value of Y is 2 taking X and left shifting byte Y will
have the effect on moving all of the bits to the left two places. So, there's only one nonzero
bit here and you'll notice that after left shift two places,
it ends up two notches over. Right shift, by certain number
of places moves all the bits to the right, that many slots. And by the way, because the register
is finite, whenever you move the bits to the right, for example, there's
going to be bits that are falling out of the register or falling off
the end, and those just go away, we think of that-- we call that
euphemistically the bit bucket. So, they go in to the bit
bucket which is trash tries to taken away that no longer exists. And the feed in when you shift
everything out of where what feeds in on the shifting side is a 0 bit. So, some of the bits fall into oblivion
shifting off the edge there were-- sizes or place of 0 bits coming in. So in particular, if you take
any byte and left shift it by 8, it's going to become 0 and if you-- or
right shifted by 8 is going to become 0. The bitwise AND operates by applying the
logical AND if you like at every bit. So, for example, 1 bit was
ended with 0 becomes 0. So, that's like saying true--
a true and a false is false. On the other hand, an OR-- or can I
say-- or here's an OR that's false, bitwise OR with a bit that's
true so I have a true or false. Well, that's true so logically true
or false is logically true, whereas, true and false is logically false. And-- Let's see, if I click
on the narrative here. I'm going to back up one step. The narrative for the previous
slide has the definitions of AND and OR and XOR and NOT. And 8 bit, these tables
are used to calculate. And this is what I mean by logic--
and bitwise AND is the same as logical AND if you interpret 0
as false and 1 is true. So, for AND, if X is true and Y-- if X is false and Y is
false then X and Y is false. If X is false and Y is true
then X and Y is still false. If X is true and Y is false
then X and Y is false. Finally, if X is true and Y
is true then X and Y is true. OR kind of woks differently if the
only way OR is false is if both X and Y are false and OR is also false. Otherwise, one of the other two sides
being true makes the OR to be true. Exclusive OR means one is true
and the other one is not true. And so both false, you got a false. False true, you got a true. True false, you got a true. And true, true, you got it false. Of course, NOT is just
flipped a bit, not true-- not false is true and not
true is false, click the bit. So, I'm going to pause my
machine here just for a second. All right. I paused there so I could
find the calculator and of course it was
right there all along. So, click on your windows
calculator if you have one of those-- if you have a Mac, somehow
you've got a calculator I'm sure. And if you click on this view, you notice that you have standard
scientific program or statistics views. And I'm going to click on the program
overview and sort of magic world opens up here that the deck here means I'm
allowed to enter numbers as decimals. But what you're going to see
up here is the representation of those numbers in various formats. So, I'm going to hit binary
so we can play with binary, and I'm going to hit just a single byte. OK. So, there's the byte
being displayed, all right. I'm sure how that happened there. Oh, this is bit. I'm able to click on these
bits and flip on my guess. Yes. So, there is a number,
a binary number there. So, I'm going to clear that. So, let me enter the number 1 as
a binary number, it's right there. Now, I'm going to left shift that by 2. Hold on a minute, I got
to go back to decimal. Let me clear. So, I'm entering now is decimals. So, I'm going to enter 1 and
you see that bit and I'm going to left shift by, let's say, 3. So, 1 left shifted by 3, [ Pause ] 1 left shifted by 3, that's the number 3
equals 8, that's the decimal value of it but you see the 1 has just been moved over three places, went
from here to here. So, left shift 1 by 3 and the odd thing
is the decimal value of that is 8. So, that's kind of a
lesson in optimization. If you want to raise a number
such a-- let me clear this. If you want to raise a number, let's
say 3 and I want to raise it to the-- I'm sorry, let's see there. If I want to take that
number and multiply it by 8. So, 3 times 8 is 24, right? If I'm-- that's-- I'm going to left
shift it by 3 and notice I got 24. So, what happened was I had that
number 3 and I left shifted it by 3 and you can see all I did was
take the bits and move them over to the left three slots,
but decimal value was-- that was the same thing
as multiplying that by 3, the 2 to the power of 3 which is 8. So, 3 times 8 is 24. Well, anyway, play with
your calculator, it can-- it will help you get a
hand along bitwise stuff. Now, the reason I'm bringing
this up at this time is to introduce a class
which is called BitVector. And this is going to be a
class in your FSU library. We will distribute it for you-- to
you and you're going to use this class in one of your assignments namely
homework four coming up next week. So, this is a-- So, we're
in the namespace that's FSU and the class name is BitVector. Now, what is a BitVector
supposed to mean or is supposed to represent a whole collection
of bits as long as you need to be. So, if you want a thousand
bits, you can make a BitVector with a thousand bits in it. That's what a BitVector
does and it has-- there's not much we can do with bits. We can set them, that is
to make them equal to 1. You can unset them, make
them equal to zero. You can flip them which changes
as 0 to 1 or from 1 to 0 depending on what they are, and you can ask,
what is the value of that bit? And that is going to be our test option. Notice that this set, unset flip
and test all take an index value. So, if we got a thousand bits and I want to set the third bit then I would
do set three and that would flip-- that would make that
bit index 3 equal to 1. I just opened up a-- [ Pause ] When I enter [inaudible]
prog, I had already logged in. This is a test on S4 BitVector
and so this kind of what-- it's kind of what the number
of bits that I'm asking for. So, I'm going to go in and just
ask for-- let's ask for 24 bits. OK, so this is in window
in into my BitVector. Oh, I said 24 although I
entered 14, so I've got 14 bits. Notice that I can set,
unset, flip, test. Now you see I have a dump method. I've got the output operator. I can test my copy and assign
what statements by entering that. I can display the menu by entering
M and I can exit the program by hitting the X. So, I'm going to
exit the program and enter my 24 like I previously intended. With that, I now have a BitVector of 24 bits called V here
that I'm operating now. So, let's just look at the display
first of all, so that will be output. So, I'll just say output and you see that I get this representation
of just 24 0 bits there. I can also dump the bit. The difference is that the dump, not
only showed you the contents as a bunch of bits, but it gives you first
digit of index of each of those bits. So I've got index 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, 10, 11, 12 and so on out through 23. So, I have-- I asked for 24
bits and that's how many I have and they're indexed from 0 to 23 as you would expect given your
experience with the raise and so on. So the next thing I'll do
is do some flipping I guess. Notice some setting so I
can do set and set bit 1. OK. So now I can dump and you'll see
that bit 1 got set and that reminds you that bit 1 wasn't the first
bit, it's the second bit. That 0 is the first bit. I can set bit 4 and set
that 7 and you'll see that bit 4 has been set
and bit 7 has been set. I can set bit 23 and you will
see that bit has been set. I can now look at my menu again. I could unset some things. Let me do a nice flip. You can flip and-- by the way, notice
that there's a set with no parameters and an unset with no parameters
and a flip with no parameters. Those were accessed with the
capital letters in the commands. Flip with no parameters
means flip all of the bits, not just the one at--
I'm sorry, an index. I'm going to flip all of the bits
and show you that-- well what? Used to be 0 become 1 and
what used to be 1 become 0. It gets pretty dull, but-- pretty quickly because all we can
do is flip, set, unset, and so on. But this is there-- This
is going to be distributed at the time we release homework
four, you will have BitVector and you will have this test RS
fbitvect that you can compile and run and experiment with BitVectors
through that interface and that will probably help you
do your homework four assignment. I'll just flip at that for a moment. Now I like to add something nonsense
here which in fact will tend to keep me from getting automatically
log out during class. OK. So, that is the API
for BitVector, well almost. The output operator was also
overloaded for BitVector. So, it's got a constructor with one
argument and no other construct, well a copy constructor but
no other regular constructor. So, to create a BitVector,
you need to give a parameter which is the number of
bits that you want. That's called a copy destructor,
destructor assignment operator and it also has a function called size
which tells you the size of a BitVector. So, when we look at is how
might we implement that? So we have-- Whenever you have a class, this is the public, the
API for that class. The question here the developer might
immediately ask is how might I implement this class and in a sense that
starts out by asking what is going to be the private area of this
class, what it's going to look like? And what I have here
is that there's going to be private data and
private methods, OK? So, the data is going to be an array
of bytes which I'll call byteArray. Of course, an unsigned
char is a byte, so. And there's going to be--
there's going to be a size value which tells me what the
size of that byteArray is. So, the idea there is, let's say
I want 24 bits in a BitVector, what this is going to do is
allocate 3 bytes, 3 times 8 is 24. So, my byteArraysSize will
be 3 and I'll have 3 a byte, an array here of three
chars and that's where all of my bits are going to
be, all 24 of my bits. And then I'm going to have some methods. One is called ByteNumber
and one is called mask, and those two methods allow me to
zero in on a bit from an index. So, let's talk about the ByteNumber. This is how we're going
to implement ByteNumber. The idea is if I give it an index,
what byte does that bit live in? What was 8 bits in each byte? So to get the byte that that bit
lives in, you got to divided by 8, right, to get the right byte. And so, that's what we do. We take the index and
divided it by 8 as integers. Now, shifting right by 3 is exactly the
same as dividing by 8 mathematically but shifting right by 8 is a hardware
supported low level bit operation and it will take place in
essentially one clock cycle, whereas, invoking the integer division
algorithm will be a lot more time spent and because of those special nature
of 8, you know, 8 being a power of 2 and into the third power, we
can accomplish that division in a much more direct
way by accessing hardware and shifting our index
right by three notches. So the ByteNumber is really the
index divided by 8 but we optimize that by taking the index and shifting it to the right three times
and then we return index. So, that's that. And the mask says, OK, I
know which byte I'm in, now which bit of that byte
am I in that is not bit? And so, for that, we-- instead of
dividing by 8 in the integer world, we're going to have to
check the remainder when we divide by 8 in
the integer world. And so, we take our index and it's-- if what I want to do is take the
remainder when I divide by 8. Well, when you divide by 8, remember
that's right shifting by three, all those bits that fall off into
the bit bucket, that's the remainder. So, in other words, those three
least significant bits are in fact the remainder
when you divide by 8. And we can get to those in hardware
supported one clock cycle operation by taking the byte which has three bits
in it and bitwise ending with index. So let me illustrate that
by going to the board here. I think I've got the camera
setup where it will hit that. So, what we're going to do is-- so,
here's a byte and we want to know-- we want to look at-- we're going to
get the first three bits in that byte. So, this is the remainder when
the byte be here is divided by 8 which is 2 to the power of 3. That, most of the bits that fall off
when you shift to right three notches and those signs that fall
off are really the remainder. And the question is how
can you extract that? When you make a mask-- so we'll
have a mask and that mask is going to be 1 here, 1 here, 1 here, 0
here and then 0s everywhere else. So, we have all 0s down to the
last three and other one with 1. so, now if I just take M bitwise
added with B, whatever the value of those 3 bits is, they're
captured in that bitwise AND. So, this is a 0, 1M0 is 0. If this is 1, 1 and 1 is 1 so
that captures precisely the value of that remainder in
one hardware operation. So, this is remainder. [ Pause ] Now, whatever that remainder is, is the bit that we want
to look at in the bytes. And so, we have to look at that
remainder and then create another mask that looks like this, 1 and then--
and 0s everywhere else and we shift it to the left, the remainder amount. So that would be here. So that ends up. Putting the 1 there, shifted-- I'm sorry, that's only two times which
shifted three times or a bit here. So now that mask the bit what
in it with the original byte, picks out the very bit
we want to look at. Now, all of that is captured in-- well, first of all, the shift amount
is the index bitwise ended with 0x07 which 7 is-- 7 is the number that it--
it's this number right here, this 7, 1 plus 2 plus 4, right, is 7. So, this is 7. [ Pause ] And-- So that's what-- that 7 and we're
bitwise adding that with the index and that tells us the shift
amount, which in our example was 3. And so then we actually take one
and shift it left the shiftamount. And that gives us the mask
that isolates the bit for-- associated with that index value. So, ByteNumber and mask
do the following. ByteNumber tells us which
byte the bit is in. The mask isolates within
that byte which bit it is. And so, between the ByteNumber
and the mask, we can isolate on any bit
in the entire BitVector. [ Pause ] So, let's look at the-- how we'd implement the public
member functions of class BitVector. Here-- First of all, a constructor. You want that many bits. What we do is we make a byteArray,
we take the number of bits. If you have 7 and divide it by 8, that turns out to be the right
number of bytes that you need. [ Pause ] So, for example, if you wanted 24 bits, 24 plus 7 is 31 divided
by 8, you get 24 byte. If you wanted 25 bits, you need--
you're going to need an extra byte, put that next bit and 25 plus 7 is
going to be 32 divided by 8 is 4 such where you get your extra byte. And it goes up by-- like that. So, anyway, we-- that's
how many bytes we need. We allocate those bytes
with the [inaudible] in the allocated array
of the right size. This is just error checking. And then what we're going to do is
initialize all of those bits to be 0. And we do that by putting the
Ox00 which is the hexadecimal way of writing a byte for 0 bits. And we make everyone of the
bytes in the array equal to that hexadecimal value of zero. So, that initializes
every-- all the bits to 0. Assignment operator. This is just to review how assignment
operators always need to work. First, you check again self assignment
so you don't want to do anything if the incoming BitVector is
the one you are assigned to. You want-- There's nothing to do. If they're not the same
vector, then you check to see if they have the same byteArrayZize. And if they don't, then you've
got to delete your byteArray, reallocate your byteArray
into a new size, and again that's just
error checking on it. You can take that out on the
slide a few escalates the point. And then-- So, whether or not the
byteArraySize was the same when we get to this point, they are the same and
so we can just copy the bytes of bv, the incoming BitVector to the bytes
of this BitVector going by the time. And so, that's your copy constructor. And then the last thing of course
is return a reference to yourself. You return yourself in this kind of-- it looks like you're just
returning an object but because of the ampersand right up
here on the return type, you're really returning a
reference to that object. [ Pause ] OK. So, now we get to, you know,
let's solve the boilerplate that makes it a nice well-behaved class. How do we implement the operations? Well, here's set. What you do is you-- what you-- you want to set the 1 bit
associated with that index. So, remember you've got to first
figure out which byte it is and that's what the byte
number private function is for. So, we go-- We calculate the byte number
of that index, that's what that is. And then we use that as an
index into the byteArray. So that gives us the byte in the
byteArray that we need to look at. And then we want to set just that 1 bit. And so, we calculate
the mask of the index. Remember that's got a 1 in just
the right place and we order that 1 into the byte and that will have the
effect of making that 1 bit equal to 1. But in all the other 7 bits, they're
getting ordered with 0 and so, they don't-- thereby it doesn't change. [ Pause ] So again, that's a function call
to a private member function and that is a function call
to a private member function. Fortunately, those are very
efficiently implemented and that they require only one or two
hardware operations which is excellent. Because this is fundamental
to everything setting a bit. OK. Test, the way test is going
to work is we're going to return-- actually I think now in your library
that's going to be a bool value. But I have written here as an int value. In any case, you're going to test and
new ideas will return a 0 if the bit down there is 0 and return a
1 if the bit down there is 1. And bit is on a type MC++ so we
can't really return a bit value but we can return an integer
representation of that bit value. So, we return 0 or 1 the integer
to represent what we discovered about the bit way down there in the
machine that we can't touch directly. And how do we do that? Well, we first calculate the byte
number of that index coming in. And then we calculate the
mask, that index coming in, and we do a bitwise AND, OK. So the bitwise AND of that
mask with that byte will be-- will have to have 0 bits
everywhere the mask is 0. So, the only place it can have a
nonzero bit is where the mask has 1. And that will-- And to 1 only if the bit
we are interested in also has the value of 1 to produce a nonzero number. So, if this is a nonzero number on
the right, that's a nonzero number, that means the bit at that
particular index has the value of 1. And if it's a 0 number
then it has the value of 0. So we test whether or not
that thing is equal to 0. If it is equal to 0 then--
and not equal to 0, test fails and when we return 0 for false. Otherwise, if it's 1 then not equal to
is true and so we return a 1 or true to indicate the bit and the 1 there. So, it's a little bit of
slide of hand and well a lot if you count these two hard
works supported private methods. But, in the end, what you've done is
layout a collection of bytes and figure out a way to test [inaudible]
into the byte to figure out what the actual bit
value in that byte is. So, this is the fbitvect.cpp
test code that you have-- will have distributed later this
week and you'll see how that works. It's kind of menu display but basically
you just keep doing while the selection does not quit. And what you do is read the selection. If it has capital S, you
set without arguments. If it's capital U, you
can set without arguments. IF capital F, you flip
without arguments. A little s, you have to read
an index and set that index. Little u, you read an
index and unset that index. Little f, you read that
index and flip that index. And test, you test that index. Notice something here. [ Pause ] This is BitVector pointer 2. So, bvpointer is a pointer
to type BitVector. And I'm going to instantiate that
with a new BitVector of the size that is entered by the user. And notice the logic here. This program needs to know how big
a BitVector to make at runtime. And so, you can't hardwire into
the code to size for that BitVector because it has to be sized at
runtime but logical way to do is with operator new and not as a
standard statically declared variable. [ Pause ] >> So then we go in and
create a BitVector, manipulate at multiple different ways if
you-- we want to do a program, you know. I always kind of thinking-- think like,
OK, what's my problem, and then divide from there and [inaudible]
of that solution. >> All right. So, I just turned the record back on
and I realize now, I will say upfront, I realize that I had
the camera not pointing at the blackboard-- at
the whiteboard rather. So, you didn't see all
of my little scribbling about shifting back and forth. I will try to make that
up again and get it. I'm sorry to-- I have set it up
now because we had a question what in the heck can you do with BitVectors. Well, you're going to see one thing
you can do with BitVectors what-- when you do your homework four. But in the meantime, I thought I'd
show you another neat thing you can do with BitVectors, and that
is calculate prime numbers. And this is an algorithm called
the Sieve of Eratosthenes. The Sieve of Eratosthenes, that's
a great guy and this is there for-- as you can imagine, a very old
algorithm like 2,000 years old. And what Eratosthenes said was, "Well,
can I list out all the prime numbers between 0 and some highest number?" So, for example, those-- I want to
list all the prime numbers between 0 and 16 then-- Eratosthenes is
probably using rocks instead of bits or something like that. But the way where you bottle that is where you will make a
BitVector, what did I say, 16. So that would be 2 bytes worth a bit. So, that's 4 bits, 8
bits of 8 more bits. I believe that's 16 bits. I'm just going to put my index to at least significant
digit of index underneath. [ Pause ] You know, the right one, 12, 13, 14, 15. OK. I really need another
bit to get through 16, right? So, I'm going to have another bit there. My BitVector probably
allocates to buy you the time. We're going to have 7 more bits out here but I'm not going to
use them for anything. And of course, this come
to light, they are all 0s. So, anything that's empty up there,
think of it as having the 0 bit in it. So, what Eratosthenes
said was, "OK, first, I'm going to flip all the bits to 1." [ Pause ] Now, I know 0 is not prime
so I'm going to raise-- I'm going to set that back to 0. I know that 1 is not prime
and I know that 2 is prime. So, 2 is my first prime, let's
just getting started here. So, what I do is take
all the multiples of 2, and I know that those multiples
cannot be prime, so I'm going to-- all multiples of this prime number
2, I'm going to set back to 0. I just made a mistake, that's 3. So, 4 gets set back to 0, 6 back to 0,
8 back to 0, 10 back to 0, 12 back to 0, 14 back to 0 and 16 back to 0. Now, I go to the next number and
I'll say that it's got a 1 byte and that tells me that that's prime. So, 3 is prime, [inaudible]
has got a 1 bit, its bit is 1. So I'll take all the multiples
of 3 and flip those bits to 0. So, at 6 and at 9 and
at 12 and at 15, OK. So, 4 has got 0, it's not prime,
5 has got a 1 so it is prime, so that I can take the multiples
of 5 and flip those bits down to 0. So, it will be 10 and 15 and-- so then, you keep going 6 has 0 so it's
not prime, 7 has a 1 so it is prime and you'd flip all the multiples of 7. You can actually stop this when you
get to the point where the square of this number is bigger
than this number. But we can kind of stop now and go
and look and see that OK, 2 is prime, 3 is prime, 5 is prime, 7 is prime,
11 is prime, and 13 is prime. And those are the all the
prime numbers between 0 and 16. And we did that by just initializing
and then every time we have a 1 bit, you know, that's a prime and so you
look at all the multiples of that number and note that those are not going to be
primes and you flip the bits from that-- from 1 to 0 for those numbers--
or 7 to 0 for those numbers. I guess the word would be on 7 to 0. And that's the Sieve of
Eratosthenes algorithm that will use-- we use to calculate for our numbers. And that's actually another thing that you'll eventually
see in the FSU library. There's a prime number calculator that
actually uses the Sieve of Eratosthenes which actually uses that
as you BitVector class to calculate prime numbers. So that-- [ Pause ]