Типы с плавающей точкой: float, double, long double

Типы данных с плавающей точкой хранятся в памяти компьютера в виде мантиссы со знаком и порядка со знаком. В 32-разрядных персональных компьютерах величины типа float занимают 4 байта, из которых один двоичный разряд отводится под знак мантиссы, 24 разряда под мантиссу и 8 разрядов под порядок. При этом обеспечивается точность представления данных, равная 7 десятичным цифрам. Для величин типа double, занимающих 8 байт, под мантиссу и порядок отводится 53 и 11 разрядов соответственно. При этом обеспечивается точность представления, равная 15 десятичным цифрам. Спецификатор long перед именем типа double указывает, что под величину типа long double отводится 10 байт. Обеспечиваемая точность – 19 десятичных цифр.

 

Диапазоны значений вещественных типов данных для 32-разрядного компьютера

Тип Диапазон значений Размер (байт) Точность
Float 3.4e-38 . . . 3.4e+38
Double 1.7e-308 . . . 1.7e+308
long double 3.4e-4932 . . . 3.4e+4932

 

Тип void

Кроме перечисленных к простым типам данных относится тип void, но множество значений этого типа является пустым. Он используется для описания функций, которые не возвращают значений, а также для указания пустого списка аргументов функции, например:

void main(void) {}

 

Тема 3. Выражения

 

В любой программе требуется производить какие-то вычисления. Для вычисления значений используются выражения, которые состоят из операндов, объединенных знаками операций. Операнды задают данные для вычислений и чаще всего являются переменными или константами. Операции выполняются в соответствии с их приоритетами. Для изменения порядка выполнения операций используются круглые скобки. Примеры выражений:

a + 2

(a + b)*c

В первом выражении + является знаком операции, а а и 2 – операндами. Во втором выражении знаками операций являются + и *, а операндами a, b и c. При этом скобки необходимы, иначе первой выполнялась бы операция умножения.

Рассмотрим составные части выражений и правила их вычислений.

Переменные

Переменная – это именованная область оперативной памяти, предназначенная для хранения данных определенного типа. Во время выполнения программы значение переменной может изменяться. Все переменные, используемые в программе, должны быть описаны явным образом. При описании каждой переменной задаются ее имя и тип. Например:

int a;

float x;

Здесь описаны целая переменная с именем a и вещественная переменная с именем x.

Имя переменной служит для обращения к области памяти, в которой хранится значение переменной. Имя дает программист, с учетом, что оно должно соответствовать правилам именования идентификаторов языка С, отражать смысл хранимой величины и быть легко распознаваемым.

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

В одном операторе можно описать несколько переменных одного типа, разделяя их запятыми:

int a, b, с;

При описании переменной можно присвоить ей некоторое начальное значение, т.е. инициализировать ее, например:

int a, b = 100, с;

float x = 0.05, y = 2.37e4;

char ch1 = 65, ch2 = ‘A’;

Здесь описаны:

  • переменные a, b и с типа int, при этом переменная b имеет начальное значение 100;
  • переменные x и y типа float, которым присвоены начальные значения 0.05 и 23700 соответственно;
  • переменные ch1 и ch2 типа char, которым присвоено одно и то же начальное значение 65 (прописная латинская буква A кодируется числом 65).

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

int a, b(100), с;

float x(0.05), y(2.37e4);

char ch1(65), ch2(‘A’);

При инициализации можно использовать не только константу, но и выражение – главное, чтобы на момент описания оно было вычисляемым, например:

int a = 5, b = 100;

int с = a * b + 25;

Инициализировать переменную прямо при объявлении не обязательно, но перед тем, как ее использовать в вычислениях, это сделать все равно придется.

Именованные константы

Можно запретить изменять значение переменной, задав при ее описании ключевое слово const, например:

const int A = 5, B = 100, MIN = 100, MAX = 800;

const int C = A * B + 25, STEP = (MAX – MIN)/100;

const float X = 0.05, Y = 2.37e4, PI = 3.14;

Такие величины называются именованными константами или просто константами. Именованные константы должны обязательно инициализироваться при описании. Они применяются в программах для того, чтобы вместо значений констант можно было использовать их имена. Это делает программу более понятной и облегчает внесение в нее изменений, поскольку изменить значение достаточно только в одном месте программы (там, где константа описана и инициализирована). Например, число p = 3.14 может быть впоследствии заменено более точным значением 3.14159 и т.д.

В хорошо написанной программе вообще не должно встречаться иных чисел, кроме 0 и 1, все остальные числа должны задаваться именованными константами с именами, отражающими их назначение. Имена именованных констант принято записывать заглавными буквами, чтобы они отличались от обычных переменных.

