Хотя процессоры x86-64 используют двоичную систему счисления для собственного внутреннего представления данных, но некоторые алгоритмы могут зависеть от десятичных вычислений для получения правильных результатов. Поэтому, хотя десятичная арифметика, как правило, менее эффективна и менее точна, чем двоичная, потребность в десятичной арифметике сохраняется.
Для представления десятичных чисел в двоичном формате наиболее распространенным методом является использование представления в двоично-десятичном формате (BCD). При этом используются 4 бита для представления 10 возможных десятичных цифр. Двоичное значение этих 4 бит равно соответствующему десятичному значению в диапазоне от 0 до 9.
BCD | Десятичное значение |
0000 | 0 |
0001 | 1 |
0010 | 2 |
0011 | 3 |
0100 | 4 |
0101 | 5 |
0110 | 6 |
0111 | 7 |
1000 | 8 |
1001 | 9 |
1010 | - |
1011 | - |
1100 | - |
1101 | - |
1110 | - |
1111 | - |
Хотя с помощью 4 бит можно представить 16 различных значений; формат BCD игнорирует оставшиеся шесть битовых комбинаций. Поскольку для каждой двоично-десятичной цифры требуется 4 бита, мы можем представить двузначное двоично-десятичное число одним байтом. Это означает, что мы можем представить десятичные значения в диапазоне от 0 до 99 с помощью одного байта (в отличие от 0 до 255 с байтом в двоичном формате).
Например,
число 99 в BCD-формате равно 99h
. Хотя в конце добавляется символ h, как и в случае с обычными шестнадцатеричными числами. Но в реальности это десятичное число 99.
Чтобы повысить производительность приложений с десятичной арифметикой, Intel внедрила поддержку десятичной арифметики непосредственно в FPU. FPU поддерживает значения с точностью до 18 знаков после точкой, при этом в вычислениях используются все арифметические возможности FPU (сложение, вычитание и т.д.).
FPU поддерживает только один тип данных BCD: 10-байтовое 18-разрядное десятичное значение. Первые 9 байтов используются для хранения значения BCD в стандартном упакованном десятичном формате. Первый байт содержит младшие две цифры, а девятый байт содержит старшие две цифры числа. Старший бит десятого байта содержит бит знака, оставшиеся биты в десятом байте игнорируются и никак не используются (поскольку использование этих битов может создать возможные значения BCD, которые FPU не может точно представить в родном формате с плавающей точкой).
FPU использует нотацию с дополнением до единицы для отрицательных значений BCD. Бит знака содержит 1, если число отрицательное, и содержит 0, если число положительное. Если число равно 0, бит знака может быть либо 0, либо 1, потому что для 0 существует два различных представления.
В MASM — для определения упакованных переменных BCD применяется тип данных tbyte, значения которого можно инициализировать шестнадцатеричным/BCD-значением.
FPU предоставляет две инструкции, fbld и fbstp, которые преобразуют упакованные десятичные в двоичные форматы с плавающей точкой при перемещении данных в FPU и обратно. Инструкция fbld загружает 80-битное упакованное значение BCD в вершину стека FPU после преобразования этого значения значение с плавающей точкой. Аналогично, инструкция fbstp извлекает значение с плавающей точкй из вершины стека, преобразует его в упакованное значение BCD и сохраняет значение в переменной. Преобразование между упакованным BCD и форматом с плавающей точкой — недешевая операция, поэтому данные инструкции могут быть довольно медленными.
Поскольку FPU преобразует упакованные десятичные значения во внутренний формат с плавающей точкой, можно смешивать числа BCD и числа с плавающей точкой и целочисленные (двоичные) значения в одном и том же вычислении:
.data value1 tbyte 21h value2 real8 2.0 value3 dword 1 .code main proc fbld value1 ; загружаем value1 fmul value2 ; умножаем на value2 fiadd value3 ; прибавляем value3 fbstp value1 ; сохраняем данные обратно в value1 mov rax, qword ptr value1 ; RAX = 67 ret main endp end
В данном случае значение tbyte хранится в переменной value1. В программе загружаем это значение в стек FPU, умножаем на value2, прибавляем value3 и полученное значение извлекаем из вершины стека и сохраняем обратно в value1.
Таким образом, учитывая, что tbyte использует формат BCD, а переменная value1 изначальна равна 21h (то есть десятичное число 21), то после умножения на 2 и прибавления 1 оно
будет равно 21 * 2 + 1 = 43
Для примера в конце программы идет загрузука результата из value1 в регистр RAX с преобразованием до qword. И мы увидим, что в RAX в реальности не 43, а 67. Потому что для RAX это число в двоичном формате, и поэтому интерпертируется как 67.
Но мы можем использовать преобразования FPU для конвертации значения в вершине стека в нужный формат:
.data value1 tbyte 21h value2 real8 2.0 value3 dword 1 result qword ? .code main proc fbld value1 ; загружаем value1 fmul value2 ; умножаем на value2 fiadd value3 ; прибавляем value3 fistp result ; извлекаем значение в переменную result mov rax, result ; RAX = 43 ret main endp end
Здесь значение из вершины стека - результат операции извлекается в переменную result. FPU автоматически преобразует значение в нужный формат. И поэтому в RAX будет число 43, как и ожидалось.