Search code examples
arraysassemblyx86masmx87

How do i find the average of a floating point array in MASM?


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

Solution

  • 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