Достоинства формы представления чисел с плавающей точкой

Представление вещественных чисел в памяти ПК

В дальнейшем будем рассматривать тип double

Пример. X=3.5 = 11.12 = (-1)0 *1.11*21

P=P+1023 P’ = 1+1023 =1024

Достоинства формы представления чисел с плавающей точкой - student2.ru Достоинства формы представления чисел с плавающей точкой - student2.ru p’=1024 f=0.11

7 6 5 4 3 0

16-ый код: 40 0С 00 00 00 00 00 00

Число –3.5: С0 0С 00 00 00 00 00 00

Диапазон чисел, представимых в формате с плавающей точкой (тип double): |X|min<=|X|<=|X|max и X=0 1<=P’<=2046 (для чисел X ≠0)

|M|min*2pmin <=|X| <=|M|max*2pmax и X=0

1*2-1022<=|X| <=(2-2-52)*21023 и X=0

10k<=|X| <=10L lg2=0.30103

k= –1022*lg 2= –307.65266= -308+0.34734

L= 1024* lg 2=1024*0.30103=308.25472

2.2*10-308<=|X| <=1.7*10308 и X=0

__[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\] _____|_____[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\]______

Xmax -Xmin 0 Xmin Xmax

Точность чисел, представленных в формате с плавающей точкой: часто вместо числа Х в МС хранится его приближение Х*. Погрешность вносится из-за хранения приближенного значения мантиссы.

Абсолютная погрешность числа Х*:

|X – X*| = ∆( Х*) = |MX –MX*|*2px = 2-52*2px

т.е. абсолютная погрешность числа зависит от порядка числа. Обычно для формата с плавающей точкой определяют относительную погрешность Х* : d(Х*)

Для пользователя более важным является практический вопрос: сколько значащих цифр десятичного представления числа гарантированно сохраняются при таком формате хранения числа. Есть приближенное правило для определения этого количества К цифр при q=2:

m двоичных разрядов мантиссы соответствуют К десятичным цифрам:

К»[m / 3.32]; Для типа double К=[53/3.32]=15.96, т.е. для значения типа

doubleсохраняется в памяти 15-16 десятичных знаков.

Выполнение операций над числами, представленными с плавающей точкой (говорят- в плавающей арифметике).

Пусть X = Mx×2Px ,а Y = My×2Py

a)Сложение (вычитание) чисел:

Z = X ± Y = Mx×2Px ± My×2Py

={1шаг–выравнивание порядков к большему; пусть Px >Py}

= 2Px×(Mx ± My×2PyPx)

={2 шаг–сдвиг мантиссы My на |Py–Px| разрядов}

= 2Px×mz

= {3шаг–сложение(вычитание) мантисс, получается мантисса mz}.

= Mz ×2Pz

Возможны случаи:

· 1 £ |mz| < 2 Þ операция закончена, Mz = mz ; Pz = Px ;

· 2 £ |mz|, но |mz| <4

Þ выполняется нормализация результата сдвигом мантиссы вправо на 1 разряд с коррекцией порядка (+1);

· |mz| <1 Þ выполняется нормализация результата сдвигом мантиссы влево на t разрядов с коррекцией порядка (–t).

з pх 1.ххххх

– з py 1.xxyyy пусть pх == py

з pх 0.00zzz При вычитании близких чисел

з pz 1.zz???происходитпотеря точности

б)Умножение :

Z = X × Y = Mx×2Px × My×2Py = Mx×My×2Px+Py == Mz ×2Pz , где Mz = Mx×My ; Pz = Px+Py; т.е. при умножении чисел их мантиссы перемножаются, а порядки складываются. При умножении двух мантисс может получиться результат такой, что потребуется сдвиг мантиссы вправо, но не более, чем на один разряд, тогда нужна коррекция порядка (+1).

в)Деление :

