Большинство инструкций, которые работают с числами с плавающей точкой, никак не обновляют условные флаги. Но есть специальная инструкция FCMP, которая сравнивает числа с плавающей точкой и обновляет соответсвующим образом условные флаги. Эта инструкция имеет следующие формы:
FCMP Hd, Hm FCMP Hd, #0.0 FCMP Sd, Sm FCMP Sd, #0.0 FCMP Dd, Dm FCMP Dd, #0.0
Эта инструкция может сравнивать два 16-разрядных регистра H, два 32-разрядных регистра S, два 64-разрядных регистра D. Она также может сравнивать значение регистра с конкретным числом. В итоге инструкция FCMP вычитает операнды и обновляет флаги.
Например, сравним два числа
// Пример программы, которая сравнивает два числа с плавающей точкой .global _start _start: LDR X4, =number1 LDP S0, S1, [X4], #8 // загружаем числа number1 и number2 в S0 и S0 FCMP S0, S1 // сравниваем числа в S0 и S1 B.GE else // если S0 больше чем S1, переход к метке else LDR X1, =less // строка для вывода, если S0 меньше чем S1 MOV X2, #16 // длина строки B endif // переход к метке endif для завершения условной конструкции else: // если S0 меньше чем S1 LDR X1, =greater // строка для вывода, если S0 больше чем S1 MOV X2, #19 // длина строки endif: // вывод сообщения на консоль MOV X0, #1 // 1 = StdOut - поток вывода MOV X8, #64 // устанавливаем функцию Linux для вывода строки SVC 0 // вызываем функцию MOV X0, #0 // код возврата из функции - 0 MOV X8, #93 // номер функции Linux для выхода из программы - 93 SVC 0 // вызываем функцию и выходим из программы .data number1: .single 2.08 number2: .single 0.16 greater: .asciz "S0 greater than S1\n" less: .asciz "S0 less than S1\n"
Здесь загружаем в регистр S0 число number1, а в регистр S1 - число number2 и сравниваем их. В зависимости от сравнения переходим к определенной метке. Поскольку в данном случае number1 больше чем number2, то на консоль будет выведена строка
S0 greater than S1
Но стоит отметить, что сравнение двух чисел с плавающей точкой на равенство, поскольку нередко два числа близки по значению, но не полностью равны. Например, 1 доллар и 1 цент равны 1 доллару или нет? В
разных ситуациях ответ может отличаться. Стандартным решением в этом случае является установка некоторого допустимого отклонения. И если разница между числа меньше этого этого отклонения, то числа считаются равными. Например,
определим допустимое отклонение e = 0.000001
. Тогда числа в двух регистрах считаются равными, если
abs(S1 - S2) < e
Здесь abs()
- это функция для вычисления абсолютного значения.
Сначала посмотрим на возможную проблему. Например, первое число равно 1.0, а второе равно 0.0. Математически если ко второму числу сто раз прибавить 0.01, то мы можем получить
первое число, то есть 0.01 * 100 = 1.0
. Но это математически, посмотрим как все это будет выглядеть в программе на ассемблере:
.global main main: STR LR, [SP, #-16]! LDR X0, =increment // загружаем указатель на число increment LDR S0, [X0], #4 // загружаем в S0 число increment (0.01) LDR S1, [X0], #4 // загружаем в S1 число number1 (0.0) LDR S2, [X0] // загружаем в S2 число number2 (1.0) MOV W1, #100 loop: FADD S1, S1, S0 // сто раз прибавляем S0 (0.01) к S1(0.0) SUBS W1, W1, #1 // уменьшаем счетчик цикла B.NE loop // если не достигли 100, переходим обратно к loop // в этой точке программы ожидаем, что S1 равно 1.0 FCMP S1, S2 // сравниваем S1 и S2 B.EQ equal // если числа равны, переходим к метке equal LDR X0, =notequalstr // если не равны, загружаем строку notequalstr B next equal: LDR X0, =equalstr // если равны, загружаем строку equalstr next: BL printf // вывод на экран с помощью функции printf MOV X0, #0 // код возврата из функции LDR LR, [SP], #16 RET .data increment: .single 0.01 number1: .single 0.0 number2: .single 1.0 equalstr: .asciz "S1 and S2 are equal\n" notequalstr: .asciz "S1 and S2 are NOT equal\n"
Здесь в регистр S1 загружается число number1 (0.0), и затем к нему в цикле 100 раз прибавляется число из S0 (то есть increment или 0.001).
MOV W1, #100 loop: FADD S1, S1, S0 // сто раз прибавляем S0 (0.01) к S1(0.0) SUBS W1, W1, #1 // уменьшаем счетчик цикла B.NE loop // если не достигли 100, переходим обратно к loop // в этой точке программы ожидаем, что S1 равно 1.0 FCMP S1, S2 // сравниваем S1 и S2
То есть в конце мы ожидаем, что в S1 будет число 1.0. И затем это число сравниваем с числом из регистра S1 (number1), которое изначально равно 1.0. То есть ожидаем, что числа в S2 и S1 будут равны.
Для упрощения вывода на консоль здесь применяется функция printf
, соответственно для компиляции применяется компилятор gcc.
Однако, если мы запустим программу на выполнение, то увидим, что числа не равны:
S1 and S2 are NOT equal
Посмотрим детально, что у нас будет в регистрах после стократного сложения:
.macro printRegister reg // макрос для вывода содержимого регистра STR LR, [SP, #-16]! STP D0, D1, [SP, #-16]! STP D2, D3, [SP, #-16]! FCVT D0, S\reg // преобразуем single в double FMOV X2, D0 // помещаем число из D0 для вывода на консоль MOV X1, #\reg // помещаем номер регистра для вывода на консоль ADD X1, X1, #'0' // для установки символа для спецификатора %c LDR X0, =printStr // строка форматирования BL printf // вызываем функцию printf LDP D2, D3, [SP], #16 LDP D0, D1, [SP], #16 LDR LR, [SP], #16 .endm .global main main: STR LR, [SP, #-16]! LDR X0, =increment // загружаем указатель на число increment LDR S0, [X0], #4 // загружаем в S0 число increment (0.01) LDR S1, [X0], #4 // загружаем в S1 число number1 (0.0) LDR S2, [X0] // загружаем в S2 число number2 (1.0) MOV W1, #100 loop: FADD S1, S1, S0 // сто раз прибавляем S0 (0.01) к S1(0.0) SUBS W1, W1, #1 // уменьшаем счетчик цикла B.NE loop // если не достигли 100, переходим обратно к loop // в этой точке программы ожидаем, что S1 равно 1.0 printRegister 1 // вывод регистра S1 printRegister 2 // вывод регистра S1 MOV X0, #0 // код возврата из функции LDR LR, [SP], #16 RET .data increment: .single 0.01 number1: .single 0.0 number2: .single 1.0 printStr: .asciz "S%c = %f\n"
Для упрощения вывода содержимого регистра я добавил специальный макрос. При выполнении программы мы увидим содержимое регистров:
S1 = 0.999999 S2 = 1.000000
Мы видим, что число из S2 почти равно числу из S1, но полного равенства нет. Однако разница настолько невелика, что в большинстве случаев ей можно пренебречь. И теперь применим допустимое отклонение
.global main main: STR LR, [SP, #-16]! LDR X0, =increment // загружаем указатель на число increment LDR S0, [X0], #4 // загружаем в S0 число increment (0.01) LDR S1, [X0], #4 // загружаем в S1 число number1 (0.0) LDR S2, [X0], #4 // загружаем в S2 число number2 (1.0) LDR S3, [X0] // загружаем в S3 число epsilon (0.00001) - допустимое отклонение MOV W1, #100 loop: FADD S1, S1, S0 // сто раз прибавляем S0 (0.01) к S1(0.0) SUBS W1, W1, #1 // уменьшаем счетчик цикла B.NE loop // если не достигли 100, переходим обратно к loop FSUB S1, S1, S2 // находим разность между S1 и S2 FABS S1, S1 // получаем абсолютное значение FCMP S1, S3 // сравниваем S1 и S3 (допустимое отклонение) B.LE equal // если числа равны, переходим к метке equal LDR X0, =notequalstr // если не равны, загружаем строку notequalstr B next equal: LDR X0, =equalstr // если равны, загружаем строку equalstr next: BL printf // вывод на экран с помощью функции printf MOV X0, #0 // код возврата LDR LR, [SP], #16 RET .data increment: .single 0.01 number1: .single 0.0 number2: .single 1.0 epsilon: .single 0.00001 // допустимое отклонение equalstr: .asciz "S1 and S2 are equal\n" notequalstr: .asciz "S1 and S2 are NOT equal\n"
Здесь отклонение задано с помощью метки epsilon, которое загружается в регистр S3:
epsilon: .single 0.00001
То есть если разница между числами S1 и S2 будет равна или меньше этого отклонения, то будем считать, что числа равны
FSUB S1, S1, S2 // находим разность между S1 и S2 FABS S1, S1 // получаем абсолютное значение FCMP S1, S3 // сравниваем S1 и S3 (допустимое отклонение)
И в этом случае получим другой результат.
Поскольку сравнение чисел - довольно распространенная задача, то лучше определить соответствующий функционал в виде отдельнй функции. Например, определим следующий файл fpcompare.s
// Функция для сравнения двух чисел с плавающей точкой // Входные параметры: // X0 - указатель на три числа с плавающей точкой number1, number2, epsilon // Результат: // X0 - 1, если числа равны, и 0, если не равны .global fpcompare fpcompare: LDP S0, S1, [X0], #8 // загружаем 3 числа и увеличиваем адрес в X0 на 8 байт LDR S2, [X0] // S0 = number1, S1 = number2, S2 = epsilon FSUB S3, S1, S0 // S3 = S1 - S0 = number2 - number1 FABS S3, S3 // получаем абсолютное значение FCMP S3, S2 // сравниваем с отклонением B.GT notequal // если абсолютное значение больше допустимого отклонения, переход на метку notequal MOV X0, #1 // если равны, помещаем в X0 число 1 B done // переходим к выходу из функции notequal: MOV X0, #0 // если не равны, помещаем в X0 число 0 done: RET
Здесь функция fpcompare
получает через регистр X0 указатель на три числа, перва два из которых - сравниваемые числа, а третье число - допустимое отклонение.
В самой функции вычитаем второе число из первого, получаем абсолютное значение разности и сравниваем с отклонением. Если числа равны, то в качестве результата помещаем в X0 число 1, если не равны, то число 0.
В файле main.s, который будет представлять основной файл программы, используем эту функцию:
.global main main: STR LR, [SP, #-16]! LDR X0, =increment // загружаем указатель на число increment LDR S0, [X0], #4 // загружаем в S0 число increment (0.01) LDR S1, [X0], #4 // загружаем в S1 число number1 (0.0) LDR S2, [X0], #4 // загружаем в S2 число number2 (1.0) //LDR S3, [X0] // загружаем в S3 число epsilon - допустимое отклонение MOV W1, #100 loop: FADD S1, S1, S0 // сто раз прибавляем S0 (0.01) к S1(0.0) SUBS W1, W1, #1 // уменьшаем счетчик цикла B.NE loop // если не достигли 100, переходим обратно к loop LDR X0, =number1 // загружаем указатель на число number1 STR S1, [X0] // сохраняем в number1 число из S1 BL fpcompare // вызываем функцию сравнения CMP X0, #1 // сравниваем результат B.EQ equal // если равно 1, то есть числа равны, переходим к метке equal LDR X0, =notequalstr // если не равны, загружаем строку notequalstr B next equal: LDR X0, =equalstr // если равны, загружаем строку equalstr next: BL printf // вывод на экран с помощью функции printf MOV X0, #0 // код возврата из функции LDR LR, [SP], #16 RET .data increment: .single 0.01 number1: .single 0.0 number2: .single 1.0 epsilon: .single 0.00001 // допустимое отклонение equalstr: .asciz "S1 and S2 are equal\n" notequalstr: .asciz "S1 and S2 are NOT equal\n"
Здесь опять же у нас есть два числа number1
и number2
. Для тестирования также сто раз прибавляем число increment
к number1
. Затем
сохраняем полученное значение из регистра S1 обратно в number1 и загружаем адрес этого числа в регистр X0 для передачи в функцию fpcompare
LDR X0, =number1 // загружаем указатель на число number1 STR S1, [X0] // сохраняем в number1 число из S1 BL fpcompare // вызываем функцию сравнения
Поскольку программу использует функционал языка С, скомпилируем ее с помощью команды
aarch64-none-linux-gnu-gcc main.s fpcompare.s -o main -static