Даны массивы из n дробных чисел (float): A, B, C, D, E. Вычислить математическое выражение, используя команды SSE

Я столкнулась с проблемой в коде на ассемблере, который выполняет вычисления с массивами чисел с плавающей точкой, используя команды SSE. Код должен вычислять значения для массива E, используя элементы массивов A, B, C и D по следующей формуле: e=(a+b)/d + c*d

При выполнении программы результаты в массиве E оказываются некорректными. Например, вместо ожидаемых значений, я получаю:

E[0] = 512.000123
E[1] = 262144.063538
E[2] = 33554440.187500
E[3] = 805306499.562500
E[4] = 17179873412.000000
E[5] = 171798725768.000000

Ожидаемый вывод:

E[0] = 5.0
E[1] = 10.0
E[2] = 17.0
E[3] = 26.0
E[4] = 37.0
E[5] = 50.0

Код:

section .data
    n equ 6                         ; Количество элементов
    A dd 1.0, 2.0, 3.0, 4.0, 5.0, 6.0
    B dd 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
    C dd 2.0, 3.0, 4.0, 5.0, 6.0, 7.0
    D dd 2.0, 3.0, 4.0, 5.0, 6.0, 7.0
    E dd 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
    fmt db "E[%d] = %f", 10, 0      ; Формат для вывода

section .text
    global main
    extern printf

main:
    push ebp
    mov ebp, esp
    xor ebx, ebx                  ; Инициализация индекса

.loop:
    cmp ebx, n
    jge .end_loop

    ; Загружаем значения
    movss xmm0, [A + ebx * 4]      ; A[i]
    movss xmm1, [B + ebx * 4]      ; B[i]
    movss xmm2, [C + ebx * 4]      ; C[i]
    movss xmm3, [D + ebx * 4]      ; D[i]

    ; Вычисление E[i] = C[i] * D[i] + (A[i] + B[i]) / D[i]
    addss xmm0, xmm1               ; xmm0 = A[i] + B[i]

    ; Проверка на деление на ноль для D[i]
    movss xmm4, xmm3               ; Копируем D[i] в xmm4
    ucomiss xmm4, xmm4             ; Сравнение D[i] с самим собой
    je .skip_div                   ; Если D[i] == 0, пропускаем деление

    divss xmm0, xmm3               ; xmm0 = (A[i] + B[i]) / D[i]
    jmp .continue_calculation       ; Переход к продолжению вычисления
.skip_div:
    xorps xmm0, xmm0               ; Установить xmm0 в 0.0, если деление на ноль

.continue_calculation:
    mulss xmm2, xmm3               ; xmm2 = C[i] * D[i]
    addss xmm2, xmm0               ; xmm2 = C[i] * D[i] + (A[i] + B[i]) / D[i]

    ; Хранение результата в E
    movss [E + ebx * 4], xmm2       ; E[i] = Result

    ; Подготовка для printf
    push ebx                       ; Помещаем текущий индекс
    movss xmm0, [E + ebx * 4]      ; Загружаем значение из E для printf
    movss [esp], xmm0              ; Сохраняем его в памяти на верхушке стека
    push dword [esp]               ; Вторая запись в стек для float
    push ebx                       ; Помещаем текущий индекс снова
    push fmt                       ; Помещаем строку формата
    call printf                    ; Вызываем printf
    add esp, 12                    ; Очищаем стек (4 для float + 4 для индекса + 4 для строки формата)

    inc ebx                        ; Увеличиваем индекс
    jmp .loop                      ; Повторяем цикл

.end_loop:
    xor eax, eax                   ; Возвращаем 0
    mov esp, ebp                   ; Восстанавливаем указатель на стек
    pop ebp                        ; Восстанавливаем указатель на базу
    ret

Вопрос: Как исправить код, чтобы он правильно вычислял значения в массиве E?


Ответы (1 шт):

Автор решения: Вероника

Проблема решена использованием cvtss2sd (преобразованием в двойную точность):

section .data
    n equ 6                         ; Количество элементов (увеличено до 6)
    A dd 1.0, 2.0, 3.0, 4.0, 5.0, 6.0  ; Массив A
    B dd 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 ; Массив B
    C dd 2.0, 2.0, 2.0, 2.0, 2.0, 2.0 ; Массив C
    D dd 10.0, 10.0, 10.0, 10.0, 10.0, 10.0 ; Массив D
    E dd 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ; Массив E для хранения результата
    fmt db "E[%d] = %f", 10, 0     ; Формат для вывода
    div_by_zero db "Division by zero!", 10, 0 ; Сообщение об ошибке

section .text
    global main
    extern printf

main:
    push ebp
    mov ebp, esp
    xor ebx, ebx                  ; Инициализация индекса

.loop:
    cmp ebx, n                    ; Сравниваем индекс с n
    jge .end_loop                 ; Если индекс >= n, выходим из цикла

    ; Загружаем значения из массивов (single-precision)
    movss xmm0, [A + ebx * 4]     ; xmm0 = A[ebx]
    movss xmm1, [B + ebx * 4]     ; xmm1 = B[ebx]
    movss xmm2, [C + ebx * 4]     ; xmm2 = C[ebx]
    movss xmm3, [D + ebx * 4]     ; xmm3 = D[ebx]

    ; Проверка на деление на ноль
    ucomiss xmm3, xmm0            ; Сравниваем D[ebx] с 0
    je .div_zero_error            ; Если D[ebx] = 0, переходим к обработке ошибки

    ; (A[ebx] + B[ebx]) / D[ebx]
    addss xmm0, xmm1              ; xmm0 = A[ebx] + B[ebx]
    divss xmm0, xmm3              ; xmm0 = (A[ebx] + B[ebx]) / D[ebx]

    ; C[ebx] * D[ebx]
    mulss xmm2, xmm3              ; xmm2 = C[ebx] * D[ebx]

    ; Результат
    addss xmm0, xmm2              ; xmm0 = (A[ebx] + B[ebx]) / D[ebx] + C[ebx] * D[ebx]

    ; Сохраняем результат в E (single-precision)
    movss [E + ebx * 4], xmm0     ; E[ebx] = результат

    ; Convert single-precision to double-precision for printf
    cvtss2sd xmm0, xmm0           ; Преобразуем в двойную точность

    ; Подготовка к printf
    push ebx                      ; Индекс
    sub esp, 8                    ; Выделяем место для double на стеке
    movsd [esp], xmm0             ; Сохраняем double-precision float на стеке
    push dword [esp+4]            ; Пушим старшую часть double
    push dword [esp]              ; Пушим младшую часть double
    push ebx                      ; Индекс снова
    push fmt                      ; Формат строки
    call printf                   ; Вызов printf
    add esp, 16                   ; Очистка стека

    inc ebx                       ; Увеличиваем индекс
    jmp .loop                     ; Повторяем цикл

.div_zero_error:
    push div_by_zero
    call printf
    add esp, 4
    jmp .end_loop

.end_loop:
    xor eax, eax                  ; Возвращаем 0
    mov esp, ebp                  ; Восстанавливаем указатель стека
    pop ebp                       ; Восстанавливаем указатель базы
    ret
→ Ссылка