русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

Представление вещественных чисел в памяти компьютера.


Дата добавления: 2015-06-12; просмотров: 3251; Нарушение авторских прав


Рассматриваемая тема не обязательно связана с объединением, она представляет также самостоятельный интерес. Здесь этот материал нужен для понимания некоторых примеров этого и последующих параграфов.

Вещественное число (число с плавающей запятой) состоит из двух частей: мантиссы и порядка. Например, число в десятичной системе счисления 0,000123 можно записать одним из следующих способов: 0.0000123*10; 0,123*10-3; 1,23*10-4 и т.д. Аналогично 78900=0,789*105=78,9*103 и т.д. Термин “число с плавающей запятой” и связан с тем, что десятичная запятая перемещается (плывёт) по числу. Из такого рода различных записей в десятичной системе счисления нас будет интересовать нормализованное число, соответственно 0,123*10-3 и 0,789*105. Первая его часть называется мантиссой (0,123 и 0,789), а числа -3 и 5 – порядком.

Аналогично различные варианты записи (на бумаге, а не в памяти компьютера) вещественного числа имеют место и в двоичной системе счисления. Например, рассмотрим десятичное число 12,375. Для его перевода в двоичную систему счисления отдельно переводим целую часть (см. гл. 4 файла Lections1Semestr) и отдельно дробную часть. В качестве вспомогательной системы счисления можно использовать шестнадцатеричную. Для перевода дробной части из 10 с.с в 16 с.с выполняем следующее:

дробную часть числа умножаем на 16;

полученную целую часть результата (число от 0 до 15) переводим в 16-ю с.с и берём в качестве первой после запятой 16-й цифры результата;

дробную часть результата, если она не равна нулю, повторно умножаем на 16;

полученную целую часть переводим в 16-ю с.с и берём в качестве следующей 16-й цифры;

дробную часть результата снова умножаем на 16;

это продолжаем, пока не наступит одна из следующих ситуаций:

a) на некотором шаге, не обязательно в самом начале, получим в дробной части нуль. В этом случае перевод выполнили точно. Это имеет место в нашем примере: 0,375*16=6.0;



b) получим в дробной части число, которое было раньше. Например, 0,15*16=2,4; 0,4*16=6,4. Если продолжать умножение 0,4*16, будем получать одно и то же, т. е 6,4. В таком случае получаем следующий результат: 0,1510= 0,2666…16=0,2(6)16. Круглые скобки означают, что записанное в них одно или несколько разных чисел будут повторяться бесконечное число раз. Говорят, что это число в периоде, т.е. 6 в периоде;

c) если не получаем ни нуль, ни повторяющиеся числа, то ограничиваемся заданным предварительно количеством двоичных или шестнадцатеричных цифр. Для числа типа float необходимо получить 24 двоичные цифры, считая от первой значащей, или не менее 7 шестнадцатеричных цифр, не считая первые 16-е нули.

Для перевода дробной части из 16-й в 2-ю с.с. записываем каждую 16-ю (но не 10-ю!) цифру в виде тетрады, т.е. четырёх двоичных цифр. Получим 12.37510=С.616=1100,0110. При этом последнюю цифру ‘0’ можем не писать. Как и в 10-й с.с., этот нуль незначащий. Остальные нули рядом с десятичной запятой обязательны!

Это двоичное число, как и в 10-й с.с., записать можно по-разному: 11,00011*22; 1100011*2-3; 1.100011*23. Из приведенных вариантов нас будет интересовать последняя нормализованная запись, в которой в целой части записана одна первая значащая единица. Получим: m= памяти не хранится, но 1.100011; p=310=112, где m —нормализованная мантисса, p — порядок в 2 с.с.

Пусть число объявлено как float. Тогда 4 байта (32 бита) распределяются следующим образом:

один самый “левый” бит отводится под знак мантиссы, или, что то же самое, под знак всего числа. Записывается 0, если мантисса, а, значит и само вещественное число, положительное, и 1 в противном случае. Никакого дополнительного кода для отрицательного вещественного числа, как это было для целых чисел, получать не надо;

следующие 8 разрядов (бит) занимает изменённый порядок записи числа в 2-й с.с., который называется характеристикой числа. Обозначим её x. Знак порядка нигде не хранится. Чтобы он всегда был неотрицательным, порядок увеличивается на 12710, т. е. x=p+12710=p+7F16. (1)

