Машинное представление чисел типа FLOAT

Для глубокого понимания работы компьютера желательно знание того, как различные числовые величины представляются в компьютере. Это т.н. машинное представление чисел. С целыми числами всё достаточно просто. Если число положительное, то оно стандартным образом переводится в двоичное и полученные двоичные разряды записываются в байты, выделенные для данной переменной. При этом самый старший бит (самый левый) при любом представлении целого числа равен нулю или единице в зависимости от знака числа 0/1 +/-. Для получения машинного представления целого числа со знаком минус производит следующие действия:

Знак минус временно забывается.
Это положительное число стандартным образом переводится в двоичное представление. Это т.н. исходный код.
все разряды исходного кода инвертируются (взаимообмен нулей и единиц) и это дает обратный код.
к обратному коду добавляется 1 и это дает дополнительный код, этот дополнительный код является двоичной записью исходного отрицательного числа (старший бит числа равен 1).

С плавающими числами всё значительно сложнее.
Ниже приводится программа на языке С, переводящая введенное плавающее типа FLOAT в его машинное представление или, наоборот, машинное представление числа в его запись в различных системах счисления.
В самой программе даются подробные комментарии всех действий.

Далее приводится вариант программы на языке С (компилятор BORLAND C++ 3.1)

Show »

//*******************************************************************
// // Программа работает в двух режимах:
// - переводит введенное число в его машинное представление 
// в тип FLOAT (32 двоичных разряда)
// - переводит машинное представление типа FLOAT в его
// двоичное, десятичное, 8 и 16-ти ричные представления.
// При переводе плавающего числа в машинное представление
// типа FLOAT/DOUBLE/LONG DOUBLE следует помнить следующее:
// - на всё число отводится 32/64/80 двоичных разряда
// - разряды нумеруются справа налево от 0 до 31/63/79
// - самый старший разряд (31/63/79) определяет знак всего числа 0/1 +/-
// - разряды 23-30/52-62/64-78 определяют характеристику увеличенную на 
// 127/1023/16383
// - сама характеристика определяется как смещение в двоичной 
// записи исходного плавающего числа, обеспечивающее (смещение)
// перенесение десятичной точки сразу ЗА первой значащей единицей
// для форматов FLOAT/DOUBLE. Для формата LONG DOUBLE - ПЕРЕД первой
// значащей единицей. Т.е. для первых двух первая значащая единица 
// подразумевается, а для последнего - она имеется в машинной записи числа. 
// Если при этом переносил влево, характеристика=127/1023/16383+перенос,
// а если вправо, то характеристика=127/1023/16383-перенос.
// - в разрядах 0-22/0-51/0-63 хранится мантисса.
// Вся информация по номерам используемых бит для 
// различным форматам собрана в таблице ниже
// формат знак характеристика мантисса
// FLOAT 31 30 - 23 +127 22 - 0
// DOUBLE 63 62 - 52 +1023 51 - 0
// LONG D.. 79 78 - 64 +16383 63 - 0 
// Все примеры рассмотрены для формата FLOAT.
// ПРИМЕР №1.
// Необходимо получить машинное представление десятичного числа 45.56.
// Его двоичное представление имеет вид 
// 101101.100011110101110001
// Для того, чтобы десятичная точка оказалась после первой значащей единицы
// её надо переместить влево на 5 разрядов. Таким образом смещенная
// характеристика будет равна 127+5=132=10000100. Это помещается в разряды
// 23-30. В разряды 0-22 помещается смещенная мантисса, но без самой старшей
// единицы, которая предполагается по умолчанию. Таким образом мантисса
// будет иметь вид 01101100011110101110001.
// Общая запись числа 45.56 в машинном представлении типа FLOAT имеет вид
// + хара+127 мантисса без старшей единицы
// 1 87654321 32109876543210987654321
// 0 10000100 01101100011110101110001 =42363D71
// Если последнее представление перевести в 16-ти ричное, то получим 42363D71.
// См. Юров Ассемблер. стр. 515.
// ПРИМЕР №2.
// Необходимо получить машинное представление числа -0.123е-1.
// Его двоичное представление (без учета знака) имеет вид 
// 0.000000110010011000010
// Для того, чтобы десятичная точка оказалась после первой значащей единицы
// её надо переместить вправо на 7 разрядов. Таким образом смещенная
// характеристика будет равна 127-7=120=01111000. Это помещается в разряды
// 23-30. В разряды 0-22 помещается смещенная мантисса, но без самой старшей
// единицы, которая предполагается по умолчанию. Таким образом характеристика
// будет иметь вид 10010011000010111110000.
// Общая запись числа -0.0123 в машинном представлении типа FLOAT имеет
// вид (с учетом 1 в 31-ом разряде за счет минуса перед всем числом)
// + хара+127 мантисса без старшей единицы
// 1 87654321 32109876543210987654321
// 1 01111000 10010011000010111110000 =
// Если последнее представление перевести в 16-ти ричное, то получим BC4985F0.
//*******************************************************************************************
#include<stdio.h>
#include<conio.h>
 void s816(int);
 char rezdw[32]; // массив переписи двоичного результата в симв. виде
 char mss[11]; // массив символьной записи числа в 8 и 16 СС
 int har=0; // характеристика
 int k=0;
 int sn; // знак числа
 
// Объявляю тип структуры с битовыми полями
struct byte
 {
 unsigned b1:1; unsigned b2:1;
 unsigned b3:1; unsigned b4:1;
 unsigned b5:1; unsigned b6:1;
 unsigned b7:1; unsigned b8:1;
 };
// Глобальная переменная, определяющая номер выводимого байта
 int nb;
// Объявляю тип объдинение и переменную этого типа.
// Тут обе составляющие занимают одно и то же место (поочереди).
// Тут это необходимо по той причине, что побитовые операции
// допустимы только с целыми.
union bits
 {
 char ch;
 struct byte b;
 }u;
// объединение, совмещающее FLOAT и LONG INT
union sl
 {
 float fl;
 long int lf;
 }u1;
 
// Прототипы функций.
void decode (union bits );
void vivod (long int);
 
 
//************************
// Основная программа.
//************************
void main (void)
{
 float b1=0,bb=1;
 char ch1;
 char a[9]; // массив ввода исходного числа в символьном виде
 int b[9]; // массив перевода символов в их числовые значения
 unsigned long int mant; // мантисса
 unsigned long int wes=1; // вес
 int dw[31]; // массив записи двоичных разрядов мантиссы
 int i,j=0,har1;
 int pr=0;
 clrscr();
 printf("nУкажите режим работы ПЛАВ.->МАШИН. / МАШИН.->ПЛАВ. / ВЫХОД 0/1/не 0 и не 1 ");
 scanf("%d",&pr);
 while ((pr==0) || (pr==1))
 {
 if (pr==0)
 {
 //================================================
 // перевод плавающего в машинное представление
 //================================================
 printf("nВведите анализируемое плавающее число ");
 scanf("%f",&u1.fl);
 printf("Число=%18.8f имеет двоичную запись n",u1.fl);
 printf("+ хара+127 мантисса без старшей единицыn");
 printf("1 87654321 32109876543210987654321n");
 vivod(u1.lf);
 getch();
 }
 else
 //================================================
 // перевод машинного представления в плавающее
 //================================================
 {
 b1=har=k=j=0;
 wes=bb=1;
 puts("nВведите анализируемое число в машинном представлении ");
 gets(a);
 gets(a);
 // начальное обнуление
 for(i=0;i<=30;dw[i++]=0);
 for(i=0;i<=31;rezdw[i++]='\0');
 // цикл перевода символов числа в их значение
 for(i=0;i<=8;i++)
 {
 if ((a[i]>=48) && (a[i]<=57 )) b[i+1]=a[i]-48;
 if ((a[i]>=97) && (a[i]<=102)) b[i+1]=a[i]-87;
 if ((a[i]>=65) && (a[i]<=70 )) b[i+1]=a[i]-55;
 }
 // определяю знак числа
 sn=(b[1]>7)?(1,b[1]-=8):(0);
 mant=0;
 for(i=4;i<=8; i++)
 {
 mant=(b[i]+mant)*16;
 wes*=16;
 }
 mant/=16;
 if (b[3]>7)
 {
 b[3]-=8;
 har=1;
 }
 mant+=b[3]*wes;
 har+=b[2]*2;
 har+=b[1]*32;
 har-=127; // получил истинное значение ха-ки (без сдвига на 127)
 // перевод мантиссы в ее двоичную запись
 for(i=30;i>=1;i--)
 {
 dw[i]=mant%2;
 mant/=2;
 }
 dw[7]=1;
 // Вывод результата во всех системах счисления.
 //************************
 // ДВОИЧНОЕ ПРЕДСТАВЛЕНИЕ.
 //************************
 printf("nДВОИЧНОЕ ПРЕДСТАВЛЕНИЕ ");
 if (sn) printf("-"); else printf("+");
 if (har>=0)
 for(i=7;i<=30;i++)
 {
 if (j==har+1)
 {
 printf(".");
 rezdw[j++]='.';
 }
 printf("%d",dw[i]);
 rezdw[j++]=dw[i]+48;
 }
 else
 {
 har1=-har;
 printf("0.");
 rezdw[j++]='0',rezdw[j++]='.';
 for(i=1;i<har1;printf("0"),rezdw[j++]='0',i++);
 for(i=7;i<=30-har1;i++)
 {
 printf("%d",dw[i]);
 rezdw[j++]=dw[i]+48;
 }
 }
 //***********************************************************
 // ДЕСЯТИЧНОЕ ПРЕДСТАВЛЕНИЕ
 //***********************************************************
 i=0;
 while (rezdw[i++]!='.') {bb*=2;k++;};
 for(i=0;rezdw[i];b1+=((rezdw[i] != '.')?((rezdw[i]-48)*(bb/=2)):(0)),i++);
 printf("nДЕСЯТИЧНОЕ ПРЕДСТАВЛЕНИЕ ");
 if (sn) printf("-"); else printf("+");
 printf("%-18.8f",b1);
 printf("nВОСЬМЕРИЧНОЕ ПРЕДСТАВЛЕНИЕ ");
 s816(16);
 printf("nШЕСТНАДЦАТИРИЧНОЕ ПРЕДСТАВЛЕНИЕ ");
 s816(8);
 getch();
 }
 if ((pr!=0) && (pr!=1)) break;
 printf("nУкажите режим работы ПЛАВ.->МАШИН. / МАШИН.->ПЛАВ. / ВЫХОД 0/1/не 0 и не 1 ");
 scanf("%d",&pr);
 
}
}
 
