LibMPU (Длинная арифметика)
Библиотека выполнена как эмулятор процессора с набором регистров и флагов, устанавливаемых по результатам проведенных операций. Набор целочисленных функций содержит арифметические, логические операции, а также операции сдвига. Для вещественных и комплексных чисел реализованы основные тригонометрические функции.
Разрядность ограничена 65536 бит для арифметических операций и 16384 бит для тригонометрии. Ограничения обусловлены порядком рядов аппроксимации.
Форматы данных
Библиотека поддерживает целые, вещественные и комплексные типы. Переменные хранятся в массивах байтов.
Целые числа
В заголовочном файле libmpu.h определены константы, которые представляют количество байтов для хранения целых переменных:
#define NB_I8 1
#define NB_I16 2
#define NB_I32 4
#define NB_I64 8
#define NB_I128 16
#define NB_I256 32
#define NB_I512 64
#define NB_I1024 128
#define NB_I2048 256
#define NB_I4096 512
#define NB_I8192 1024
#define NB_I16384 2048
#define NB_I32768 4096
#define NB_I65536 8192
#define NB_I_MAX 8192
Данные константы можно использовать в качестве значения аргумента nb для операций с целыми числами.
В системах с big-endian порядком байтов старший байт числа хранится по наименьшему адресу памяти, а младший байт — по наибольшему. В системе с little-endian порядком байтов, напротив, младший байт хранится по наименьшему адресу.
На следующей схеме представлено размещение целого числа в зависимости от архитектуры машины:
if( MPU_BYTE_ORDER_BIG_ENDIAN == 0 )
{
[NB-1], . . . , [0];
+--------------------------- . . . ---------------------------+
| high low |
+--------------------------- . . . ---------------------------+
^Sign bit
size: NB.
}
if( MPU_BYTE_ORDER_BIG_ENDIAN == 1 )
{
[0], . . . , [NB-1];
+--------------------------- . . . ---------------------------+
| high low |
+--------------------------- . . . ---------------------------+
^Sign bit
size: NB.
}
Здесь, символ NB — обозначает количество байтов числа.
Для представления целых переменных пользователь может самостоятельно создавать массивы байтов любым из перечисленных ниже способов:
__mpu_byte_t a[NB_I65536];
mpu_int a[NB_I65536];
mpu_int *a = (mpu_int *)malloc( NB_I65536 * sizeof(__mpu_byte_t) );
а также использовать, предопределенные в libmpu.h типы данных, которые прямо говорят о размерности:
mpu_int4096_t a;
Целые числа могут рассматриваться как знаковые (signed), так и беззнаковые (unsigned). Знаковые переменные для удобства операций с ними представляются в дополнительном коде. Ниже приведена таблица некоторых значений 8-разрядной переменной в дополнительном коде.
+---------------+------------------+
| Десятичное | Двоичное |
| представление | представление |
+---------------+------------------+
| 127 | 0111 1111 |
| 3 | 0000 0011 |
| 2 | 0000 0010 |
| 1 | 0000 0001 |
| 0 | 0000 0000 |
| -1 | 1111 1111 |
| -2 | 1111 1110 |
| -3 | 1111 1101 |
| -127 | 1000 0001 |
| -128 | 1000 0000 |
+---------------+------------------+
Вещественные числа
Вещественные переменные, также как и целые, хранятся в виде массивов байтов. В заголовочном файле libmpu.h определены константы, которые представляют количество байтов для хранения вещественных переменных:
#define NB_R32 4
#define NB_R64 8
#define NB_R128 16
#define NB_R256 32
#define NB_R512 64
#define NB_R1024 128
#define NB_R2048 256
#define NB_R4096 512
#define NB_R8192 1024
#define NB_R16384 2048
#define NB_R32768 4096
#define NB_R65536 8192
#define NB_R_MAX 8192
Данные константы можно использовать в качестве значения аргумента nb для операций с вещественными числами.
Вещественные числа имеют в своем составе два поля: смещенной экспоненты и мантиссы. Бит целой единицы неявный. Знак расположен в старшем бите числа.
На следующей схеме представлено размещение вещественного числа в зависимости от архитектуры машины:
if( MPU_BYTE_ORDER_BIG_ENDIAN == 0 )
{
[NB-1], . . . , [nS] | [nS-1], . . . , [0];
+------ . . . -------+--------------- . . . ------------------+
| Sign + Exponent | Significand |
+------ . . . -------+--------------- . . . ------------------+
^Sign bit ^(1. - implicit)
size: nE nS.
}
if( MPU_BYTE_ORDER_BIG_ENDIAN == 1 )
{
[0], . . . , [nE-1] │ [nE], . . . , [NB-1];
+------ . . . -------+--------------- . . . ------------------+
| Sign + Exponent | Significand |
+------ . . . -------+--------------- . . . ------------------+
^Sign bit ^(1. - implicit)
size: nE nS.
}
Здесь, символы nE и nS — обозначают количество байтов экспоненты и количество байтов мантиссы, соответственно.
Количество бит выделяемое для представления знака, экспоненты и мантиссы распределено следующим образом:
+-----------------------+-------------------+-------------+
| Общее количество бит | (Sign + Exponent) | Significand |
+-----------------------+-------------------+-------------+
| 32 | 1 + 8 + | 23 |
| 64 | 1 + 11 + | 52 |
| 128 | 1 + 31 + | 96 |
| 256 | 1 + 31 + | 224 |
| 512 | 1 + 63 + | 448 |
| 1024 | 1 + 63 + | 960 |
| 2048 | 1 + 127 + | 1920 |
| 4096 | 1 + 127 + | 3968 |
| 8192 | 1 + 255 + | 7936 |
| 16384 | 1 + 255 + | 16128 |
| 32768 | 1 + 511 + | 32256 |
| 65536 | 1 + 511 + | 65024 |
+-----------------------+-------------------+-------------+
Форматы 32- и 64-битных чисел полностью совпадают с IEEE (Institute of Electrical and Electronics Engineers) форматом.
Для удобства декларирования переменных вещественного типа в заголовочном файле libmpu.h определены соответствующие типы данных, применение которых может выглядеть, например, следующим образом:
mpu_real16384_t a, b;
Бесконечности, неопределенность, не числа, максимальные и минимальные величины
Для расширения вычислительных возможностей в формате чисел с плавающей точкой наряду с обычными вещественными числами предусмотрено несколько специальных значений. Они имеют определенный смысл и дают важную информацию об алгоритмах и операциях, в которых появляются эти значения. К специальным значениям относятся вещественные числа с нарушением нормализации, неопределенность, нули, бесконечности и не числа, представленные в следующей таблице.
+------+-------------------+-------------------+------------------+
| Sign | Exponent | Significand | comments |
+------+-------------------+-------------------+------------------+
| S | 1111 . . . 1111 | 0000 . . . 0000 | +/- inf |
| S | 0000 . . . 0000 | 0000 . . . 0000 | +/- 0 |
| 1 | 1111 . . . 1111 | 1000 . . . 0000 | - ind |
| S | 1111 . . . 1111 | 0000 . . . 0001 | +/- NaN (min) |
| S | 1111 . . . 1111 | 1111 . . . 1111 | +/- NaN (max) |
+------+-------------------+-------------------+------------------+
Здесь:
+/- inf – +/- бесконечность;
+/- 0 – +/- знаковый нуль;
- ind – неопределенность;
+/- NaN (min) – +/- минимальное не число;
+/- NaN (max) – +/- максимальное не число.
Числа с нарушением нормализации:
+------+-------------------+-------------------+------------------+
| Sign | Exponent | Significand | comments |
+------+-------------------+-------------------+------------------+
| S | 0000 . . . 0000 | 0000 . . . 0001 | +/- min |
| S | 0000 . . . 0000 | 1111 . . . 1111 | +/- max |
+------+-------------------+-------------------+------------------+
Кодировка максимального и минимального вещественных чисел:
+------+-------------------+-------------------+------------------+
| Sign | Exponent | Significand | comments |
+------+-------------------+-------------------+------------------+
| S | 0000 . . . 0001 | 0000 . . . 0000 | +/- MIN |
| S | 1111 . . . 1110 | 1111 . . . 1111 | +/- MAX |
+------+-------------------+-------------------+------------------+
Комплексные числа
Комплексные числа хранятся в памяти машины как структура, состоящая из двух вещественных чисел.
Константы, определяющие размер комплексных чисел в байтах, заданы так, что они представляют половину размера комплексного числа:
#define NB_C32 4
#define NB_C64 8
#define NB_C128 16
#define NB_C256 32
#define NB_C512 64
#define NB_C1024 128
#define NB_C2048 256
#define NB_C4096 512
#define NB_C8192 1024
#define NB_C16384 2048
#define NB_C32768 4096
#define NB_C65536 8192
#define NB_C_MAX 8192
Здесь важно отметить, что функции, работающие с комплексными переменными в качестве параметра, определяющего размер операндов принимают именно эти величины. Например для работы с переменной типа mpu_complex256_t, на вход функций надо подавать nb = 32 == NB_C256, в то время, как sizeof (mpu_complex256_t) == 64 == NB_C256×2.
Представление комплексных чисел в памяти показано на следующей схеме:
if( MPU_BYTE_ORDER_BIG_ENDIAN == 0 )
{
[NB*2-1], . . . , [NB] │ [NB-1], . . . , [0];
+------------ . . . -----------+------------ . . . -----------+
| Real part | Imaginary |
+------------ . . . -----------+------------ . . . -----------+
size: NB_Real == NB_CXXX NB_Imag == NB_CXXX.
}
if( MPU_BYTE_ORDER_BIG_ENDIAN == 1 )
{
[0], . . . , [NB-1] │ [NB], . . . , [NB*2-1];
+------------ . . . -----------+------------ . . . -----------+
| Real part | Imaginary |
+------------ . . . -----------+------------ . . . -----------+
size: NB_Real == NB_CXXX NB_Imag == NB_CXXX.
}
Форматы вещественной и мнимой частей комплексных переменных совпадают с форматами вещественных чисел.
Флаги
Большинство операций с целыми и вещественными числами выставляют флаги. Флаги операций размещены в целой 32-битной переменной. Младшие 8 битов [7 … 0] отданы под флаги целочисленных операций. Биты с 8-го по 15-й занимают флаги, выставляемые операциями с вещественными числами.
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
. . . -+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| INX | IND | PLS | TLS | UDF | OVF | SNG | DOM | V | R | Z | P | S | O | C | A |
. . . -+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
Флаги целочисленных операций:
A - Auxiliary Carry Flag (carry from lowest 4-bit word)
C - Carry Flag
O - Overflow Flag
S - Sign Flag
P - Parity Flag (of lowest significant byte)
Z - Zero Flag
R - major || remainder
V - Invalid operation
NOTE: Флаги A и P выставляются только операциями над 8-разрядными и 16-разрядными переменными.
Флаги операций с вещественными переменными:
DOM - Domain Flag
SNG - Singularity Flag
OVF - Overflow Flag
UDF - Underflow Flag
TLS - TLOSS Flag
PLS - PLOSS Flag
IND - ind-produsing operation Flag
INX - Inexact Flag
В заголовочном файле libmpu.h определены функции работы с флагами, такие как очистка флагов, сброс, выставление флагов, а также проверки флагов операций.
Исключения и коды ошибок
Помимо выставления флагов операциями с целыми и вещественными числами, библиотека libmpu поддерживает стандартную переменную errno и, ктоме того, собственные переменные __mpu_integer_error_no, __mpu_real_error_no, __mpu_complex_error_no, __mpu_math_error_no.
Коды ошибок определены в заголовочном файле libmpu.h.
Библиотека libmpu поддерживает обработку ошибок посредством функции __mpu_math_error (), которая может быть переопределена пользователем на этапе компоновки объектного кода так, как это возможно при переопределении функции matherr () во время компоновки программ со стандартной библиотекой языка C (например, GNU Libc).
Кроме того, пользователь может переопределить функцию __mpu_warning (), которая выводит дополнительную информацию об ошибках.
Как и в случае функции matherr () стандартной библиотеки языка C, параметром функций __mpu_math_error (), __mpu_warning () является указатель на структуру __exception:
struct __exception
{
int who; /* _COMPLEX_, _REAL_, _INTEGER_, _MATH_ */
int type;
__mpu_char8_t *name;
__mpu_char8_t *msg;
int msg_type; /* >= 1 - error, 0 - warning */
int nb_a1; /* number of bytes in arg_1 */
int nb_a2; /* number of bytes in arg_2 */
int nb_rv; /* number of bytes in return_value */
unsigned char *arg_1;
unsigned char *arg_2;
unsigned char *return_value;
};
в которой определены: источник ошибки, тип ошибки, имя функции выполнение которой привело к ошибке, а также указатели на аргументы функции и полученное возвращаемое значение.
С помощью функции __mpu_utf8mpu_error () можно получить указатель на строковую константу, содержащую текстовое описание ошибки, соответствующее коду ошибки (см. переменные __mpu_integer_error_no, __mpu_real_error_no, __mpu_complex_error_no, __mpu_math_error_no).
Для простых вычислений, как правило, не приходится переопределять функции matherr (), __mpu_math_error () и __mpu_warning (), однако мы сохранили такую возможность как одну из стандартных возможностей, предоставляемых библиотекой языка C.
Исходный код
Исходный код можно найти в репозитории libmpu.git, где в файле README.md приведен простой пример вычиcлений.
Выпуски исходного кода, не требующие выполнения ./bootstrap, можно найти в каталоге releases на FTP-сервере.
Кроме того, существует зеркало основного репозитория libmpu.git на ресурсе GitFlic.ru. Здесь пользователи библиотеки смогут создавать pull-request-ы на изменения.
Сборка
Сборка и инсталляция библиотеки на системе GNU/Linux достаточно проста, поскольку исходный код оформлен с помощью утилит Autoconf, Automake:
wget https://ftp.radix-linux.su/libmpu/releases/libmpu-1.0.14.tar.xz
mkdir build
cd build
../libmpu-1.0.14/configure \
--prefix=/usr \
--libdir=/usr/lib64 \
--enable-static=no
make
make install
Дополнительно можно задать ограничения разрядности с помощью управлений: --with-real-io-limit и --with-real-math-f-limit. Подробную информацию о управлениях процессом конфигурирования можно получить с помощью команды:
../libmpu-1.0.14/configure --help
Пример использования
Рассмотрим сборку простой программы на примере вычисления арктангенса отношения с точностью 16384 бит.
Составим простой Make-файл:
CC = gcc
CFLAGS = `mpu-config cflags`
LDFLAGS = `mpu-config --ldflags`
LIBS = `mpu-config --libs` -lpthread
SOURCES = main.c
OBJECTS = main.o
all: main
.SUFFIXES:
.SUFFIXES: .o .c
%.o : %.c
$(CC) $(CFLAGS) -c -o $@ $<
main: $(OBJECTS)
$(CC) $(LDFLAGS) -o main $(OBJECTS) $(LIBS)
clean:
rm -f main *.o
и создадим программу на языке С в файле main.c:
#include
#include
int main( void )
{
int rc = 0;
__mpu_init();
__mpu_extra_warnings = 1;
{
mpu_real16384_t r, d;
int nb = NB_R16384;
__mpu_char8_t s[6000]; /* size can be obtained by _real_max_string( NB_R16384 ) function */
ascii_to_real( r, "1.0", nb ); /* evaluate the R variable */
ascii_to_real( d, "1.0", nb ); /* evaluate the D variable */
r_atan2( r, r, d, nb ); /* arc tangent function of R / D and place result to the R variable */
real_to_ascii( s, r, _real_mant_digs(nb), 'E', 0, 0, nb ); /* convert R value to ASCII string S */
printf( "s = %s\n\nrc = %d;\n", s, rc );
}
__mpu_free_context();
return( rc );
}
Выполнив сборку и запустив нашу программу main:
make
./main
мы получим следующий вывод на терминал:
s = 7.853981633974483096156608458198757210492923498437764552437361480769541015715522496570087063355292669955370216283205766617734611523876455579313398520321202793625710256754846302763899111557372387325954911072027439164833615321189120584466957913178004772864121417308650871526135816620533484018150622853184311467516515788970437203802302407073135229288410919731475900028326326372051166303460367379853779023582643175914398979882730465293454831529482762796370186155949906873918379714381812228069845457529872824584183406101641607715053487365988061842976755449652359256926348042940732941880961687046169173512830001420317863158902069464428356894474022934092946803671102253062383575366373963427626980699223147308855049890280322554902160086045399534074436928274901296768028374999995932445124877649329332040240796487561148638367270756606305770633361712588154827970427525007844596882216468833020953551542944172868258995633726071888671827898907159705884468984379894454644451330428067016532504819691527989773041050497345238143002663714658197164840383454569920575754880088254632422489434056498534728124304438208697828788937143106135376739877073832792154319639722687745954386593662348298137651002319254177847524622060032145904008909269150261775454857388990497366919593623620638449436817117760118836615520116710647673728233284192572472880261880405142415060145095375483778133456075088969100618741183159785498181510674806991955886954090023354304103049811465787575715457436389266874596263736471467317489227318026993773257388302913362468006889900591201663747799704586994383915924518566356319656379546043936682227444431984500204117650040363122980433043036930875353604933921370403402893381380333689354623671096308309768485358336369406042156297458923713905248048055460681375635642219179476236504133667012471568081979465214460959569919941703635252384709465902376700160562813012793482462240210321221567364010604913212555526652965766860696555097987362617809284467402390910929793218669411643934906034727164581614989533476195068975248664410197378173670995881489274564556551306223519316798695671206503692477256600340986093638262670508740631129373499128578574522976648127343054241153789274645968526494714899432438747325404384821170345671709672356935389979639813114884898577624931311702114968184111739621634591840556565247811520128109710976128110341374406951994289228589994250324040223604237171389621015883555180564571221620396140071265042106848630668669197238130346306374886666819559966141490290887215576443641245088984086420435810281287690173698627741490235063072199277232864172842168071872351400253758271544821702302186902294562346472524287274189963153413874454732824462054207497371806647012143910035676193887830949103628809365585911357146111988164669552627853386839348807783567915255339923840578607381212342967775364413508975699836009355018276446347655495968619521196224208303614284671879858766075546132977621202513426536701687298195477994849880153549158571886101609362809295480449995977539798904536687856728099372352267966235579903919863020237866375563079009704825355234405344639891597344467707597674480193366805456406499153753553757670096119336373006513536664813003743607126831296665005331085220476776215829336624128634764490671402513770266646992495408936840960131428647757242660350277428015701097599398789192894251050844674840018076396907940898554682812867801332320479565465314668303947996037122072911176523938176739305229391779180727745422728820043376678187145805717958802338491431280270894787842470522861052516877583667775785316777842477164629099578754925412877921292859414404587889127212274974649955968363820829088769119796339733065463724080332332724642660511688147677288703541374648654092950677454985774811222393928207226480808315243249280211083933163723455977983149365916826459010357034707580095622593974631093604257283191404523443361007676867302800478255082595049405275275112323303790210611214909417459738071711957808881645532862394214315608360473259921606560852693306744507018297288602752611705813179050263163068027915099166393273136777639463440866705163277474131729655141192328142646589155046395251823401649691215294776133372125865284144216883123604170099066449469296390211382413531663521326535861079646692437864153517001750594694147836004281873676051405576347486403285177817501019636833174847703636661614701993177066707658582146964245763089522332664393516988642909438135505287389403953500625315571485325541178877448148077476991368440313794141878393794574166136947936252824903722576159986783240526835109379739339903647254847428277947607445714118758007996728785071770214976200273530368053294869119431556035637136350830392963265355720343962607658304379574496655592930397901929173136871847466637373625286635157108415984475099423164180365963266840241428022951909581791604068722200196731400725571180260079302965205104750105741542799094480334393778739875391512407965736816368410631E-1
rc = 0;
Все операции, предусмотренные библиотекой LibMPU, можно увидеть с заголовочном файле libmpu.h. Здесь мы рассмотрим лишь некоторые из них.
Сложение и вычитание
Библиотекой предусмотрены четыре операции сложения и вычитания целых чисел. Две из них, iadd (), isub (), не используют флаг переноса (C). В то время как две другие, iadc (), isbb (), позволяют учитывать флаг переноса (C), выставленный предыдущей операцией.
iadd ()
#include
void iadd( mpu_int *c, mpu_int *a, mpu_int *b, int nb );
Функция iadd () выполняет операцию сложения целых чисел, расположенных по адресам a и b, c размещением результата по адресу c. Операнды могут быть числами со знаком или без него. Содержимое памяти по адресам a, b не изменяется, предыдущее содержимое памяти по адресу c теряется. Параметр nb определяет размер операндов (в байтах), расположенных по адресам c, a, b.
Функция воздействует на флаги A, C, O, P, S, Z и V.
Флаги А и P выставляются только в том случае, когда размер операндов a, b равен одному или двум байтам (nb == 1 || nb == 2).
Пример
#include
#include
int main( void )
{
int rc = 0;
__mpu_init();
__mpu_extra_warnings = 1;
{
mpu_int128_t c, a, b;
int nb = NB_I128;
__mpu_char8_t s[256];
iatoi( a, "237", nb ); /* evaluate the A variable */
iatoi( b, "37", nb ); /* evaluate the B variable */
iadd( c, a, b, nb );
iitoa( s, c, RADIX_DEC, LOWERCASE, nb ); /* convert C value to ASCII string S */
printf( "c = %s;\n", s ); /* c = 274; */
}
__mpu_free_context();
return( rc );
}
isub ()
#include
void isub( mpu_int *c, mpu_int *a, mpu_int *b, int nb );
Функция isub () выполняет операцию вычитания целых чисел, расположенных по адресам a и b, c размещением результата по адресу c. Операнды могут быть числами со знаком или без него. Содержимое памяти по адресам a, b не изменяется, предыдущее содержимое памяти по адресу c теряется. Параметр nb определяет размер операндов (в байтах), расположенных по адресам c, a, b.
Функция воздействует на флаги A, C, O, P, S, Z и V.
Флаги А и P выставляются только в том случае, когда размер операндов a, b равен одному или двум байтам (nb == 1 || nb == 2).
Пример
#include
#include
int main( void )
{
int rc = 0;
__mpu_init();
__mpu_extra_warnings = 1;
{
mpu_int128_t c, a, b;
int nb = NB_I128;
__mpu_char8_t s[256];
iatoi( a, "237", nb ); /* evaluate the A variable */
iatoi( b, "37", nb ); /* evaluate the B variable */
isub( c, a, b, nb );
iitoa( s, c, RADIX_DEC, LOWERCASE, nb ); /* convert C value to ASCII string S */
printf( "c = %s;\n", s ); /* c = 200; */
}
__mpu_free_context();
return( rc );
}
iadc ()
#include
void iadс( mpu_int *c, mpu_int *a, mpu_int *b, int nb );
Функция iadc () выполняет операцию сложения целых чисел, расположенных по адресам a и b, к полученной сумме добавляет значение флага переноса (C) и размещает результат по адресу c. Операнды могут быть числами со знаком или без него. Содержимое памяти по адресам a, b не изменяется, предыдущее содержимое памяти по адресу c теряется. Параметр nb определяет размер операндов (в байтах), расположенных по адресам c, a, b. Поскольку функция iadc () использует флаг переноса (C), то она может применяться для сложения чисел, длина которых превышает максимально допустимые размеры целых чисел.
Функция воздействует на флаги A, C, O, P, S, Z и V.
Флаги А и P выставляются только в том случае, когда размер операндов a, b равен одному или двум байтам (nb == 1 || nb == 2).
Пример
#include
#include
int main( void )
{
int rc = 0;
__mpu_init();
__mpu_extra_warnings = 1;
{
mpu_int128_t c, a, b;
int nb = NB_I128;
__mpu_char8_t s[256];
iatoi( a, "237", nb ); /* evaluate the A variable */
iatoi( b, "37", nb ); /* evaluate the B variable */
__mpu_stc(); /* Set Carry Flag */
iadc( c, a, b, nb );
iitoa( s, c, RADIX_DEC, LOWERCASE, nb ); /* convert C value to ASCII string S */
printf( "c = %s;\n", s ); /* c = 275; */
}
__mpu_free_context();
return( rc );
}
isbb ()
#include
void isbb( mpu_int *c, mpu_int *a, mpu_int *b, int nb );
Функция isbb () выполняет операцию вычитания целых чисел, расположенных по адресам a и b, из полученной разности вычитает значение флага переноса (C) и размещает результат по адресу c. Операнды могут быть числами со знаком или без него. Содержимое памяти по адресам a, b не изменяется, предыдущее содержимое памяти по адресу c теряется. Параметр nb определяет размер операндов (в байтах), расположенных по адресам c, a, b. Поскольку функция isbb () использует флаг переноса (C), то она может применяться для вычитания чисел, длина которых превышает максимально допустимые размеры целых чисел.
Функция воздействует на флаги A, C, O, P, S, Z и V.
Флаги А и P выставляются только в том случае, когда размер операндов a, b равен одному или двум байтам (nb == 1 || nb == 2).
Пример
#include
#include
int main( void )
{
int rc = 0;
__mpu_init();
__mpu_extra_warnings = 1;
{
mpu_int128_t c, a, b;
int nb = NB_I128;
__mpu_char8_t s[256];
iatoi( a, "237", nb ); /* evaluate the A variable */
iatoi( b, "37", nb ); /* evaluate the B variable */
__mpu_stc(); /* Set Carry Flag */
isbb( c, a, b, nb );
iitoa( s, c, RADIX_DEC, LOWERCASE, nb ); /* convert C value to ASCII string S */
printf( "c = %s;\n", s ); /* c = 199; */
}
__mpu_free_context();
return( rc );
}
Сдвиги
#include
void ishl( mpu_int *c, mpu_int *a, int nb );
void ishr( mpu_int *c, mpu_int *a, int nb );
void isal( mpu_int *c, mpu_int *a, int nb );
void isar( mpu_int *c, mpu_int *a, int nb );
Для операций сдвигов на один бит входным операндом служит переменная размером nb байт, находящаяся по адресу a. Результат помещается в переменную того же размера, расположенную по адресу c. Пространства, занимаемые входной и выходной переменной в памяти могут пересекаться, как частично, так и полностью, что не влияет на правильность получаемого результата. Содержимое переменной, расположенной по адресу a останется неизменным после выполнения операции, если занимаемое ею пространство не пересекается с пространством, занимаемым переменной c.
При выполнениии операций сдвига, флаг переноса (C) всегда содержит значение последнего выдвинутого бита. Существуют следующие виды операций сдвига:
SHL — логический беззнаковый сдвиг влево на один бит.
SHR — логический беззнаковый сдвиг вправо на один бит.
SAL — арифметический сдвиг влево на один бит.
SAR — арифметический сдвиг вправо на один бит.
Следующие таблицы иллюстрируют выполнение операций ishl (), ishr ().
+----------------+---+-------------------+------------+
| SHL(<<): | C | значение операнда | заполнение |
+----------------+---+-------------------+------------+
| до операции | | 10110111 | 0 |
+----------------+---+-------------------+------------+
| после операции | 1 | 01101110 | |
+----------------+---+-------------------+------------+
+----------------+------------+-------------------+---+
| SHR(>>): | заполнение | значение операнда | C |
+----------------+------------+-------------------+---+
| до операции | 0 | 10110111 | |
+----------------+------------+-------------------+---+
| после операции | | 01011011 | 1 |
+----------------+------------+-------------------+---+
Из таблиц видно, что при любом логическом сдвиге на освободившееся место всегда задвигается 0. Флаг переполнения (O) выставляется, если в результате операции операнд изменил знак.
Операция арифметического сдвига влево isal () действует аналогично операции ishl () (также на пустое место справа задвигается 0). Операция арифметического сдвига вправо действует иначе: в освобождающуюся позицию слева копируется знаковый бит исходного операнда. Действие операций isal () и isar () показано с помощью следующих таблиц.
+----------------+---+-------------------+------------+
| SAL(<<): | C | значение операнда | заполнение |
+----------------+---+-------------------+------------+
| до операции | | 10110111 | 0 |
+----------------+---+-------------------+------------+
| после операции | 1 | 01101110 | |
+----------------+---+-------------------+------------+
+----------------+------------+-------------------+---+
| SAR(>>): | заполнение | значение операнда | C |
+----------------+------------+-------------------+---+
| до операции | sign(1) | 10110111 | |
+----------------+------------+-------------------+---+
| после операции | | 11011011 | 1 |
+----------------+------------+-------------------+---+
Флаг переполнения при выполнении операции isal () выставляется таким же образом, как и при операции ishl (). При выполнении операции isar (), операнд не может изменить свой знак. Многократный сдвиг isar () может, в конце концов, привести к потере значения, и положительное число задвинется в 0, а отрицательное в -1. По этому флаг переполнения (O) для операции isar () сбрасывается в 0.
При выполнении операций isal (), isar (), ishl (), ishr (), так же выставляются флаги четности (P), знака (S) и нуля (Z). Флаг переноса из младшей тетрады (A) не определяется и просто сбрасывается в 0.
Сдвиг влево можно применять для удваивания чисел, а сдвиг вправо — для деления на 2. Эти операции выполняются значительно быстрее, чем операции умножения и деления. Деление пополам нечетных чисел (например, 5 или 7) образует меньшие значения (2 или 3 соответственно) и устанавливает флаг переноса (C) в 1. При этом C фактически содержит остаток от деления. Такое использование операций сдвига требует контроля за флагом переполнения (O). Для простоты, рассмотрим контроль флагов на 4-битных знаковых переменных:
Примеры с 4-битными операндами:
Умножение на 2:
SAL(0111) = 7; результат = 1110 = -2, ошибка, должно быть 14, CF = 0, OF = 1
Деление на 2:
SHR(1000) = -8; результат = 0100 = 4, ошибка, должно быть -4, CF = 0, OF = 1
Деление на 2:
SAR(1000) = -8; результат = 1100 = -4, абсолютно точно, CF = 0, OF = 0
Деление на 2:
SAR(0111) = 7; результат = 0011 = 3, остаток 1 (см. флаг C), абсолютно точно, CF = 1, OF = 0
Пример программы
#include
#include
int main( void )
{
int rc = 0;
__mpu_init();
__mpu_extra_warnings = 1;
{
mpu_int8_t c, a;
int nb = NB_I8;
__mpu_char8_t sc[32], sa[32];
iatoi( a, "0b10110111", nb ); /* evaluate the A variable */
ishl( c, a, nb );
iitoa( sa, a, RADIX_BIN, LOWERCASE, nb ); /* convert A value to ASCII string SA */
iitoa( sc, c, RADIX_BIN, LOWERCASE, nb ); /* convert C value to ASCII string SC */
printf( "a = %s;\n", sa ); /* c = 0b10110111; */
printf( "c = %s;\n", sc ); /* c = 0b01101110; */
printf( "carry = %d;\n", __mpu_gtc() ); /* Carry Flag */
printf( "overflow = %d;\n", __mpu_gto() ); /* Overflow Flag */
}
__mpu_free_context();
return( rc );
}
Циклические сдвиги
#include
void irol( mpu_int *c, mpu_int *a, int nb );
void iror( mpu_int *c, mpu_int *a, int nb );
void ircl( mpu_int *c, mpu_int *a, int nb );
void ircr( mpu_int *c, mpu_int *a, int nb );
Для операций циклического сдвига на один бит входным операндом служит переменная размером nb байт, находящаяся по адресу a. Результат помещается в переменную того же размера, расположенную по адресу c. Пространства, занимаемые входной и выходной переменной в памяти могут пересекаться, как частично, так и полностью, что не влияет на правильность получаемого результата. Содержимое переменной, расположенной по адресу a останется неизменным после выполнения операции, если занимаемое ею пространство не пересекается с пространством, занимаемым переменной c.
При выполнениии операций циклического сдвига, флаг переноса (C) всегда содержит значение последнего выдвинутого бита. Существуют следующие виды операций циклического сдвига:
ROL — циклический сдвиг влево на один бит.
ROR — циклический сдвиг вправо на один бит.
RCL — циклический сдвиг влево на один бит с участием флага переноса.
RCR — циклический сдвиг вправо на один бит с участием флага переноса.
Следующие таблицы иллюстрируют выполнение операций irol (), iror ().
+----------------+---+-------------------+------------+
| ROL(<<): | C | значение операнда | заполнение |
+----------------+---+-------------------+------------+
| до операции | | 10110111 | sign(1) |
+----------------+---+-------------------+------------+
| после операции | 1 | 01101111 | |
+----------------+---+-------------------+------------+
+----------------+------------+-------------------+---+
| ROR(>>): | заполнение | значение операнда | C |
+----------------+------------+-------------------+---+
| до операции | low bit(1) | 10110111 | |
+----------------+------------+-------------------+---+
| после операции | | 11011011 | 1 |
+----------------+------------+-------------------+---+
Операции irol () и iror () (аналогично с командами сдвига isal (), isar (), ishl (), ishr ()) действуют на флаг переполнения (O) контролируя изменение знака операнда. Так же выставляются флаги четности (P), знака (S) и нуля (Z). Флаг переноса из младшей тетрады (A) не определяется и просто сбрасывается в 0.
В операциях ircl () и ircr () в сдвиге участвует флаг переноса (C). Выдвигаемый из операнда бит заносится во флаг переноса (C) после того, как предыдущее значение C поступит в освободившуюся позицию.
+----------------+---+-------------------+------------+
| RCL(<<): | C | значение операнда | заполнение |
+----------------+---+-------------------+------------+
| до операции | 0 | 10110111 | C(0) |
+----------------+---+-------------------+------------+
| после операции | 1 | 01101110 | |
+----------------+---+-------------------+------------+
+----------------+------------+-------------------+---+
| RCR(>>): | заполнение | значение операнда │ C │
+----------------+------------+-------------------+---+
| до операции | C(0) | 10110111 | 0 |
+----------------+------------+-------------------+---+
| после операции | | 01011011 | 1 |
+----------------+------------+-------------------+---+
Операции ircl (), ircr () воздействуют на флаги переноса (C) и переполнения (O) аналогично всем предыдущим операциям. Флаг переноса из младшей тетрады (A) не определяется и просто сбрасывается в 0.
Выставление флагов четности (P) и нуля (Z) операциями ircl (), ircr () несколько отличается от предыдущих типов операций. Это относится и к флагу знака (S). Так как операции ircl (), ircr () могут использоваться для сдвигов нескольких слов совместно с другими командами сдвига и, как правило, завершают такие составные операции, хотелось бы, после их завершения, иметь состояние флагов P и Z соответствующим не только последнему сдвигаемому слову, а целиком всей сдвигаемой комбинации слов.
Для этого операция ircr () выставляет свои флаги (P, S), а операция ircl () не определяет свои флаги (P, S) и не изменяет их предыдущих значений. Флаг нуля (Z) обеими операциями (ircl (), ircr ()) выставляется по Логическому И (AND) с предыдущим значением флага Z, то есть:
+----------------+----------------+----------------+
| | Значение Z, | |
| Предыдущее | полученное | Результирующее |
| значение Z | в результате | значение Z |
| | операции | |
+----------------+----------------+----------------+
| 1 | 0 | 0 |
+----------------+----------------+----------------+
| 1 | 1 | 1 |
+----------------+----------------+----------------+
| 0 | 0 | 0 |
+----------------+----------------+----------------+
| 0 | 1 | 0 |
+----------------+----------------+----------------+
Таким образом, если все слова, участвующие в комбинации сдвигов, дали нулевой результат, то и флаг Z останется в значении ИСТИНА, иначе, если хотя-бы одно слово дало ненулевой результат, флаг Z останется в состоянии ЛОЖЬ.
Примеры операций:
Сдвиг пары слов вправо:
isar (11111101), ircr (10011010); после isar () результат = 11111110, C=1, O=0, P=1, Z=0; после ircr () результат = 11001101, C=0, O=0, P=0, Z=0. Общий результат: 1111111011001101. Состояние флагов после операции ircr () гарантированно принадлежит ко всей комбинации двух слов (размером по 8 бит каждое).
Сдвиг пары слов влево:
ircl (00111000), isal (10111111); после isal () результат = 01111110, C=1, O=1, P=1, Z=0; после ircl () результат = 01110001, C=0, O=0, P=1, Z=0. Общий результат: 0111000101111110. Состояние флагов после операции isal () гарантированно принадлежит ко всей комбинации двух слов (размером по 8 бит каждое). Заметим, что флаг P выставлен операцией isal () и не изменялся операцией ircl (), флаг Z получен по AND между Z от isal () и Z от ircl ().
Пример программы
#include
#include
int main( void )
{
int rc = 0;
__mpu_init();
__mpu_extra_warnings = 1;
{
mpu_int8_t c, d, a, b;
mpu_int16_t z;
mpu_int *pz = NULL;
__mpu_char8_t s[32];
pz = (mpu_int *)&z;
#if ( MPU_BYTE_ORDER_BIG_ENDIAN == 1 )
++pz;
#endif
iatoi( b, "0b10111111", NB_I8 ); /* low part */
iatoi( a, "0b00111000", NB_I8 ); /* high part */
__mpu_clc(); /* clear Carry Flag */
isal( d, b, NB_I8 );
ircl( c, a, NB_I8 );
icpy( pz, d, NB_I8, NB_I8 ); /* low part of Z */
#if ( MPU_BYTE_ORDER_BIG_ENDIAN == 1 )
--pz;
#else
++pz;
#endif
icpy( pz, c, NB_I8, NB_I8 ); /* high part of Z */
iitoa( s, z, RADIX_BIN, UPPERCASE, NB_I16 ); /* convert Z value to ASCII string S */
printf( "z = %s;\n", s ); /* z = 0B0111000101111110; */
printf( "carry = %d;\n", __mpu_gtc() ); /* Carry Flag */
printf( "overflow = %d;\n", __mpu_gto() ); /* Overflow Flag */
}
__mpu_free_context();
return( rc );
}
Мы рассмотрели операции сдвига на один бит. Однако в библиотеке LibMPU предусмотрены также сдвиги на N-бит.
Библиотека LibMPU насчитывает большое количество операций с целыми, вещественными и комплексными переменными. Если пользователю помимо man (7) страниц будет необходима дополнительная информация о работе функций библиотеки, он всегда может обратиться к исходному коду и заголовочному файлу libmpu.h.
Контакты: