I have an assignment due tomorrow and I need to find the average of the floating point values in an array. I can't seem to find anything relatively useful in the book or in my notes about converting an integer into a float (the value of 5 in ecx (array length) to 5.0 so I can divide without truncation).
This is the code I was given, there are only two lines marked line1 and line2 that need to be changed but I can't seem to figure out what they need to change to. Any ideas on how to make this work?
c++ file
#include <stdio.h>
extern"C"
{
float average(float [], int); // external assembly function prototypes
float max(float [], int);
float min(float [], int);
}
int main()
{
const int SIZE = 5;
float floatArr[SIZE] = {2.2, 3.75, 1.11, 5.9, 4.64};
printf("The array contains the float numbers: ");
for (int i = 0; i<SIZE; i++)
printf("%f ", floatArr[i]);
float val1 = average(floatArr, SIZE);
printf("\n\nThe average of the floats are: %f\n", val1);
float val2 = max(floatArr, SIZE);
printf("The largest float is: %f\n", val2);
float val3 = min(floatArr, SIZE);
printf("The smallest float is: %f\n", val3);
return 0;
}
asm file
.686
.model flat
.code
_average PROC
push ebp ; save the caller frame pointer
mov ebp, esp
mov ebx, [ebp+8] ; address of first element in array
mov ecx, [ebp+12] ; store size of array in ecx
xor edx, edx ; counter for loop
fldz ; set top of FPU stack to zero
loopAdd:
fld dword ptr[ebx+edx*4] ; load next array onto register stack at st(1)
faddp ; add st(0) to st(1) and pop register stack
inc edx ; increment counter
cmp ecx, edx ; compare size of array in ecx with counter in edx
jg loopAdd ; if ecx > edx jump to loopAdd and continue
line1 cvtsi2sd eax, xmm0 ;load array size as float to compute average
line2 fdivp ;divide st(0) by st(1) and pop register stack
pop ebp ; restore caller frame pointer
ret ; content of st(0) is returned
_average ENDP
END
I decided to look at your code to come up with a solution. Don't use the xmm register. Those are SIMD instructions, and since the rest of your code is using the x87 FPU, I recommend continue using x87 FPU instructions.
It appears your code properly sums all the numbers in the array and leaves that sum in register st(0). You also have the number of items to divide by in ECX . So you need to divide st(0) by the integer value in ECX.
To accomplish this you must temporarily store the value of ECX in a temporary memory variable. This is because the FIDIV instruction doesn't take register operands. What FIDIV
will do is divide st(0) (top of FPU stack) and divide it by a 32-bit integer specified by a 32-bit memory location.
You would need to first add a .data
section to your function to hold the integer value (numitems):
.data
numitems DWORD 0
.code
Instead of what you were trying here:
line1 cvtsi2sd eax, xmm0 ;load array size as float to compute average
line2 fdivp ;divide st(0) by st(1) and pop register stack
Do this:
mov numitems, ecx ;Move ecx(# of items in array) to numitems variable
FIDIV numitems ;divide st(0) by value in numitems variable
;After division st(0) should contain the average
The code would look like this:
.686
.model flat
.code
_average PROC
.data
numitems DWORD 0
.code
push ebp ; save the caller frame pointer
mov ebp, esp
mov ebx, [ebp+8] ; address of first element in array
mov ecx, [ebp+12] ; store size of array in ecx
xor edx, edx ; counter for loop
fldz ; set top of FPU stack to zero
loopAdd:
fld dword ptr[ebx+edx*4] ; load next array onto register stack at st(1)
faddp ; add st(0) to st(1) and pop register stack
inc edx ; increment counter
cmp ecx, edx ; compare size of array in ecx with counter in edx
jg loopAdd ; if ecx > edx jump to loopAdd and continue
mov numitems, ecx ;Move ecx(# of items in array) to numitems variable
FIDIV numitems ;divide st(0) by value in numitems variable
;After division st(0) should contain the average
pop ebp ; restore caller frame pointer
ret ; content of st(0) is returned
_average ENDP
END
This function isn't re-entrant because it effectively uses a static variable numitems
to temporarily store ECX . One can get rid of this temporary static variable by placing the value on the stack temporarily and doing FIDIV
. The code for that eliminates the .data
section and uses the 4 bytes just below the current stack pointer long enough to do the FIDIV
and then the integer value is simply discarded.
.686
.model flat
.code
_average PROC
push ebp ; save the caller frame pointer
mov ebp, esp
mov ebx, [ebp+8] ; address of first element in array
mov ecx, [ebp+12] ; store size of array in ecx
xor edx, edx ; counter for loop
fldz ; set top of FPU stack to zero
loopAdd:
fld dword ptr[ebx+edx*4] ; load next array onto register stack at st(1)
faddp ; add st(0) to st(1) and pop register stack
inc edx ; increment counter
cmp ecx, edx ; compare size of array in ecx with counter in edx
jg loopAdd ; if ecx > edx jump to loopAdd and continue
mov [esp-4], ecx ;Move ecx(# of items in array) to temp location on stack
fidiv dword ptr [esp-4]
;divide st(0) by value in temporary stack location
;After division st(0) should contain the average
pop ebp ; restore caller frame pointer
ret ; content of st(0) is returned
_average ENDP
END
As an alternative, since ECX was passed in on the stack already at memory location EBP+12, the last example can be modified by removing all these lines
mov [esp-4], ecx ;Move ecx(# of items in array) to temp location on stack
fidiv dword ptr [esp-4]
;divide st(0) by value in temporary stack location
;After division st(0) should contain the average
And replacing it with this line:
fidiv dword ptr [ebp+12]
;divide st(0) by SIZE (2nd argument passed on stack)
;After division st(0) should contain the average