// Функция двоичной расшифровки байта.
void decode (union bits b)
{
 // Печатаю последовательно от левого к правому значения битов.
 // В обращении b.b.bn:
 // первая b - имя объединения
 // вторая b - имя структуры в объединении
 // bn - имя поля в структуре
 printf("%d",b.b.b8);
 if (nb<=2) printf(" ");
 printf("%d",b.b.b7);
 printf("%d",b.b.b6);
 printf("%d",b.b.b5);
 printf("%d",b.b.b4);
 printf("%d",b.b.b3);
 printf("%d",b.b.b2);
 printf("%d",b.b.b1);
}
 
// Функция печати значений бит 32-х разрядного LONG INT.
// Печатает побайтно слева направо вплотную.
void vivod(long int a)
{
 unsigned char uu[5];
 // вывод старшего байта
 uu[nb=1]=u.ch=a>>24;
 decode(u);
 // вывод второго слева байта
 uu[nb=2]=u.ch=(a>>16)&255;
 decode(u);
 // вывод третьего слева байта
 uu[nb=3]=u.ch=(a>>8)&255;
 decode(u);
 // вывод последнего (правого) байта
 uu[nb=4]=u.ch=a&255;
 decode(u);
 printf(" =");
 // вывод исходного значения в 16-ти виде побайтно.
 for(int i=1;i<=4;i++)
 {
 (uu[i]<16)? printf("0"):(0);
 printf("%X",uu[i]);
 }
}
//*****************************************************
// Функция перевода символьного представления двоичного
// плавающего в 8-ми и 16-ти системы счисления.
//*****************************************************
void s816(int ss)
{
 int j=4 ,kk,jj;
 int i,n, ii,sum;
 // опробеливаю массив вывода результата
 for(i=0;i<11;mss[i++]=' ');
 // Запись в массив результата десятичной точки и знака числа
 mss[0]=((sn)?('-'):('+'));
 if (ss==8) j--;
 n=(k+j-1)/j+1;
 mss[n]='.';
 // обработка целой части
 jj=k-1;
 for(i=n-1;i>=1;i--)
 {
 for(ii=1,sum=0 ,kk=1;ii<=j && jj>=0;ii++)
 {
 sum+=(rezdw[jj--]-48)*kk;
 kk*=2;
 }
 mss[i]=sum+((sum<=9)? 48:55);
 }
 // обработка дробной части
 jj=k+1;
 for(i=n+1;i<=31;i++)
 {
 kk=((ss==16)? (8):(4));
 for(ii=1,sum=0;ii<=j && rezdw[jj]!='\0';ii++)
 {
 sum+=((rezdw[jj++]-48)*kk);
 kk/=2;
 }
 if (rezdw[jj]=='\0') break;
 mss[i]=sum+((sum<=9)? 48:55);
 }
 for(ii=0;ii<=i;printf("%c",mss[ii++]));
}