{ Программа для исследования точности вещественных типов }
var F0 : Real; F1 : single; F2 : double; F3 : extended;
begin
F0:= 1/3; F1:= 1/3; F2:= 1/3; F3:= 1/3;
Writeln('Single = ', F1:23:18);
Writeln('Real = ', F0:23:18);
Writeln('Double = ', F2:23:18);
Writeln('Extended= ', F3:23:18);
end.
Десятичное представление дроби 1/3 нам известно, – это бесконечная последовательность троек, а результат вычислений по этой программе перед вами (для Borland Pascal, в других компиляторах результаты могут немного отличаться):
Single = 0.333333343267440796
Real = 0.333333333333484916
Double = 0.333333333333333315
Extended= 0.333333333333333333
Как и следовало ожидать, тип Extended дает самую высокую точность, – после десятичной точки следуют одни тройки. Другие типы менее точны. Если так, зачем они нужны? Обратимся к истории.
Первые версии Паскаля ещё не застали персональных компьютеров. Тогда в языке существовал только один тип вещественных чисел – Real. Его считают стандартным типом Паскаля, и для обработки таких чисел годится любой процессор (но вычисления будут медленными).
Но вот появились компьютеры с математическими сопроцессорами, многократно ускоряющими счет. Эти сопроцессоры оперируют с форматами, отличными от Real. Для совместимости с новой техникой в язык были введены ещё три типа чисел, указанные в табл. 3. Тип Extended даёт наивысшую точность и самый широкий диапазон представления чисел. И это понятно, ведь его размер больше, чем у других, и составляет 10 байтов. Но почему он выигрывает и в скорости? А потому, что для сопроцессора тип Extended – родной, применяйте его для вычислений. А что же Single и Double? Поскольку они занимают меньше места в памяти, то лучше подходят для хранения больших объёмов данных.
Сравнение вещественных чиселВещественные числа часто сравнивают между собой. Однако проверка их на точное равенство таит неприятный сюрприз, – такие сравнения ненадежны! Все потому, что вещественные числа – приближенные; они могут быть очень близки, и все же чуточку не совпадать друг с другом. Точное совпадение – это удача, а не закономерность. Правильней сравнивать числа с некоторой точностью, как в показанном ниже примере.
var A, B : Extended;
...
if A = B then … { это ненадежное сравнение }
if Abs(A-B) < 0.001 then … { надежное сравнение с точностью 0.001}
Во втором сравнении переменные A и B считаются одинаковыми, если отличаются менее чем на одну тысячную. Как видите, знаком равенства тут и не пахнет. К слову, функция Abs возвращает абсолютное значение аргумента, – ведь здесь надо получить положительное значение разности. Выражение Abs(A-B) в математике пишется так: |A-B|.
Типы данных пользователяБогатый арсенал типов данных, запасенный в Паскале, кажется достаточным на все случаи жизни. Скоро вы убедитесь, что это не так. Но Паскаль разрешает программисту создавать свои собственные типы данных, заточенные под определенную задачу. Их называют пользовательскими типами, и строят на основе все тех же базовых типов Паскаля. Рассмотрим пример.
Предположим, ваша программа содержит много переменных типа Integer. Но, по ходу работы над проектом, вы решили заменить этот тип чисел на какой-то иной, например на Longint или даже Extended. Такую замену можно сделать редактором текста, тщательно порывшись в программе и найдя все места, где объявлены переменные. Но можно сделать лучше – объявить собственный тип данных.
Для объявления пользовательских типов в Паскале служит секция описания типов, которая начинается с ключевого слова TYPE – «тип». Внутри секции можно объявить один или несколько типов данных пользователя. Так, например, вы можете объявить свой тип на базе встроенного типа Integer.
Type TValue = Integer;
Здесь объявлен тип данных по имени TValue (Value – «значение»), он равнозначен типу Integer. Как видите, объявление типа схоже с объявлением константы. Только справа от знака равенства следует не значение, а описание типа.
Имя пользовательского типа придумывают по общим правилам для имен. В свое время мы учредили префиксы для констант и аргументов процедур, – префиксы делают программу понятней. Для констант мы договорились применять префикс «C», для аргументов процедур и функций – префикс «a». Для типов возьмем префикс «T» (от слова Type). Повторяю: префиксы – это всего лишь добровольное соглашение программистов, а не требование языка.
Теперь, когда тип TValue объявлен, его можно использовать для объявления переменных, например:
Type TValue = Integer;
Var A, B, C : TValue;
...
Readln(A, B);
C:= A+B;
Если со временем потребуется изменить типы переменных A, B и C на тип Longint, то мы исправим лишь объявление пользовательского типа.
Type TValue = Longint;
И после компиляции все переменные типа TValue примут тип Longint.
На первый взгляд, пользовательские типы дают лишь косметические удобства. Но скоро – при освоении сложных типов данных – вы и шагу не ступите без них. Впрочем, пользовательские типы пригодятся нам гораздо раньше – для преобразования типов данных.
Совместимость и преобразование типовПоздравляю! Теперь вам знакомы все простые типы данных – солдаты нашей армии. Сражаясь в едином строю, они будут передавать при необходимости данные друг другу. Такая передача данных должна подчиняться определенным правилам. Они просты, но заставляют программиста всякий раз задуматься: то ли я делаю? А это придает программам надежность.
Далее рассмотрим правила, по которым данные одного типа обращаются в другой: вещественное число — в символ, или целое — в булево. Эти волшебства возможны благодаря числовому кодированию всех типов данных. Поэтому целые числа будут центральным звеном всех преобразований, с них и начнем.
Целое и целое
Все целые типы совместимы между собой, а значит позволено взаимное присваивание их значений. Но не забывайте о возможном нарушении диапазонов. Вот общее правило: переменные более ёмких старших типов всегда примут данные из младших типов своего братства. Обратное не возбраняется, но может вызвать нарушение диапазонов, например:
Var B: Byte; I: Integer; W: Word; L: Longint;
...
{ Эти операторы не вызовут нарушения границ диапазонов }
I:= B; W:=B; L:= W; L:=I;
{ Эти операторы могут повлечь нарушения диапазонов }
B:=I; I:=W; W:=L;
Когда число N не помещается в переменной, то в неё попадает лишь младшая часть числа N (т.е. остаток от деления N mod 256 или N mod 65536).
Целое и символ
Взаимно превращать эти типы данных мы научились при шифровании символа; напомню об этом. Функция Ord возвращает числовой код любого порядкового типа, в том числе и символа. А функция Char делает обратный фокус, превращая число в символ, вот примеры:
Writeln ( Ord(’D’) ); { 68 }
Writeln ( Char(68) ); { D }
Как видите, число в символ преобразуется через имя типа Char. Это общий прием, волшебная палочка для обращений типов данных. Например, булевых.
Целое и булево
Превратить булево в целое можно все той же функцией Ord.
Writeln ( Ord(False) ); { 0 }
Writeln ( Ord(True) ); { 1 }
А для обратного превращения воспользоваться именем типа Boolean.
Writeln ( Boolean(0) ); { False }
Writeln ( Boolean(1) ); { True }
Целое и перечисление
Вернемся к перечислению месяцев, вымышленному нами в предыдущей главе, где переменная была объявлена так:
var m : (Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec);
Превратить значение такой переменной в число можно функцией Ord. А наоборот? Пока нам этого не удавалось. Но после объявления пользовательского типа данных задача решается очень просто.
Type TMonth = (Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec);
Var m : TMonth;
...
m:= 3; { это ошибка }
m:= TMonth(3); { это равнозначно m:= Apr (счет идет от нуля) }
Здесь объявлен пользовательский перечислимый тип TMonth (Month – «месяц»), далее вы вольны применять его и для объявления переменных, и для преобразования типов. Вот где проявляется сила пользовательского типа!