Достоинства формы представления чисел с плавающей точкой - student2.ru т.е. при делении чисел их мантиссы делятся, а порядки вычитаются. При делении двух мантисс может потребоваться для полученной мантиссы сдвиг влево, но не более, чем на один разряд с коррекцией порядка (–1).

Особые ситуации плавающей арифметики.

1. Переполнение порядка при выполнении операций плавающей арифметики;

2. некорректность деления в плавающей арифметике: деление на число с нулевой мантиссой;

3. потеря значимости: Pz ¹0, а Mz = 0;

4. исчезновение порядка: Pz < Pmin , а Mz ¹ 0.

Две последние ситуации не являются аварийными, они обычно приводят к тому, что результат Z заменяется нулём – это машинный нуль.

Достоинства формы представления чисел с плавающей точкой.

· Сравнительно широкий диапазон чисел;

· Хранение только значащих цифр числа. Представление обеспечивает для числа максимальную точность при фиксированной разрядной сетке.

Недостатки формы представления чисел с плавающей точкой:

· более сложная конструкция схем, выполняющих операции над числами в процессоре: необходимы отдельные схемы для обработки мантисс, порядков, знаков.

Замечание. Арифметика для формы представления с плавающей точкой имеет некоторые "нехорошие " свойства:

· результаты операций получаются, как правило, с погрешностью, поэтому сравнивать два вещественных значения a и b на точное совпадение (a == b) не имеет смысла; проверяют обычно их близость с некоторой точностью e: | a – b| < e;

· вычитание близких чисел приводит к потере точности, поэтому рекомендуется его избегать;

· если a >> b, то их сложение может дать результат a + b = a, а следовательно значение суммы

a + b1 + b2 + … + bn , где bi << a при всех i = 1, 2, …, n, может зависеть от порядка действий.

Объединения.

Объединение (union) – частный случай структуры, включает данные разных типов. Особенность объединения состоит в том, что все поля его располагаются по одному и тому же адресу, т.е. все элементы объединения при размещении в памяти имеют одно и тоже нулевое смещение от начала.

Размер объединения равен максимальной из длин его полей.

Описание объединения напоминает описание структуры:

union [имя типа] {описание полей} [список имён] ;

Список имён может содержать имена переменных, указатели,массивы.

Имя типа указывать необязательно,тогда надо указать элемент(ы) в списке имён. Можно указать и то и другое.

Пример.

union ch // ch - имя типа {double x; сhar s[8]; };   union // имени типа нет {double x; сhar s[8]; }q; //переменная q-объединение  

Если введён тип, то можно определять (аналогично структурам):

ch v,w[4]; //переменные, массивы

ch *pch; //указатели

Обращение к элементу объединения:

Имя_объединения . имя_элемента

Указатель на объединение -> имя_элемента

*( Указатель на объединение). имя элемента

Примеры: v.x v.s[i] pch->x *(pch).x

Занести значение в объединение можно присвоив его элементу это значение: q.x=1.57E-2;

Назначение объединения-обеспечить возможность доступа к одному и тому же участку памяти с помощью разных типов. Это позволяет, например, задав значение вещественного числа x, посмотреть (и вывести) содержимое его отдельных байтов s[i], и таким образом получить внутреннее представление вещественного x. Для вещественного данного такой доступ к отдельным байтам невозможен.

//Внутреннее представление вещественных данных

#include <iostream>

#include <iomanip>

using namespace std;

typedef double tip; // рассматриваемый тип обозначим tip

const int L=sizeof(tip); // размер типа в байтах

union {

tip a;

unsigned char u[L];

}q; // q- включает вещ.данное и массив

//байтов размера, равного размеру типа tip

// здесь необходимо учитывать, что значение a в памяти хранится

//в перевёрнутом виде: от младшего байта к старшему,

//внутренность байта не переворачивается.

//Байты строки u в памяти хранятся в порядке u[0],u[1],…u[L-1]

