Программа 38. Внутреннее представление float
Программа позволяет исследовать внутреннее представление чисел с плавающей точкой, которое было описано в параграфе 13.7.
Чтобы получить значения отдельных двоичных разрядов внутреннего представления, следует использовать побитовые операторы, однако их нельзя применять непосредственно к числам с плавающей точкой. Поэтому используется объединение, включающее поле типа float, поле типа массив из 4-х символов и целое поле long, которое, как и float, имеет размер 4 байта.
// Файл InnPrsnt.cpp
typedef union{ // Объединение можно рассматривать
float f; // или как float
char str[sizeof(float)]; // или как массив символов
unsigned long l; // или как long
}float_chars_long;
С помощью typedef объединению присвоено имя float_chars_long. Отводимые объединению 4 байта можно рассматривать либо как число с плавающей точкой, либо как массив символов, либо как длинное целое. Получая, например, двоичное представление длинного целого, мы получим одновременно представление float.
Функция prn_long_bin выводит двоичные цифры для целого числа типа long.
// Продолжение файла InnPrsnt.cpp
#include <iostream.h>
// prn_long_bin: печать двоичного представления long
void prn_long_bin(unsigned long x)
{
int i,
n = sizeof(unsigned long) * 8; // n – число двоичных разрядов в long
unsigned long h;
h = 1L << n - 1; // В старшем разряде у h 1 и 0 в остальных
for(i = 1; i <= n; i++){
cout << ((x & h) != 0) << (i % 8 ? "" : " ");
x <<= 1;
}
}
Длинная целочисленная константа 1L представляется в памяти единичкой в младшем правом разряде. После ее сдвига на n-1 разряд влево, получается величина h, у которой имеется только одна единичка в старшем левом разряде, остальные разряды нулевые. Результат побитового сравнения
x & h
будет отличен от нуля, если x также имеет в старшем разряде 1. В этом случае на экран выводится 1, в противном случае выводится 0. После каждого сравнения x сдвигается влево. Для большей наглядности содержимое одного байта отделяется от другого пробелами путем печати выражения
(i % 8 ?"" : " "),
в котором i есть номер разряда. Когда остаток от деления номера i на 8 равен нулю, выводится пробел, в остальных случаях пустая строка.
Программа вводит число с плавающей точкой, печатает его, а также печатает содержимое 4-х байтов, отводимых под него, в шестнадцатеричной и в двоичной форме.
// Продолжение файла InnPrsnt.cpp
#include <conio.h>
void main()
{
float_chars_long var;
char c, i;
clrscr();
cout<<"\n Внутреннее представление чисел с плавающей точкой \n";
cout << "\nЧисло As hex As bin\n";
while((cin >> var.f) != NULL){ // Пока вводятся числа
gotoxy(1, wherey() - 1); // Перевод курсора в начало
// предыдущей строки экрана
// wherey() возвращает номер строки, в которой находится курсор
cout << var.f << '\t'; // Печать числа
for(i = 1; i <= sizeof(float); i++){ // Печать содержимого байтов,
c = var.str[sizeof(float) - i]; // отведенных числу
cout << hex << ((c & 0Xf0) >> 4) << (c & 0X0f) << ' ';
}
cout.put('\t'); // Вывод табуляции для выравнивания
prn_long_bin(var.l); // Печать двоичного представления
cout << endl;
}
getch();
}
При печати шестнадцатеричного представления объединение рассматривается как массив символов. Так как старшие разряды двоичного представления чисел располагаются в байтах с большими адресами, печать производится, начиная с символа массива с наибольшим индексом. Содержимое каждого байта печатается в виде двух 16-разрядных цифр, начиная со старшего полубайта. В выражении
(c & 0Xf0) >> 4
из символа с вырезаются 4 старших разряда и прижимаются к правому краю. С помощью выражения
c & 0X0f
получаются 4 младших бита символа c. После печати содержимого каждого байта выводится пробел. Эти действия обеспечивают печать внутреннего представления числа в принятой форме записи слева направо.
Входной поток настраивается на печать целых в шестнадцатеричном виде благодаря посылке в него величины
hex,
определенной в iostream.h. Сделанная настройка действует до явного ее изменения. Величина hex является манипулятором. Так называются специальные функции, которые позволяют управлять форматом выводимых чисел. Кроме hex, имеется еще манипуляторы:
dec – использование десятичной системы при вводе и выводе;
oct – использование восьмеричной системы при вводе и выводе.
После того как число набрано, следует нажать Enter, что приводит в переводу курсора на следующую строку. Чтобы диалог с программой выглядел более аккуратно, курсор переводится в начало той строки, где набиралось вводимое число с использованием функции
int wherey(),
возвращающей номер строки, в которой расположен курсор. Позицию курсора в строке выдает функция
int wherex().
Для завершения работы с программой следует нажать Ctrl+Z и Enter.
Далее приводится листинг, выданный программой:
Печать внутреннего представления чисел с плавающей точкой
Число As hex As bin
1 3f 80 00 00 00111111 10000000 00000000 00000000
2 40 00 00 00 01000000 00000000 00000000 00000000
5.3 40 a9 99 9a 01000000 10101001 10011001 10011010
0.1 3d cc cc cd 00111101 11001100 11001100 11001101
0.3 3e 99 99 9a 00111110 10011001 10011001 10011010
10 41 20 00 00 01000001 00100000 00000000 00000000
-1 bf 80 00 00 10111111 10000000 00000000 00000000
-2 c0 00 00 00 11000000 00000000 00000000 00000000
-4 c0 80 00 00 11000000 10000000 00000000 00000000
4.5 40 90 00 00 01000000 10010000 00000000 00000000
Из листинга видно, что число 5.3 округлено с избытком, так как старший отброшенный разряд мантиссы есть 1.
Битовые поля
Имеется возможность размещать в одном слове несколько объектов, что позволяет экономить память. Если известен диапазон значений некоторой величины, то нетрудно оценить количество бит для ее кодирования. Совокупность бит, необходимая для кодирования величины, называется полем. Битовые поля используются в качестве элементов структур. При объявлении таких элементов указывается их ширина в битах. Ширина битового поля не может превышать ширину слова, но структура, объединяющая битовые поля, может занимать несколько слов. Безымянные битовые поля служат для пропуска нескольких разрядов. Ширина, равная нулю, используется, когда требуется выйти на границу следующего слова. Битовые поля размещаются в слове, начиная с его младших разрядов. С битовыми полями можно работать как с малыми целыми числами.
О бинарных файлах
Для того, чтобы открыть файл как бинарный, функцию open следует вызвать с двумя аргументами. Первый аргумент задает имя открываемого файла, а второй определяет режим работы с файлом. Таких режимов может быть три: чтение, запись, чтение и запись (изменение). В файле fstream.h определены специальные константы – флаги, имеющие по одной единичке в своих разрядах, которые можно использовать для указания режима работы с файлами:
ios::in – открыть файл на чтение,
ios::out – открыть файл на запись,
ios::binary – открыть файл как бинарный, а не как текстовый.
Данные флаги можно комбинировать с помощью побитового логического оператора ИЛИ (|).
Для чтения из бинарного потока применяют функцию:
read(char *str, size_t count);
которая читает из потока count байтов и сохраняет их в массиве, начало которого указывает str.
Для записи в бинарный поток служит функция:
write(const char *str, size_t count);
направляющая в поток count байтов из массива, на который указывает str.
Позиции в файле нумеруются с нуля, поэтому поток можно представлять себе как массив. С каждым файловым потоком связан текущий указатель файла, указывающий на байт, который будет прочитан или записан при следующей операции ввода/вывода. Положением текущего указателя в выходном потоке можно управлять с помощью функции:
ostream& seekp(long offs, seek_dir dir);
Текущую позицию во входном потоке можно изменить функцией
istream& seekg(long offs, seek_dir dir);
Первый параметр offs этих функций задает число позиций, на которое надо переместить текущий указатель, а второй параметр dir назначает точку отсчета, от которой надо произвести смещение. Для указания точки отсчета можно использовать перечислимые константы из файла iostream.h:
enum seek_dir{ beg, cur, end };
beg – смещаться от начала файла,
cur – смещаться от текущей позиции,
end – смещаться от конца файла.
Например, в программе 39 открывается файловый поток f на чтение и запись. Текущий указатель перемещается в начало файла следующей инструкцией:
f.seekg(0, ios::beg);