Десятичная арифметика и двоично-десятичный формат BCD

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

Хотя процессоры 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, как и ожидалось.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850