//т.е. u[0]наложится на младший байт a,…u[L-1] на старший байт

//a, поэтому вывод надо начинать с u[L-1], затем u[L-2],… ,u[0], либо строку S формировать с конца.

void binkod (tip n, char s[])

{//функция формирует строку s с двоичным представлением //вещественного данного n.

int i, j , m;

i=0; //индекс байта строки u.

q.a = n; //значение n помещается в объединение q

m=L<<3 –1; //индекс последнего бита строки s.

do

{unsigned char x;

x=q.u[i++]; //запись в x очередного i-го байта из u

for( j=0; j<8; j++) // цикл формирует биты от 0 до7

{s[m--] = (x&1)+'0'; // i-го байта строки s.

x=x>>1;

}

}

while (i<L);

s[L<<3]=0; //нуль символ в конец строки s.

}

//Внутреннее 16-ричное представление целого.

void hexkod (tip n, char s[ ])

{int i,m;

unsigned char x;

char alf[ ]="0123456789ABCDEF";//алфавит 16-ой системы счисления

i=0; m=L<<1 –1;

do

{x=q.u[i++];

s[m--]=alf[x&15]; // младшая 16-ричная цифра i-го байта u

s[m--]=alf[(x>>4)]; // старшая 16-ричная цифра i-го байта u

}

while (i<L);

s[L<<1]='\0'; //нуль символ в конец строки s.

}

int main ()

{double x;

char t[L<<3+1];

cout<< "\n double—x= ";

cin>> x;

binkod(x,t); cout<< x<< ": \n"<<t<<endl;

hexkod (x,t); cout<< x<< ": "<<t<<endl;

return 0;

}

Выполнение этой программы

double—-x= 3.5

3.5:

3.5: 400C000000000000

Замечание 1. Этот способ доступа к значению типа tip (с union ) может быть применён для значения другого простого типа, например, int. Это более универсальный способ. Для использования этой программы для типа int достаточно изменить одно предложение:

typedef int tip;

В главе «Битовые операции» для получения внутреннего кода данных типа int был рассмотрен другой способ с использованием битовых операций и операций сдвига. Тот способ не приемлем для вещественных данных, так как для них не определены битовые операции и операции сдвига.

Замечание 2. Шаблон функции.

Поскольку алгоритм вывода внутреннего кода данного можно использовать для данных разных типов, возникает желание сделать тип параметром этого алгоритма. В языке С++ есть средство параметризации – шаблоны. С помощью шаблона функции можно определить алгоритм, которому конкретный тип передаётся в виде параметра на этапе компиляции, при этом компилятор генерирует код функции, соответствующий этому типу. Это называется инстанцированием шаблона. Тип параметра для инстанцирования задаётся при вызове функции либо явным образом, либо определяется автоматически по типу аргумента. Код функции при повторном вызове с тем же параметром не создаётся снова.

Простейшая функция-шаблон с параметром tip имеет вид:

template <class tip> заголовок_функции

{/*тело функции*/}

Пример шаблона функции с параметром tip для получения строки внутреннего двоичного кода данного этого типа.

#include <iostream>

using namespace std;

template <class tip>

void binkod (tip n,char s[])

{//функция формирует строку s с двоичным представлением //вещественного данного n.

const int L=sizeof(tip); // размер типа в байтах

union {

tip a;

unsigned char u[L];

}q;

int i,j,m;

i=0; //индекс байта строки u.

q.a=n; //значение n помещается в объединение q

m=L<<3–1; //индекс бита строки s.

do

{unsigned char x;

x=q.u[i++]; //запись в x очередного байта из u

for( j=0;j<8;j++) // цикл формирует биты от 0 до7

{s[m--]=(x&1)+'0'; // m-го бита строки s.

x=x>>1;

}

}

while (i<L);

s[L<<3]=0; //нуль символ в конец строки s.

}

template <class tip>

void hexkod(tip n,char s[])

