Кроме целых чисел в ассемблере мы можем работать с числами с плавающей точкой. Числа с плавающей точкой или floating-point numbers представляют запись вещественных чисел, в которой число хранится в виде мантиссы и порядка (показателя степени), например,
1.12345 x 102
Для определения чисел с плавающей точкой процессоры x86-64 используют стандарт IEEE 754, согласно которому каждое число содержит ряд компонентов:
бит знака, который указывает, является ли число положительным или отрицательным
мантисса, в примере выше 1.12345
экспонента или показатель степени, в примере 102
это число 2.
Также числа с плавающей точкой характеризуются таким показателем, как десятичные знаки (decimal digits) или еще называют "десятичная точность" (decimal precision). Этот показатель указывает, сколько десятичных знаков может хранить число. Например, в числе 123.45 десятичная точность равна 5 (5 цифр).
Архитектура x86-64 поддерживает три типа данных для работы с числами с плавающей точкой:
Размер | Бит знака | Дробная часть | Экспонента |
32 бита | 1 | 23 | 8 |
64 бита | 1 | 52 | 11 |
80 бит | 1 | 64 | 15 |
Числа с плавающей точкой с одинарной точностью используют 23-битную мантисса с дополнением до единицы, 8-битную экспоненту и бит с одним знаком.
Фактически мантисса имеет 24 бита, где старший бит подразумевается, и он всегда равен 1. То есть мантисса выглядит следующим образом:
1.mmmmmmm mmmmmmmm
где mmmmmmm mmmmmmmm
- это 23 бита мантисы. Эти 23 бита представляют беззнаковое число, которые идут после десятичной точки. То есть мантиса
обычно представляет значение от 1,0 до чуть меньше 2,0.
Для представления значений вне диапазона от 1,0 до 2,0 применяется экспонента. Формат с плавающей точкой возводит 2 в степень, указанную экспонентой, а затем умножает мантиссу на это значение. Показатель степени составляет 8 бит и хранится в формате "excess-127". В этом формате экспонента 0 представляет значение 127 (7Fh), отрицательные показатели представляют собой значения в диапазоне от 0 до 126, а положительные показатели — значения в диапазоне от 128 до 255. Для преобразования экспоненты в формат "excess-127", к значению экспоненты добавляется число 127.
С 24-битной мантиссой можно получить примерно шесть с половиной (десятичных) знаков точности (половина означает, что все первые шесть цифр могут быть в диапазоне от 0 до 9, а седьмая цифра может быть только в диапазоне от 0 до n, где n < 9 и обычно близко к 5). Благодаря этому можно представлить 2±127 значений или примерно 10±38.
Числа с плавающей точкой двойной точности - данные типа .double занимают 64 бита. Первый бит числа также представляет знаковый бит. Однако теперь мантисса занимает 52 бита плюс 1 подразумеваемый бит - старший бит, равный 1. А экспонента занимает 11 бит и использует формат "excess-1023". Это обеспечивает диапазон значений 10±308 и 14,5 знаков точности
Для определения чисел c плавающей точкой ассемблер NASM предоставляет ряд директив:
db: число с плавающей точкой размером в 1 байт (byte)
dw: число с плавающей точкой размером в 2 байта (word)
dd: 4 байта (dword)
dq: 8 байт (qword)
dt: 10 байт (tword)
do: 16 байт (oword)
dy: 32 байта (yword)
dz: 64 байта (zword)
Первые четыре директивы также применяются для определения целочисленных переменных
В NASM число с плавающей точкой должно начинаться с десятичной цифры, за которой может следовать точка как разделитель целой и дробной частей и некоторое количество десятичных цифр. Если число отрицательное, то перед ним указывается минус (для положительных можно указать знак +, но он необязателен). Например:
1.234 -23.456 0.23 -1.0
Если в целой части только 0, то его можно опустить
.23 (или 0.23) .01567 (или 0.01567)
Также можно использовать экспоненциальную запись, при которой после числа следует буква e или E, за которой может следовать знак (+ или -) и одна или несколько десятичных цифр.
3.75e2 1.1e-1 1.e+4 -123.456e+789 +25.0e0 1.e3
Пример определения переменных чисел с плавающей точкой в программе
section .data n1 dw 0.0 n2 dw 2.7 n3 dq 3.14159 n4 dq 0 n5 dq 1.234567e+2 ; 123.4567
В дальнейшем мы подробнее рассмотрим инструкции для работы с подобными числами. А пока для небольшого примера рассмотрим следующую задачу на Linux:
global _start section .data num dq 4.6 section .text _start: movq xmm0, [num] ; помещаем в регистр xmm0 число num cvtsd2si rdi, xmm0 ; преобразуем число из xmm0 в целое число и помещаем в rdi mov rax, 60 syscall
Здесь первая инструкция - movq
помещает в регистр xmm0 значение переменной num. Вторая инструкция - cvtsd2si
преобразует значение из xmm0 в целое число,
которое помещается в регистр rdi. Компиляция и работа программы:
root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o root@Eugene:~/asm# ld hello.o -o hello root@Eugene:~/asm# ./hello root@Eugene:~/asm# echo $? 5 root@Eugene:~/asm#
В данном случае видно, что число 4.6 при преобразовании в целое число было округлено до 5.
Аналогичный пример на Windows:
global _start section .data num dq 4.6 section .text _start: movq xmm0, [rel num] ; помещаем в регистр xmm0 число num cvtsd2si rax, xmm0 ; преобразуем число из xmm0 в целое число и помещаем в rax ret