Операции

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

 

Операции языка С в порядке убывания их приоритетов

Операции Выполнение
() [] . -> ! ~ ++ -- + - * & (тип) sizeof слева направо справа налево
* / % + - << >> < <= > >= == != & ^ | && || ?: = += -= *= /= %= &= ^= |= <<= >>= , слева направо слева направо слева направо слева направо слева направо слева направо слева направо слева направо слева направо слева направо справа налево справа налево слева направо

 

В таблице операций первые две строки задают унарные операции, а все последующие – бинарные и одну тернарную (строка 13). Строки упорядочены по убыванию приоритетов операций, при этом операции, записанные в одной строке, имеют одинаковый приоритет. Например, операции *, / и % (строка 3) имеют одинаковый приоритет, который выше, чем приоритет бинарных операций + и - (строка 4).

 

Операции присваивания

Операции присваивания (строка 14) задают новое значение переменной и подразделяются на простые и сложные. Формат операции простого присваивания:

переменная = выражение

Механизм выполнения операции присваивания такой: вычисляется выражение и его результат заносится в память по адресу, который определяется именем переменной, находящейся слева от знака операции. То, что ранее хранилось в этой области памяти, теряется. Примеры с операцией присваивания:

int a, b = 5, c = 5;

float x = 1.0, y = 33.0;

a = b + c/2;

b = a;

x = x + 0.5;

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

Необходимо отметить, что результат вычисления выражения характеризуется значением и типом, например, для b и с равных 5 выражение b + c/2 имеет значение 7 и тип int, т.к. при делении целых чисел дробная часть отбрасывается.

Если операнды, входящие в выражение, разного типа, то перед вычислениями выполняются преобразования более коротких типов в более длинные для сохранения значимости и точности. Такие преобразования называются неявным приведением типов и выполняются автоматически. При этом целочисленные константы имеют по умолчанию тип int, а вещественные – тип double. Например, выражение b + c/2.0 имеет значение 7.5 и тип double. Если же рассмотреть операторы:

a = b + c/2.0;

х = b + c/2.0;

то переменная а, поскольку она целого типа, получит значение 7, а переменная х, поскольку она вещественная, получит значение 7.5. Другие примеры:

х = 10.0 + 9/10; //х = 10.0

х = 10 + 9.0/10; //х = 10.9

х = 5/9*(у – 32.0); //х = 0 для любого значения у

х = 5*(у – 32)/9; //х = 0.555556 для у = 33.0

x = 5.0;

x = 5; //присваивание с неявным приведением типа int к типу float

unsigned char ch1 = 255, ch2;

a = ch1 + 1; //a = 256

ch2 = ch1 + 1; // ch2 = 0

Операция присваивания в отличие от большинства других операций является правоассоциативной, т.е. выполняется справа налево:

a = b = c означает a = (b = c)

Это отличается, например, от операции сложения, для которой a + b + c означает (a + b) + c. Поэтому можно выполнить такое присваивание: a = b = c = 10; (вместо цепочки: a = 10; b = 10; c = 10;).

В сложных операциях присваивания (+=, -=, *=, /= и др.) при вычислении выражения, стоящего в правой части, используется значение из левой части. Например, выражение a += b является более компактной записью выражения a = a + b, выражение a *= b можно использовать вместо выражения a = a * b и т.д.

Инкремент и декремент

Унарные операции инкремента (++) и декремента (--), называемые также операциями увеличения и уменьшения на единицу, имеют две формы записи: префиксную, когда знак операции записывается перед операндом, и постфиксную – после операнда. В префиксной форме сначала изменяется операнд, а затем его значение используется при вычислении выражения, а в постфиксной форме сначала вычисляется выражение с использованием исходного значения операнда, после чего операнд изменяется. Операции инкремента и декремента можно применить только к операндам, которые хранят свои значения в оперативной памяти. Примеры:

int a = b = 2, c, d;

c = ++a; //a = 3, c = 3

d = b++; //d = 2, b = 3

c = a + b; //c = 6

d = a-- * ++c / (--b + 1); //c = 7, b = 2, d = 7, a = 2

a = 10++; //неверно

a = (--b + 5)++; //неверно

Унарный плюс и унарный минус (строка 2)

Унарный плюс (+) перед операндом можно не ставить, т.к. он подразумевается по умолчанию. Унарный минус (-) меняет знак операнда на противоположный.

Явное преобразование типа

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

int a = 9, b = 10;

float x;

x = a/b; //x = 0

x = (float)a/b; //x = 0.9

x = a/(float)b; //x = 0.9

x = (float)(a/b); //x = 0

 

a = 256;

b = a; //b = 256

b = (char)a; //b = 0 (потеря информации)