{//Внутреннее 16-ричное представление целого.

const int L=sizeof(tip);

union {

tip a;

unsigned char u[L];

}q;

int i,m;

unsigned char x;

char alf[]="0123456789ABCDEF";

q.a=n;

i=0; m=L<<1–1;

do

{x=q.u[i++];

s[m--]=alf[x&15];

s[m--]=alf[(x>>4)];

}

while (i<L);

s[L<<1]='\0';

}

Int main ()

{const int Q=sizeof(long double);

int n;

double u;

float v;

bool p;

char t [Q<<3 +1];

cout<< "\n int--n ";

cin>> n;

cout<< "\n double--u ";

cin>> u;

cout<< "\n float--v";

cin>> v;

p=true;

//Вызовы функций

binkod( n,t); //либо binkod<int>(n,t);

cout<< n<< ": "<<t<<endl;

hexkod(n,t); //либоhexkod<int>(n,t);

cout<< n<< ": "<<t<<endl;

binkod( u,t); //либо binkod<double>(u,t);

cout<< u<< ": "<<t<<endl;

hexkod(u,t); //либоhexkod<double>(u,t);

cout<< u<< ": "<<t<<endl;

binkod( v,t); //либо binkod<float>( v,t);

cout<< v<< ": "<<t<<endl;

hexkod(v,t); //либоhexkod<float>(v,t);

cout<< v<< ": "<<t<<endl;

binkod( p,t); //либо binkod<bool>(p,t);

cout<< p<< ": "<<t<<endl;

hexkod(p,t); //либоhexkod<bool>(p,t);

cout<< p<< ": "<<t<<endl;

return 0; }

Результаты работы программы:

1023: 00000000000000000000001111111111

FF

-3.5: 1100000000001100000000000000000000000000000000000000000000000000

-3.5: C00C000000000000

2.5: 01000000001000000000000000000000

2.5: 40200000

1: 00000001

1: 01

//вывод 16-го кода с использованием манипуляторов

cout.precision(8);

q.a=-3.5;

cout.fill('0');

for(int i=7;i>=0;i--)

cout<<hex<<setw(2)<<int(q.u[i])<<' ';

cout<<endl;

//Вывод минимального и максимального значений

cout<<"min - max"<<endl;

for(int i=0;i<6;i++)

q.u[i]=0;

q.u[6]=16;

q.u[7]=0;

cout<<q.a<<" ";

for(int i=0;i<6;i++)

q.u[i]=255;

q.u[6]=239;

q.u[7]=127;

cout<<q.a<<endl;

#include <iostream>

using namespace std;

struct xx

{float y;

int p;

};

//Внутреннее представление структуры типа XX

typedef xx tip;

const int L=sizeof(tip);

union {

tip a;

unsigned char u[L+1];

}q;

void inbitreal (tip n,char s[])

{//Внутреннее 2-ое представление структуры типа XX

int i,j,m;

i=0; //0-oй байт

q.a=n; m=L<<3–1;

do

{unsigned char x;

x=q.u[i];

for( j=0;j<8;j++)

{s[m--]=(x&1)+'0';

x=x>>1;

}

i++;

}

while (i<L);

s[L<<3]=0;

}

void inreal16(tip n,char s[])

{//Внутреннее 16-ричное представление целого.

int i, m;

unsigned char x;

char alf[]="0123456789ABCDEF";

i=0; m=L<<1–1;

do

{x=q.u[i++];

s[m--]=alf[x&15];

s[m--]=alf[(x>>4)];

}

while (i<L);

s[L<<1]='\0';

}

int main ()

{xx n;

char t[L<<3+1];

n.y=1.5; n.p=123;

inbitreal(n,t); cout<< n.y<<” ”<<n.p<< ": "<<t<<endl;

inreal16(n,t); cout<< n.y<<” ”<<n.p<< ": "<<t<<endl;

return 0;

}

Наши рекомендации