 Hey guys, welcome back skits own lab two once again in this series We implement scientific computing programs from scratch in assembly no libraries no dependencies no compiler no linker very bare bones There's two kind of parallel paths One is the episodes where we actually implement the functions themselves and then in these lab videos We do live coding or I show you how to take those Functions integrate them together and make useful programs from scratch in like a live coding session The topic today is finding multiple roots of a function and it should be pretty quick and easy The ultimate question and answer to this video is find and plot all roots of sine of x between plus or minus 10 Here's the answer. We want to make a program that can solve that Give you the roots as well as make a nice pretty picture of sine of x and drop where the zeros are of that function So pretty simple. Here's the expected output Let's go ahead and do that There are four steps for this. I kind of broke them down into Parts as well as what examples what episodes cover these topics So you can go and copy and paste things from these examples to ultimately implement these steps first one is to count the roots which requires some basic 20-point math as well as a That's covered in the example 7 which was on fractions as well as some trig So obviously sine of x is a trig function. We covered that example 16 on trigonometry Step two was to allocate space for this vector that we're going to make and fill it with the roots so for that we'll have to use our heap which we implemented in example 11 and Our root finding techniques, which is from the previous episode episode 17 Finally, we want to print out that vector like we did in example 10 and also make a scatter plot which we covered examples 14 and 15 so Let's do that pretty quick intro. Let's get into the programming right away So if you get the suppository you download this get close whatever you have to do you will end up with Actually, you'll end up with this And when you run make bins you will get this binary folder Which will give you some functions that you can use including one function that enables you to Make binaries executable. We're not using a compiler or a linker. So we have to use our own DIY Function to do that. So this is what implements that All our code for these lab videos is in the lab directory. So if we go into the lab directory You can see I already implemented lab two, but we're going to implement it again By copying the template. So we're going to copy our template, which is just a bare bones assembly file to a lab two Directory let's go to lab two and let's show you what we got in here So we have just a assembly code listing a bare bones one as well as an An executable script basically so the way this works is Let me just show you So that shell script runs nasm on our code Includes some sys calls that are based on what os you're on linux or bsd And then it takes that makes it executable sets that you know to work and then executes the binary This is a very quick like test shell script. Let's just Compile and run our program pretty quick Then in that code assembly listing, it's a bare bones file. All it has is a bare bones elf header with an exit command And accommodations accommodations for a print buffer, which is usually required. So if I run this nothing happens We return to zero if you check So it's a very simple program and it's the the minimal program I would say so Let's get into it. So the first thing you want to do is implement the The first step. So what was the first step? Let's go back and see Count the number of roots. So what is the algorithm for this? Let me Let me paste in the algorithm that I'm going to be using just like a as a comment Here's the algorithm. I want to use I mentioned this last time Basically, what we do is to find roots to find the find multiple roots We start at a lower bound. So I want to do it start at a lower bound record the sign of that location increment by some step size until you change sign and Count the number of sign changes that you have until you hit the upper bound and that's how many roots you have Ideally between those boundaries. So let's implement that the first thing you have to do though is actually include the sign function That's the goal, right? So let's grab the sign function This is all copy and paste. So let's go to the example on trigonometry Take the sign function right here This is the include for the sign function. Let's just snag that If I include this in my program, I will now have access to the sign function boom easy Now um, I want to wrap that because you'll see that I don't want to have to keep Working with this this in this function requires two inputs. It requires a x value for sign of x. It also requires the tolerance of the Taylor series approximation. So We have to give both those things. So let's make a wrapper function for sign of x We'll call it funk and what I want it to be like is I want it to be a function that returns a double in x and m zero And it will take you in a double in x and m zero And it will basically would be a wrapper for sign So how does this work? Well, what we're going to call sign of x and we're going to return but This takes input. So it takes an input. Let's just do this in x and m zero, but That input is already passed in x and m zero. So we can ignore this line completely instead You need to pass the tolerance. So where's the tolerance? Well, I don't know where the tolerance is. Let's make a tolerance At address tolerance, let's define a tolerance called Tolerance and it will be a zero point zero zero one. How's that sound? Now This function is only supposed to affect x and m zero not any of any other registers. So let's preserve those So let's subtract from let me check my Pre-made just make sure I do it right um, so the track from rsp the number of bytes in the x from register 16 and then we'll move into the stack location zero offset the value in x and m one And then when we're done, we will undo that. So we'll move Into x and m one the value from the stack and then we will add 16 back to the stack to rely on this pre reposition the stack on the right spot And yeah, that shouldn't be it. This is our wrapper function for sign of x Very nice Now the algorithm. How does this algorithm work? Well, let's think about this We'll need a A loop. So let's make a loop of address or whatever a label called count roots A loop and in this loop we'll try to Include this algorithm. So let's make some stuff in the beginning. Let's uh, first of let's move that function into rdi So we can access it In rdi as i was having to keep calling this wrapper function we can just call the register rdi instead. It makes it a little bit easier um And then let's make a variable or register for counting. So let's use rcx as a counter That's i'm not going to put the comments here at the waste of time. Um, and uh, we will move into x and m three let's use that to track our x value across the function the lower bound So what's this loop going to be basically we're going to increment by this step checking the sign as we go until we change signs and that will be A root so let's let's do that. So let's use rbx as a flag So this is like a sign flag um zero for positive one for negative And we'll keep tracking that until it's changed. I would see wasting 63 bits here. Um, but fine. Who cares not me So what we'll do is we will move into x and m zero the the value of x and x and m three We're going to call rdi which is the wrapper function for sign And now let's compare That value so you know now x and m zero contains sign of x Compare that against the value of zero, but what is zero? We have to define zero also have to find our bounds Let's define all that stuff right now. So what are the bounds? so let's say lower bounds dq negative 10 You have to put point zero to make it a float. Um upper bound Dq 10 uh, say step Dq I don't know 0.01 has that sound and then zero to compare against there are other ways to Check the sign of things, but this is the easiest in my opinion So we'll dq 0.0 that happens to just be all zeros, but it's okay. We'll define a value for that So now we're going to compare that against x and m zero and the idea is If we are above or equal to that zero then we're positive. So we'll say sign compared So we'll leave rbx at zero because it's a positive number. Otherwise, we're going to increment rbx Which makes it negative So that's fine The caveat here is that we have to compare against the previous value of rbx So we have to have that established. So let's just say that's in rdx Just say that for now. We'll add that in later. So we're going to compare rbx against rdx And if they are equal That means there's no roots otherwise There was a root found in which case We're going to increment rcx. That means we're going to count that as a root if the sign has changed Number of roots has increased by one. So increment our rcx and now if there's no root found Let's well either way we're going to save the previous the current value of rbx in rdx. So move into rdx the value in rbx and Now we're going to just increment our rx value. So we're going to add the step size to xm3. So Add step to xm3 and then we're going to compare If that has exceeded the um upper bound If it has then um We stop so we'll say jump the lower equal to count roots loop Now this should work There's one caveat here is that what about in the first step It turns out the way this is written in the first step if design is negative It will count that as a as a root. So we have to basically define rdx going into this How we're going to do that is actually pretty simple. We're basically going to take all this logic outside the loop um Yeah, basically like all this logic here outside the loop and Have like a condition when we go in that rdx is already set up and we'll jump into the middle of that loop So i'll show you how that works how that works So um Let's do that xm0 to 3 call rdi fine compare again zero. So now um, we will say In knit sign compared No, no screw that. Let's just jump right in to the middle of the Of the function. We're just going to jump into here. No root found Because yeah, that's fine Jump no roots So this does basically is this Enables us to start our Our loop and immediately jump into Here meaning we won't be able to detect our root in the first step of our bound But it's okay because we'll be able to detect some We won't miscount the number of Roots by one assuming that input was negative at that first location. So This will enable us to do that. Hopefully this will count the roots for us. So now at the end of this rcx Contains number of roots How can we check that? Well, I could add a bunch of includes here to print out decimal numbers We can do that or You know big brain, how will we just return that as a function as the as the program return value? So instead of this Let's move into and you could do di l cl or you can do rdi rcx. This is simpler. So let's just do this This is that some bytes um, and then Run this Oops, what happened? I don't oh, I don't have the executables. Let's go back It's uh make make all the bins now. I have that bin directory Now if I run this still what happened? No binary hold on Oh, it just failed to something was wrong. Um, oh operand. So what did I do wrong line 82? Let's check 82 82 Oh, I'm done. I have to add 16. It's so stupid. I'm an idiot Now let's echo the return value seven. So are there seven roots between negative 10 and 10? Let's see One two three four five six. Yes, there are seven roots. So we did that right Now let me go back This is done count the roots is done. What is the next step? To allocate space for and make a vector for those roots. Okay How to do that? Well, first we need a heap. So let's copy some stuff for a heap Let us go back in the code and and begin this So the first thing in a heap is let's go to see what a heap even is I have a example on that memory allocation example 11. I look in free code Let us see first. We have to have a heap size defined We'll do that that also feeds into the the size of the segment in memory. So we have to add that to this A bunch of includes are required. Let's just snag these three includes To make a heap to free a heap and to free heap Chunks and allocate for heap chunks. And then we also need to set the bottom which is Definition of the heap star address. So we'll talk about that right now. First. Let's paste in that include Before I forget right here And actually let's get rid of this one on heap free only normies free memory. We don't do that around here um Now heap size. Let's define a heap size. Let's do the same size as the print buffer 4k Why not plenty of plenty around on this computer? Keep size 4096. Let's add that to our segment This is in the um When it's loaded, but it's not in the binary. So that's good. Doesn't waste 4k of memory on our hard drive plus heap size And uh, we also need that at the very bottom It doesn't have to be at the bottom. It's just a macro. So uh, whatever. It's like a I'm not sure what that is even called. It's like a Assembler directive For nasm. It defines this in there It could be anywhere in the code, but I put it down here because that's where the heap is located You can see here the heap is defined to be after the print buffer. So I put the heap definition down here So, yeah, that makes a heap. Let us return this to what it was before before I forget I don't like returning values that are non-zero. I don't have to um, and Yeah, so now we have a heap now. Let us Initialize the heap, right? That was the first thing to do if I recall correctly. Let me check the example It was to Initialize the heap like this and then we had to allocate like this. Let's copy this into our program What am I doing? I'm done Put it right here initialize the heap We don't want to print anything out. Don't need that. Don't need that. Don't need that Um, so we have to put the number of bytes in rdi. So let's take them from rcx Multiply by eight or shift it right shift it left. Sorry by Three so shift rdi by three This will allocate eight times rcx bytes And let's save that In a register. So we'll move into let's do r15 the value in rx Let's do it in two spots. Let's do it two spots because I want to be able to increment through this So I'm going to save it in r15 and r19 and r9. So we will say um save address to Uh eight by rcx by vector four Roots in r9 And r15 Copy the same thing Again But this time I want to save it and this is going to sound dumb. I want to save an empty one For the zeros later on the actual value of zero. So we're going to say save it in r14 another Seven times eight byte chunk. So like this and the beauty of this is Basically, we don't have to redo this math right here. This move rdi rcx Multiply by eight We can ignore that because our calling convention is so nice that these function calls heap alloc and whatever They don't affect any registers that aren't return values And so believe it or not rdi is the same value as it was way back here Even though we've called this function already rdi is unaffected and I can guarantee that for all the functions that we have in the In the code base. So this will allocate another eight by rcx bytes for the roots and it will save the address in r14 great Now That gives us space Now we have to actually populate that Vector with values, right? We've got the space for it Now you have to actually find the roots and populate the the vector. So let's do that How do we do that? Well Ultimately, it's the exactly the same logic as this I'm going to snag this entire thing And just change a few things here and there Copy that entire, you know 20 lines dump it here But instead this time i'm not counting roots. I'm finding roots. So I don't care about rcx at all Get rid of all everything to do with rcx deletes And instead of where we would have incremented rcx to count a root now. I want to just find the root So let's just find the root. So how do we do that? We need the bisection method So let's go to our example 17 That was the one that covered the root finding algorithms including the bisection method And let's just snag the include really quick. Here's the include for the method copy Let's go back in And paste This takes some inputs. What are they? Who knows? Let's just copy the actual implementation here right there. Bam copy here Use bisection method to compute To find All this is the same except for a couple things. So the tolerance is not in dot tolerance. It's in tolerance Let's use the same tolerance as the one that we used for the sine function the actual Taylor series tolerance Let's use the same value. Who who cares now the bounds of the method are not the same bounds as our total Search it's just the local bounds, right? We've just changed signs Where the previous value was negative the current value is positive. There's a root in the middle somewhere Let's find the root between those two things. And so the upper bound is actually x and m3. That's the current value Of x and the previous value. Let's just say that's an x of m1 And we'll implement that later and the last thing is rdi. We've already set who needs that line of code not me So We have to save x of m1. So let's do that So we will move the previous value of x of m3 into x of m1 right here Before we increment that now one last thing. I just copied that entire set of code It's gonna break. I can guarantee it's gonna break. Why because it's the same labels So let's change all the labels to be twos this way. It's a new A new set of addresses. So let's change all these to two And uh, that will hopefully prevent any error messages from popping up Okay So now at this point, um x and m0 So Should contain the roots Now you have to save the root and remember we saved that address to the first Root that vector was in both r9 and r15. We're going to use r9 So I'm going to say move sd into r9 The value in x and m0 And then I'm going to add r9 comma 8 so now we'll point to the next location in memory and uh, yeah, that should be just fine. Okay, great Um, so once we're done here the vector of Roots should be populated How can we guarantee that? How can we check that? Well, let's print it out So how do we print out matrices? Who knows not me, but I do know we did it before so let's go to our matrix basics example You can look at the names to guess where it would be And I'm sure in here somewhere we printed out a an array of floats. Here's the function Print array floats. Let's copy that and let's paste it into our includes Right here and the beauty of this function is that doesn't just print floats But it also includes All the functions required to help print floats all dependencies are in that tree So there's anything else don't need to have print buffer or print characters any that stuff It's all dependency of this function. And so we can just come down here And print it out. How do we print out of how do we print out a matrix? I have no idea. Let's just check We did this copy Paste like I said before programming is all copy and paste. So let's just copy and paste this print roots Print the standard out. Where is the first address that's in r15 How many rows there is one row? How many columns there's rcx columns? We can change that rcx columns r8 print float five six figs fine Last thing you have to do to print out stuff. You have to flush the print buffer. So we'll say call print buffer flush and leave the code This should work. Let's see what happens in my break It did not break. So here you can see You run it again. Here you can see one two three four five six seven roots between negative 10 and 10. Is that correct? Does it match our expectation? It does match Great Now we've done the first step the second step and the third step Last step is to make the scatter plot. How does that work? Like so before I have no idea, but I do know we did it before We had a a whole example on scatter plots example 14 Example c was evaluating functions. Let's go in there And let's see what we had to do to print out a scatter plot So We had a heap. We have to open and close the file. We have to run scatter plot We had these two things here evaluate parameters and linear space. They were functions that basically enabled us to Evaluate sign from negative 10 to 10 The whole length of that range. So let's let's grab that Don't need these we already have these two functions for the heap. So let's just copy these functions into our includes Really quick up here File open file close scatter plot Sure And how do we use those functions beats me? Copy and paste so it is Let's copy that entire program into here So how did we do this? Oh, we had some wrapper function I'll talk about this in a minute and how did we do this? We had to keep initialized We allocated some arrays for the X and Y data for the sign function We've put a linear spacing in the X. We evaluated into the Y Open the file or in the scatter plot close the file. Okay, great. Let's paste that in Boom done program done. Isn't that easy? What else? Well, this stuff calls a bunch of garbage um We will We will change some of the stuff. So the first thing is this is all fine 101 values is fine. Who cares? That's enough for me. Um X param will put that in a second Y param fine This is the linear spacing for the X parameter. Let's not use random numbers. Let's use the actual numbers. Let's use lower bound and upper bound lower bound upper bound and then the oops The function Let's make a new one. This is a special function. I'll talk about that in a second. Let's call it param Funk. We'll do that at the top in a minute. What else? This is all fine. That's all fine Let's also while we're at it, let's paste in the rest of the Stuff you can see here. We have these data structures called plot structure file name, you know parameters all these different Labels that have nothing in the in the code. Let's paste those in. So let's do that Take this Copy everything Without regard for what it even is copy copy copy Paste paste paste This will plot something. I don't know why it's plotting but it's plotting something here the names of stuff Here's some inputs. Let's change these things who needs this garbage 2 3 negative 2 not for us Um, these are the parameters. That's fine 101 values. That's fine. Um File name let's change that to be something else. Let's change that to be sign roots change the title instead of that polynomial. Let's change the title to be um Roots of sign of x x label x y label y Let's change quickly the the the Minute and max for the scatter plot just so it plots nicely Let's say negative 12 to 12. Let's say y values negative 3 to 2 because it's it's sign right that fits between negative 2 and 2 We'll leave all this for now. We'll change this in a minute. Who cares about all this stuff the data. Um Let's leave all this. I'm just gonna turn off the the marker size And the bit for the marker you can see here these this byte is a bunch of flags bit zero is the markers bit one is the lines The rest of bits are for other stuff. I'm gonna turn off the marker. So I'll say db zero x zero two Okay, now comes for the definition of that function You can see here. We have that function right here param funk This function is basically a function that Is like what we have already it's it's this sign function here this wrapper, but it can't Effect any registers and it takes all its inputs in order from the stack. So let me show you We called it param funk and it's uh It took and And returned a value in xm, but it has to be an rsp plus eight Um, because it's a single value and the reason why this is the case is so we can potentially have functions of multiple Multiple parameters, you know a multi-dimensional design space and have multiple return values And so we use the stack as opposed to registers to handle that because you only have 16 registers. So if you use the stack you can have infinite number of uh Inputs so we'll say double Rsp plus eight So it's the same function the difference is you have to not affect any registers And you have to take your input from rsp eight. So how does that work? Well, copy that Um, we'll say zero we'll say one we'll add this to 16. Oops Don't want any typos 16 I mean that is zero Um, we have to take From xm to xm zero from rsp plus eight. Let me just quickly check to make sure that's the right move I always mess up my move instructions That is the right move instruction But it says rsp plus eight, but we just added 32 things to the stack So it's actually 32 plus eight or 40 sounds good to me Lastly, you have to put that back onto the stack and so We will move just that single quad word to rsp plus 40 xm zero Is that correct? Let me check Yeah, that's right to me Um, so yeah, hold on. Let me change this up here to param funk Not that it matters. It's just a comment um And this should work Let's see. Let's see what happens. I'm gonna run this. See what happens. Probably gonna have an error. Let's see No error. We got a sine roots dot svg If I open that Sine roots svg Here it is. Here is a plot of sine of x between minus 10 and 10 There's some weird stuff going on with this The reason why it's doing that is because it's trying to subtract two-thirds from two-thirds is getting very close to zero But not quite so we'll change that in a second. We'll make that look nicer um I might change like the number of grid lines and stuff do that in a minute Let's first put the actual dots where the zeros are we already have this saved So just a matter of Implementing that in the scatter plot. So let's do that really quick. Let me duplicate this and uh go back So we have the suppository let's go back in the code Now this last um Structure we have called data. The first entry is it's actually a linked list This entry points to the next data set in the linked list. So let's make another one. We'll call it roots And let's create the roots data structure Gonna be a copy and paste of this one The story of my life This will be the last entry in the linked list. We'll call it No pointer there. This one is going to be a little bit different. It's not going to have any Lines just just going to have a marker. So we'll say line size zero marker size 10, I don't know Um, let's make it no line color. Let's make it green just just for fun sake Let's put the flag on just for the marker and Number of elements is not 101. It's actually going to be seven, but we don't know that If we change our function, it might change the number of elements. So let's not put a number in here Let's populate this so this function it needs to contain an entry you can see here at 16 That's going to be the address of that Memory that we allocated for the roots. So all the x values At offset 16 a quad word offset 26 another quad word for the y value. It's going to be all zeros um, and then in 36 double words So half the size so four bytes for Number of elements so that'll be seven, but we have to put that in there automatically. So let's do that really quick How are we going to do that? Well, let's do that Right before we print. Where's our first print right here? So let's populate that there structure with stuff. So Um, we're going to move into I believe what was called roots 16 was the offset And it was the address of the x value. So that was an r15 the y values was in r 14 and that was the 26 and then the number of elements was 36 and that was supposed to be the Number of elements and that was in r6, but it was a double word So half the size and you could do that in different ways with assembly. I just like to say move the Four byte version that register into that slot. That's just the easiest way in my opinion. So this is basically to populate the roots data set structure Okay, let's see what happens. I run this No error messages. That's insane All right, um Let's open it up. What's looked like refresh this page Boom, it was just a pretty big green dots. I'm not sure I like that big. Um Let me go through and change the formatting of this one to match more or less what I had in the example In in this slide, but we'll keep green dots. I like green. Let's just leave that as green So let's go back to the code And let's just check the the formatting of what I had in the example So in the example, I had a little bit bigger plot. I believe right it was less zooming required. Yeah, it was 800 by 400 oops did not type 800 by 400 plot margins was 5 12 12 2 2 0 0 0 f 0 0 I had 9 by 3 that makes it easier because You're dividing into two groups. So that makes it easier to um subtract. So we'll have that 0 0 0 0 thing Um subdivisions per tick. What do I add? 2 in 2 really Fine 2 2 2 2 What's this 32 5 let me see scroll down Font size for the labels was 24. Everything's obviously twice as big basically 16 for this one. Um 5 5 Okay, it was double thickness for the bagers one for the minor stroke thickness. Um on the grid What were my uh tick margins? It was 60 and 40 The flags was 1 and f Um Let's see how that looks. I'm down to run it. Oh, let's make this one a little bit bigger this line thickness Okay, so we're gonna make everything bigger. So If I run this how does it look now? Run the code. I still have that same svg. Let's open that in firefox Zoom out a little bit boom. That is our roots of sine of x. So we found all the roots of sine of x The plot looks very pretty. I like that So, yeah, the we're done. We finished everything. We've checked off all the boxes. Um We've kind of the roots Made some space for made a vector of the roots print out the vector and also be the scatter plot. So We integrated like I don't know one two three four five six different examples You could have done more. You could have made a nice, um report with all this from example 15 You could have done so much stuff. So I just want to show that it's it's actually super easy How long was this video this video is only 38 minutes long and we've already been able to implement this entire program from scratch Just by copying and pasting stuff from the skitzone code base. So if that's cool to you you have any questions Come to discord. We have a server going. Um ask your question and we could hang out with that. I'm done I want to thank you for watching and I'll see you in the next video. All right. See you