Для нашего примера здесь будет храниться число x=310+12710= 13010=8216=100000102. Это же можно вычислить и так: x=316+7F16=8216 =100000102 ;

последние 23 (32-1-8) разряда занимает мантисса. При этом целая её часть, равная 1, в памяти не хранится, но учитывается при вычислениях. Если дробная часть числа переведена в 16-ю, а, значит и в двоичную с.с не точно, т. е. имели место варианты b) и c) (см. выше перевод), последняя 2-я цифра округляется по обычным правилам. Если первая отбрасываемая 2-я цифра равна 1, то прибавляем двоичную единицу, в противном случае оставляем без изменения.

Таким образом, число 12,375 в формате float будет представлено следующим образом: 01000001010001100000000000000000. Иногда в литературе можно встретить шестнадцатеричную запись этого результата: 4146000016.

Упражнение. Представить число -0.01 как число с плавающей точкой в формате float.

Переводим модуль числа в шестнадцатеричную, а затем в двоичную системы счисления описанным выше способом:

0.01= 0.028F5С28F5C…16=0.0(28F5С)16=0.0000001010001111010111000010100…2.

Так как под мантиссу отводится 23 разряда, то должны получить 25 двоичных цифр, не считая первых после десятичной точки подряд идущих нулей. Почему? По правилу нормализации самая первая значащая единица (в примере в десятичной цифре 2) в память не записывается, а ещё одна дополнительная двоичная цифра нужна для того, чтобы определить, как округлять число. Так как первая отбрасываемая двоичная цифра =0, то получаем

m=0.010001111010111000010102.

Если число “маленькое”, т.е. целая часть =0, а в дробной части после запятой несколько подряд идущих нулей, то получим отрицательный порядок. Так как 0.0000001010001111010111000010102 = 1.01000111101011100001010*2-7,

то p=-710=-716, x= p+7F16=7F16-716=7816=011110002.

В результате получим ответ: 10111100001000111101011100001010.

Рассмотрим обратную задачу. Пусть в ячейке размером 4 байта хранится следующая последовательность нулей и единиц, шестнадцатеричное представление которой такое: С215999A16. Известно, что здесь хранится вещественное число, т.е. в программе записано, например, объявление: float a. Что это за число в 10-й системе счисления?

Для ответа на этот вопрос в обратном порядке выполняем действия, описанные выше.

1) Запишем двоичное представление числа: 11000010000101011001100110011010.

2) Единица в старшем бите (самая “левая”) означает, что всё вещественное число отрицательное.

3) В следующих 8 битах находится характеристика числа, т.е. x=100001002=8416. Из формулы (1) получаем двоичный порядок числа: p=x-7F16=8416-7F16 =516=510.

4) Из последних 23 разрядов получаем m=0.001010110011001100110102.

5) Поэтому искомое число

a=1.00101011001100110011010*25=100101.0110011001100110102@25.(6) 16@37.410.

Перевод дробной части выполняли следующим образом:

0.(6) 16 = 0.6666616 = 6*16-1+6*16-2+6*16-3+6*16-4+6*16-5@0.410.

Т.к. это отрицательное число, то получаем ответ: - 37.410

5.2. Объявление объединения.(+)

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

Для изучения объединения сначала рассмотрим следующий код для работы со структурой.

Пример 1.

void main()

{

struct TS

{float f; int K;

} S={25.6, 22};

/* Совместное объявление структуры и инициализация её полей. */

printf("%d\n", sizeof(TS));

/* Выведем число 8 — объём занимаемой памяти для размещения всех полей структуры: 4 байта для вещественного числа и столько же для целого */

cout<<endl<<S.f; // Выведем число 25.6

cout<<endl<<hex<<S.K; // Выведем число 16

/* Манипулятор hex используется для ввода и вывода данных (здесь для вывода целого числа) в шестнадцатеичной системе счисления (2210=1616 )*/

/* Рассмотрим аналогичный код для работы с объединением. Объявим теперь тип объединения (TU) и переменную этого типа (U).: */

union TU1

{float f; int K;

} U1= {25.6};

/* Объявление объединения аналогично объявлению структуры, только вместо struct записывается union. Но инициализацию в объединении можно выполнить только для одного поля ! */

cout<< endl<<sizeof(U1)<<endl; // Выводится число 4

cout<<endl<<U1.f; // Число 25.6

cout<<endl<<hex<<U1.K; // Выводится 41cccccd

getch();

}

