Сравнение чисел с плавающей точкой

Последнее обновление: 19.10.2023

Для сравнения отдельных чисел с плавающей точкой расширения SSE предоставляют набор инструкций сравнения для конкретных условий, которые сохраняют значение true (все биты 1) или false (все биты 0) во втором операнде:

<
cmpss xmmn, xmmm/mem32, imm8
cmpsd xmmn, xmmm/mem64, imm8

cmpeqss xmmn, xmmm/mem32       ; операнды равны
cmpltss xmmn, xmmm/mem32       ; первый операнд меньше второго
cmpless xmmn, xmmm/mem32       ; первый операнд меньше или равен второму
cmpunordss xmmn, xmmm/mem32    ; неупорядоченное сравнение (один из операндов или оба равны NaN)
cmpneqss xmmn, xmmm/mem32      ; операнды не равны
cmpnltss xmmn, xmmm/mem32      ; первый операнд НЕ меньше второго
cmpnless xmmn, xmmm/mem32      ; первый операнд НЕ меньше или равен второму
cmpordss xmmn, xmmm/mem32      ; упорядоченное сравнение
cmpeqsd xmmn, xmmm/mem64       ; операнды равны
cmpltsd xmmn, xmmm/mem64       ; первый операнд меньше второго
cmplesd xmmn, xmmm/mem64       ; первый операнд меньше или равен второму
cmpunordsd xmmn, xmmm/mem64    ; неупорядоченное сравнение (один из операндов или оба равны NaN)
cmpneqsd xmmn, xmmm/mem64      ; операнды не равны
cmpnltsd xmmn, xmmm/mem64      ; первый операнд НЕ меньше второго
cmpnlesd xmmn, xmmm/mem64      ; первый операнд НЕ меньше или равен второму
cmpordsd xmmn, xmmm/mem64      ; упорядоченное сравнение

Первые две инструкции могут в качестве третьего операнда (imm8) принимать значение от 0 до 7, которое указывает на принцип сравнения:

  • 0: Первый операнд == второй операнд

  • 1: Первый операнд < второй операнд

  • 2: Первый операнд <= второй операнд

  • 3: Неупорядоченное сравнение

  • 4: Первый операнд ≠ второй операнд

  • 5: Первый операнд не меньше второго операнда (>=)

  • 6: Первый операнд не меньше или равен второму операнду (>)

  • 7: Упорядоченное сравнение

Эти инструкции устанавливают в 0 (неверно, ложь) или 1 (верно, истина) все биты в первом операнде. После сравнения значение из регистра XMM можно поместит в регистр общего назначения и проверить этот регистр на наличие нуля/не нуля. Для этого можно использовать инструкции movq или movd. Например, возьмем следующую программу на Linux:

global _start

section .data
num0 dq 3.4
num1 dq 3.4

section .text
_start:
    movsd xmm0, [num0]       ; помещаем в xmm0 число num0
    movsd xmm1, [num1]       ; помещаем в xmm1 число num1

    cmpeqsd xmm0, xmm1      ; проверяем, равны ли операнды
    movq rdi, xmm0          ; для проверки помещаем результат сравнения в rdi

    mov rax, 60
    syscall

Здесь помещаем в XMM0 и XMM1 два числа .double и инструкцией cmpeqsd сравниваем их на равенство. Если они равны, то в xmm0 все биты будут равны 1, если нет - то 0. Для проверки значения помещаем его в регистр rdi. Поскольку при равенстве все биты будут установлены в 1, то фактически в десятичной системе это будет число -1.

Аналогичная программа на Windows:

global _start

section .data
num0 dq 3.4
num1 dq 3.4

section .text
_start:
    movsd xmm0, [rel num0]       ; помещаем в xmm0 число num0
    movsd xmm1, [rel num1]       ; помещаем в xmm1 число num1

    cmpeqsd xmm0, xmm1      ; проверяем, равны ли операнды
    movq rax, xmm0          ; для проверки помещаем результат сравнения в rax
    ret

Получив результат проверки в целочисленный регистр, мы можем использовать уже стандартные инструкции перехода, чтобы определить условные конструкции. Так, возьмем следующую программу на Linux:

global _start

section .data
num0 dq 3.6
num1 dq 3.5
str_equal db "Equal", 10, 0
str_equal_len equ $ - str_equal
str_notequal db "Not equal", 10, 0
str_notequal_len equ $ - str_notequal

section .text
_start:
    movsd xmm0, [num0]       ; помещаем в xmm0 число num0
    movsd xmm1, [num1]       ; помещаем в xmm1 число num1
    
    cmpeqsd xmm0, xmm1      ; проверяем, равны ли операнды
    movq rdi, xmm0

    test rdi, rdi         ; проверяем на равенство - на единицы
    jnz equal             ; если xmm0 == xmm1
    
    mov rsi, str_notequal   ; адрес строки
    mov rdx, str_notequal_len ; размер строки
    jmp exit
equal:
    mov rsi, str_equal  ; адрес строки
    mov rdx, str_equal_len ; размер строки
exit:
    mov rax, 1           ; номер системной функци
    mov rdi, 1           ; дескриптор стандартного (консольного) вывода
    syscall

    mov rax, 60
    syscall

Здесь по сравнению с предыдущим примером добавили проверку на 0 с помощью инструкции test - если результат этой инструкии равен 0, то будет установлен флаг нуля ZF, который мы можем проверить с помощью инструкции jnz - если флаг не установлен (то есть по сути если xmm0 и xmm1 были равны), то далее выполняем переход на метку equal. И в зависимости от результата выводим на консоль то или иное сообщение.

Аналогичная программа на Windows:

global _start

extern WriteFile        ; подключем функцию WriteFile
extern GetStdHandle     ; подключем функцию GetStdHandle

section .data
num0 dq 3.6
num1 dq 3.5
str_equal db "Equal", 10, 0
str_equal_len equ $ - str_equal
str_notequal db "Not equal", 10, 0
str_notequal_len equ $ - str_notequal

section .text
_start:
    sub  rsp, 40   ; Для параметров функций WriteFile и GetStdHandle резервируем 40 байт (5 параметров по 8 байт)

    movsd xmm0, [rel num0]       ; помещаем в xmm0 число num0
    movsd xmm1, [rel num1]       ; помещаем в xmm1 число num1
    
    cmpeqsd xmm0, xmm1      ; проверяем, равны ли операнды
    movq rdi, xmm0

    test rdi, rdi         ; проверяем на равенство - на единицы
    jnz equal             ; если xmm0 == xmm1
    
    mov rdx, str_notequal   ; адрес строки
    mov r8d, str_notequal_len ; размер строки
    jmp exit
equal:
    mov rdx, str_equal  ; адрес строки
    mov r8d, str_equal_len ; размер строки
exit:   ; выводим строку на консоль
    mov  rcx, -11  ; Аргумент для GetStdHandle - STD_OUTPUT
    call GetStdHandle ; вызываем функцию GetStdHandle
    mov  rcx, rax     ; Первый параметр WriteFile - в регистр RCX помещаем дескриптор файла - консоли
    xor  r9, r9       ; Четвертый параметр WriteFile - адрес для получения записанных байтов
    mov  qword [rsp + 32], 0  ; Пятый параметр WriteFile
    call WriteFile ; вызываем функцию WriteFile
    add  rsp, 40
    ret
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850