5.3. Сравнение объединения и структуры.(+)

 

Основное отличие объединения от структуры в том, что объединение позволяет нескольким переменным различных типов занимать один участок памяти. Его объём равен количеству байт, необходимых для размещения самого “длинного ” поля. В нашем примере оба поля имеют одинаковый размер и занимают четыре байта оперативной памяти. Переменная f и K размещаются в одной и той же области памяти. Содержимое этих четырёх байт рассматривается как вещественное число, если используем U.f или как целое число с идентификатором U.K. Число 25.6 в формате float в четырёх байтах будет представлено так: 01000001110011001100110011001101. А то, что получили при выводе U.K , т.е. 41cccccd —это тот же “набор нулей и единиц” в шестнадцатеричной системе счисления, так как при выводе использовали манипулятор вывода hex.

Рассмотрим пример 2.

union TU2

{int N; short n; char ch[2];

} U2= {0x12345678};

cout<< sizeof(U2)<<endl;

cout<<hex<<U2.N<<endl<<” “<<U2.n<<endl;

cout<<” “<<U2.ch[1]<<U2.ch[0]<<endl;

В результате получим:

4

12345678

5678

Vx

Анализ этого и других результатов показывает, что в современных системах С++ поля объединения выравниваются по правому краю. Выполнение оператора printf("%x %x", 'x', 'V'); показывает, что символ ‘x’ имеет код 7816, а 'V' — код 5616.

Одни и те же четыре байта в программе обозначаются следующим образом:

 

|----------------------------- U2.N-------------------------|

|------------- U2.n----------|

В последнем “правом” байте находится символ U2.ch[0], а в предпоследнем — символ U2.ch[1].

Элементом (полем) объединения может быть структура. Рассмотрим следующий пример 3.

union TU3 // Тип объединения

{ // Анонимное объявление структуры в объединении

struct

{ unsigned short lo;

unsigned short hi;

} w;

long N;

};

// Раздельное объявление переменной типа объединение.

TU3 U3; U3.N=0x12345678L; // или U3.N=0x12345678;

cout<< hex<<endl<<U3.N<<endl;

cout<<hex<<U3.w.lo<<endl;

cout<<hex<<U3.w.hi<<endl;

В результате получим:

12345678

5678

1234

Здесь четыре байта обозначаются следующим образом:

|----------------------- U3.N-------------------------------|

|-----------U3.w.hi----------|---------- U3.w.lo----------|

C помощью объединения можно, например, “посмотреть” каждый байт целого четырёхбайтного числа.

Пример 4.

union TU4

{ char ch[4];

long a; // b) short a; // c) unsigned a ;

} U4;

U4.a=0x30334142; // или U4.a=0x30334142L;

cout<<endl<<"Size of union "<<sizeof(U4)<<endl;

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

cout<<U4.ch[i]<<" ";

cout<< endl<< U4.a<<endl;

cout<<hex<< U4.a<<endl;

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

Рассмотрим следующее, например, анонимное объединение:

union

{int k; float f;};

f=25.6;

cout<<k;

Будет выведено 41cccccd (см. объединение U1). Этот пример показывает, что объединение чем-то похоже на ссылочный тип. Но в отличие от последнего одна и та же ячейка не просто по-разному называется. Её содержимое можно интерпретировать с разными типами данных.

Как и для структур, можно объявить и использовать ссылки и указатели на объединение.

Например, после объявления TU4 &SU=U4 к одной и той же области памяти из четырёх байт можно обращаться как с помощью идентификатора SU (SU.ch[i]), так и с помощью U4 (U4.ch[i]).

Объявим указатель на объединение:

TU3 *pU3=new TU3;

Тогда доступ к полям осуществляется, как и для указателя на структуру, с помощью операции ->. Например

pU3->N=0x12345678L;

cout<< hex<<pU3->N<<endl;

cout<<hex<<pU3->w.lo<<endl;

cout<<hex<<pU3->w.hi<<endl;

Заметим, что внутри объединения используется по-прежнему просто структура, а не указатель на неё. Поэтому остаётся и операция точка для доступа к полям вложенной структуры w.

 

 



<== предыдущая лекция | следующая лекция ==>
Cтруктуры и классы. | Поля битов (битовые поля)


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.371 сек.