• ProDelphi - Тип данных. Типы данных в DELPHI Типы переменных в delphi таблица

    08.05.2023

    Двоичные дроби

    Для начала - немного математики. В школе мы проходим два вида дробей - простые и десятичные. Десятичные дроби, по сути дела, представляют собой разложение числа по степеням десяти. Так, запись 13.6704 означает число, равное 1*10 1 +3*10 0 +6*10 -1 +7*10 -2 +0*10 -3 +4*10 -4 . Но внутреннее представление всех чисел в компьютере, в том числе и вещественных - не десятичное, а двоичное. Поэтому используются двоичные дроби. Они во многом похожи на десятичные, но основание степени у них двойка. Так, число 101.1101=1*2 2 +0*2 1 +1*2 0 +1*2 -1 +1*2 -2 +0*2 -3 +1*2 -4 . То есть в десятичном представлении это число равно 5.8125, в чём нетрудно убедиться с помощью любого калькулятора.

    Теперь вспомним научный формат записи десятичного числа. Первым в этой записи идёт знак числа - плюс или минус. Дальше идёт так называемая мантисса - число от 1 до 10. Затем идёт экспонента - степень десяти, на которую надо умножить мантиссу, чтобы получить нужное число. Итак, уже упоминавшееся число 13.6704 запишется в этом формате как 1.36704*10 1 (или 1.36704E1 по принятым в компьютере правилам). Если записываемое число меньше единицы, экспонента будет отрицательной. Аналогичная запись существует и в двоичной системе. Так, 101.1101 запишется в виде 1.011101*10 10 (Везде использована двоичная форма записи, так что 10 10 означает 2 2). Именно такое представление используется в компьютере. Двоичная точка в такой записи не остаётся на одном месте, а сдвигается на величину, указанную в экспоненте, поэтому такие числа называются числами с плавающей точкой (floating point numbers).

    Вещественные типы Delphi

    В Delphi существует четыре вещественных типа: Single , Double , Extended и Real . Их общий формат одинаков: Знак Экспонента Мантисса

    Знак - это всегда один бит. Он равен нулю для положительных чисел и единице для отрицательных. Что же касается размеров мантиссы и экспоненты, то именно в них и заключается различие между типами.

    Прежде чем перейти к конкретным цифрам, рассмотрим подробнее тип Real, сделав для этого небольшой экскурс в историю. Real - это стандартный тип языка Паскаль, присутствовавший там изначально. Когда создавался Паскаль, процессоры ещё не имели встроенной поддержки вещественных чисел, поэтому все операции над этим типом сводились к операциям с целыми числами. Соответственно, размер полей в типе Real был подобран так, чтобы оптимизировать эти операции.

    Микропроцессор Intel 8086/88 и его улучшенные варианты - 80286 и 80386 - также не имели аппаратной поддержки вещественных чисел. Но системы на базе этих процессоров имели возможность подключения так называемого сопроцессора. Эта микросхема работала с памятью через шины основного процессора и обеспечивала аппаратную поддержку вещественных чисел. В системах средней руки гнездо сопроцессора обычно было пустым, так как это удешевляло систему (разумеется, вставить туда сопроцессор не было проблемой). Для каждого центрального процессора выпускались свои сопроцессоры, маркировавшиеся Intel 8087, 80287 и 80387 соответственно. Были даже сопроцессоры, выпускаемые другими фирмами. Они работали быстрее, чем Intel"овские, но появлялись на рынке позже. Тип вещественных чисел, поддерживаемый сопроцессорами, не совпадает с Real.

    Чтобы обеспечить в своих системах поддержку сопроцессорных типов, Borland вводит в Turbo Pascal типы Single, Double и Extended. Extended - это родной для сопроцессора тип, а типы Single и Double получаются из него очень простым усечением. При загрузке числа типа Single или Double во внутренний регистр сопроцессора последний конвертирует их в Extended. Напротив, при выгрузке чисел этих типов из регистра в память сопроцессор усекает их до нужного размера. Внутренние же операции всегда выполняются с данными типа Extended (впрочем, из этого правила есть исключение, на котором мы остановимся позже, после детального рассмотрения формата различных типов). Single и Double используются для экономии памяти. Ни один из них также не совпадает с типом Real. В системах с сопроцессорами новые типы обрабатываются заметно (в 2-3 раза) быстрее, чем Real (это с учётом того, что тип Real после соответствующего преобразования также обрабатывался сопроцессором; если же сравнивать обработку типа Extended на машине с сопроцессором и Real на машине без сопроцессора, то там на отдельных операциях достигалась разница примерно в 100 раз). Чтобы программы с этими типами можно было выполнять и в системах без сопроцессора, была возможность подключать к ним программный эмулятор сопроцессора. Обработка этих типов эмулятором была медленнее, чем обработка Real.

    Начиная с 486-ого процессора Intel берёт курс на интеграцию процессора и сопроцессора в одной микросхеме. Процент брака в микросхемах слишком велик, поэтому Intel идёт на хитрость: если у микросхемы брак только в сопроцессорной части, то на этой микросхеме прожигаются перемычки, блокирующие сопроцессор, и микросхема продаётся как процессор 80486SX, не имеющий встроенного сопроцессора (в отличие от полноценной версии, которую назвали 80486DX). Бывали и обратные ситуации, когда сопроцессор повреждений не имел, зато процессор был неработоспособен. Такие микросхемы превращали в «сопроцессор 80487». Но это уже из области экзотики, и, насколько мне известно, до России этот сопроцессор не дошёл.

    Процессор Pentium во всех своих вариантах имел встроенный сопроцессор. Таким образом, с приходом этого процессора тип Real стал как бы обузой, а на передний план вышли Single, Double и Extended. Чтобы свести к минимуму необходимые переделки программ, Borland ввела новую директиву компилятора: {$REALCOMPATIBILITY ON/OFF}. По умолчанию стоит OFF, что означает отсутствие полной совместимости. В этом случае тип Real в Delphi совпадает с типом Double. Если же совместимость включена, тип Real совпадает со своим прообразом из Паскаля. Существует ещё тип Real48, который всегда, вне зависимости от настроек, совпадает со старым Real. Далее в этой статье под словом “Real” я всегда буду подразумевать старый тип. Отмечу, что всё это появилось только в Delphi 4, в более ранних версиях тип Real48 отсутствовал, а тип Real был всегда старым, шестибайтным.

    Итак, теперь можно, наконец, добраться до размеров полей.

    Тип Размер типа, байт Размер мантиссы, бит Размер экспоненты, бит
    Single 4 23 8
    Double 8 52 11
    Extended 10 64 15
    Real 6 40 7

    Другие параметры вещественных типов, такие как диапазон и точность, есть в справке Delphi, в которую я рекомендую почаще заглядывать.

    Внутренний формат вещественных чисел

    Рассмотрим тип Single, так как он является самым коротким и, следовательно, самым простым для понимания. Остальные типы отличаются от него только количественно. В дальнейшем числа в формате Single мы будем записывать как s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm, где s означает знаковый бит, e - бит экспоненты, m - бит мантиссы. Именно в таком порядке эти биты хранятся в четырёхбайтном значении (здесь учтена перестановка байтов; напоминаю, что в процессорах Intel байты в многобайтных значениях переставляются так, что младший байт идёт первым, а старший - последним). В мантиссе хранится двоичное число. Чтобы получить истинное значение мантиссы, к ней надо мысленно добавить слева единицу с точкой (то есть, например, мантисса 1010000000000000000000 означает двоичную дробь 1.101). Таким образом, имея 23 двоичных разряда, мы записываем числа с точностью до 24-ёх двоичных разрядов. Такая запись числа называется нормализованной.

    Экспонента по определению всегда целое число. Но способ записи экспоненты в вещественных числах не совпадает с обычным способом записи чисел со знаком. Ноль в этом представлении записывается как 01111111. В обычном представлении это равно 127. Соответственно, 10000000 (128 в обычном представлении) означает единицу, а 01111110 (126) означает -1, и так далее (то есть из обычного беззнакового числа надо вычесть 127, и получится число, закодированное в экспоненте).

    Из описанных выше правил есть исключения. Так, если все биты экспоненты равны нулю (то есть там стоит число -127), то к мантиссе перед её началом надо добавлять не “1.”, а “0.” (денормализованная запись). Это позволяет увеличить диапазон вещественных чисел. Если бы этого исключения не было бы, минимально возможное положительное число типа Single было бы равно примерно 5.9*10 -39 . А так появляется возможность использовать числа до 1.4*10 -45 . Побочным эффектом этого является то, что числа, меньшие, чем 1.17*10 -38 , представляются с меньшей, чем 24 двоичных разряда, точностью.

    Если все биты в экспоненте равны единице, а в матрице - нулю, то мы получаем комбинацию, известную как INF (от английского Infinity - бесконечность). Эта комбинация используется тогда, когда результат вычислений превышает максимально допустимое форматом число. В зависимости от значения бита s бесконечность может быть положительной или отрицательной. Если же при такой экспоненте в мантиссе хоть один бит не равен нулю, такая комбинация называется NAN (Not A Number - не число). Попытки использования комбинаций NAN или INF приводят к ошибке времени выполнения.

    Для задания нуля все биты мантиссы и экспоненты должны быть равны нулю (формально это означает 0*10 -127). С учётом описанных выше правил если хотя бы один бит экспоненты не будет равен нулю (т.е. экспонента будет больше -127), запись будет считаться нормализованной, и нулевая мантисса будет рассматриваться как единица. Поэтому никакие другие комбинации значений мантиссы и экспоненты не могут дать ноль.

    Тип Double устроен точно так же, разница только в количестве разрядов и в том, какое значение экспоненты берётся за ноль. Итак, мы имеем 11 разрядов для экспоненты. За ноль берётся значение 1023.

    Несколько иначе устроен Extended . Кроме количественных отличий добавляется ещё и одно качественное: в мантиссе явно указывается первый разряд. То есть, мантисса 1010... интерпретируется как 1.01, а не как 1.101, как это было в типах Single и Float. Поэтому если 23-битная мантисса типа Single обеспечивает 24-знаковую точность, а 52-битная мантисса Double - 53-битную, то 64-битная мантисса Extended обеспечивает 64-, а не 65-битную точность. Соответственно, при денормализованной форме записи первый разряд мантиссы явно содержит 0. За ноль экспоненты принимается значение 16383.

    Тип Real , как уже упоминалось, стоит особняком. Во-первых, в нём используется другой порядок следования битов, а, во-вторых, не используется денормализованная форма. Я не стал детально разбираться с типом Real, потому что сейчас это нужно разве что историкам, но никак не программистам.

    «Неполноценный» Extended

    Напомню, чуть выше я сказал, что сопроцессор всегда выполняет все операции в формате Extended, оговорившись при этом, что есть исключение, к которому я вернусь чуть позже. Если вы прочитали всё, что было изложено выше, теперь у вас достаточно знаний, чтобы понять, что это за исключение.

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

    Если восьмой бит содержит единицу (так установлено по умолчанию), то десять байт внутренних регистров сопроцессора будут использоваться полностью, и мы получим «полноценный» Extended. Если же этот бит равен нулю, то всё определяется значением бита 9. Если он равен единице, то используются только 53 разряда мантиссы (остальные всегда равны нулю). Если же этот бит равен нулю - только 24 разряда мантиссы. Это увеличивает скорость вычислений, но уменьшает точность. Другими словами, точность работы сопроцессора может быть понижена до типа Double или даже Single. Но это касается только мантиссы, экспонента в любом случае будет содержать 15 бит, так что диапазон типа Extended сохраняется в любом случае.

    Для работы с управляющим словом сопроцессора в модуле System описана переменная Default8087CW:Word и процедура Set8087CW(CW:Word). При запуске программы в переменную Default8087CW записывается то управляющее слово, которое установила система при запуске программы. Функция Set8087CW записывает новое значение в управляющее слово. Одновременно это новое значение записывается в переменную Default8087CW.

    Такое поведение этой функции не всегда удобно - иногда бывает нужно сохранить старое значение переменной Default8087CW (впрочем, это несложно сделать, заведя дополнительную переменную). С другой стороны, если значение управляющего слова изменить, не используя Set8087CW (а в дальнейшем мы увидим, что такие изменения могут происходить помимо нашей воли), то с помощью функции Default8087CW просто нет возможности узнать текущее значение управляющего слова. В Delphi 6 и выше появилась функция Get8087CW, позволяющая узнать значение именно контрольного слова, а не переменной Default8087CW. В более ранних версиях единственный способ получить значение этого слова - использование ассемблера, тем более что в Delphi нет проблем с ассемблерными вставками.

    Установить значение управляющего слова можно с помощью команды FLDCW, прочитать - с помощью FNSTCW. Обе эти команды имеют один аргумент - переменную типа Word. Чтобы, например, установить 53-значную точность, не изменив при этом другие биты управляющего слова, надо выполнить такую последовательность команд:

    Asm
    FNSTCW MyCW
    AND MyCW,0FCFFh
    OR MyCW,200h
    FLDCW MyCW
    end; Современные сопроцессоры обрабатывают числа с такой скоростью, что при обычных вычислениях вряд ли может возникнуть необходимость в ускорении за счёт точности - выигрыш будет ничтожен. Эта возможность используется, в основном, в тех случаях, когда вычисления с плавающей точкой составляют значительную часть программы, а высокая точность не имеет принципиального значения (например, в движках для 3D-игр). Однако забывать об этой особенности работы сопроцессора не стоит, потому что она может преподнести один неприятный сюрприз, о котором чуть позже.

    Бесконечные дроби

    Из школы мы все помним, что не каждое число может быть записано конечной десятичной дробью. Бесконечные же дроби бывают двух видов: периодичные и непериодичные. Примером непериодичной дроби является число «пи», периодичной - число 1/3 или любая другая простая дробь, не представимая в виде конечной десятичной дроби.

    Для тех, кто забыл математику, напомню, что периодичные дроби - это такие дроби, которые содержат бесконечно повторяющуюся последовательность цифр. Например, 1/9=0.11111…, 1/12=0.08333333…, 1/7=0.142857142857… Для записи таких чисел используют скобки - в них заключают повторяющуюся часть. Те же числа должны быть записаны так: 1/9=0.1(1), 1/12=0.08(3), 1/7=0.1(428571).

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

    До сих пор мы говорили о только о десятичных бесконечных дробях. Но двоичные дроби тоже могут быть бесконечными. Даже более того, любое число, выражаемое конечной двоичной дробью, может быть также выражено и десятичной конечной дробью. Но существуют числа (например, 1/5) которые выражаются конечной десятичной дробью, но не могут быть выражены конечной двоичной дробью. Это сильно усложняет жизнь программистам.

    Примеры «неправильного» поведения вещественных типов

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

    Все примеры построены одинаково: на форму надо кинуть два компонента - метку (TLabel) и кнопку (TButton). Так как это только примеры, я не стал придумывать имена для этих компонентов, пусть называются Button1 и Label1. Обработчик Button1Click содержит некоторый код, результаты работы которого выводятся на форму через Label1. Таким образом, нужно запустить программу, нажать на кнопку и посмотреть, что будет написано в метке. Я буду приводить только код обработчика Button1Click, так как всё остальное тривиально. Напомню, что в Паскале допускается не ставить точку с запятой перед end"ом (за исключением end"а в описании класса) и перед until"ом. Я предпочитаю пользоваться этой возможностью, так что не надо тыкать в меня пальцем, что я забываю ставить точки с запятой.

    Пример первый - «неправильное значение»

    Итак, напишем такой код: var R:Single;
    begin
    R:=0.1;
    end; Что мы увидим, когда нажмём кнопку? Разумеется, не «0.1», иначе не было бы смысла писать этот пример. Мы увидим «0.100000001490116». То есть расхождение в девятой значащей цифре. Ну, из справки по Delphi мы знаем, что точность типа Single - 7-8 десятичных разрядов, так что нас, по крайней мере, никто не обманывает. В чём же причина? Просто число 0.1 не представимо в виде конечной двоичной дроби, оно равно 0.0(0011). И эта бесконечная двоичная дробь обрубается на 24-ёх знаках; мы получаем не 0.1, а некоторое приближённое число (какое именно - см. выше). А если мы присвоим переменной R не 0.1, а 0.5? Тогда мы получим на экране 0.5, потому что 0.5 представляется в виде конечной двоичной дроби. Немного поэкспериментировав с различными числами, мы заметим, что точно представляются те числа, которые выражаются в виде m/2 n , где m, n - некоторые целые числа (разумеется, n не должно превышать 24, а то нам не хватит точности типа Single). В качестве упражнения предлагаю доказать, что любое целое число, для записи которого хватает 24-ёх двоичных разрядов, может быть точно передано типом Single.

    Пример второй - сравнение

    Теперь изменим код так: var R:Single;
    begin
    R:=0.1;
    if R=0.1 then
    Label1.Caption:="Равно"
    else
    Label1.Caption:="Не равно"
    end;

    При нажатии кнопки мы увидим надпись «Не равно». На первый взгляд это кажется абсурдом. Действительно, мы уже знаем, что переменная R получает значение 0.100000001490116 вместо 0.1. Но ведь «0.1» в правой части равенства тоже должно преобразоваться по тем же законам, ведь в компьютере всё предопределено. Тут самое время вспомнить, что процессоры Intel работают только с 10-байтным типом Extended, поэтому и левая, и правая часть равенства сначала преобразуется в этот тип, и лишь потом производится сравнение. То корявое число, которое оказалось в переменной R вместо 0.1, хоть и выглядит страшно, но зато представляется в виде конечной двоичной дроби. Информация же о том, что это на самом деле должно означать «0.1», нигде не сохранилось. При преобразовании этого числа в Extended младшие, избыточные по сравнению с типом Single разряды мантиссы просто заполняются нулями, и мы снова получим то же самое число, только записанное в формате Extended. А «0.1» из правой части равенства преобразуется в Extended без промежуточного превращения в Single. А 0.1 - бесконечная в двоичном представлении дробь. Поэтому некоторые из младших разрядов мантиссы будут содержать единицы. Другими словами, мы получим хоть и не точное представление числа 0.1, но всё же более близкое к истине, чем 0.100000001490116. Из-за таких хитрых преобразований оказывается, что мы сравниваем два близких, но всё же не равных числа. Отсюда - закономерный результат в виде надписи «Не равно».

    Тут уместна аналогия с десятичными дробями. Допустим, в одном случае мы делим 1 на три с точностью до трёх знаков, и получаем 0.333. Потом мы делим 1 на три с точностью то четырёх знаков, и получаем 0.3333. Теперь мы хотим сравнить эти два числа. Для этого приводим их к точности в четыре разряда. Получается, что мы сравниваем 0.3330 и 0.3333. Очевидно, что это разные числа.

    Если попробовать заменить число 0.1 на 0.5, то мы получим «Равно». Думаю, вы уже знаете почему, но для полноты текста объясню. 0.5 - это конечная двоичная дробь. При прямом приведении её к типу Extended в младших разрядах оказываются нули. Точно такие же нули оказываются в этих разрядах при превращении числа 0.5 типа Single в тип Extended. Поэтому в результате мы сравниваем два числа. Это похоже, как если бы мы делили 1 на 4 с точностью до трёх и до четырёх значащих цифр. В первом случае получили бы 0.250, во втором - 0.2500. Приведя их оба к точности в четыре знака, получим сравнение 0.2500 и 0.2500. Очевидно, что эти цифры равны.

    Пример третий - сравнение разных типов

    Немного усложним наш пример: var R1:Single;
    R2:Double;
    begin
    R1:=0.1;
    R2:=0.1;
    if R1=R2 then
    Label1.Caption:="Равно"
    else
    Label1.Caption:="Не равно"
    end;

    Наученные горьким опытом, вы, наверное, ожидаете увидеть надпись «Не равно». Что ж, жизнь вас не разочарует, именно это вы и увидите. Тип Double точнее, чем Single (хотя его точности тоже не хватает для представления бесконечной дроби). В R2 мы получим не 0.100000001490116, а другое число, с точностью 15-16 десятичных знаков. Я не могу назвать точно это число, потому что FloatToStr воспринимает его как 0.1, так что, заменив в первом примере Single на Double, вы увидите 0.1 (только не надо обольщаться, всё равно это не 0.1, просто функция FloatToStr имеет такую особенность работы). Числа в обеих переменных приводятся к типу Extended, но при этом они не меняются и, как были не равны, так и остаются неравными. Это напоминает ситуацию, когда мы сравниваем 0.333 и 0.3333, приводя их к точности в пять знаков: числа 0.33300 и 0.33330 не равны.

    Мне уже неловко надоедать вам такими очевидными замечаниями, но всё-таки: если в этом примере заменить 0.1 на 0.5, мы увидим «Равно».

    Пример четвёртый - вычитание в цикле

    Рассмотрим ещё один пример, иллюстрирующий ситуацию, которая часто озадачивает начинающего программиста var R:Single;
    I:Integer;
    begin
    R:=1;
    for I:=1 to 10 do
    R:=R-0.1;
    Label1.Caption:=FloatToStr(R)
    end; Конечно, если бы в результате выполнения этого примера вы увидели бы ноль, я бы не стал тратить на него время. Но на экране появится -7.3015691270939E-8. Думаю, такой оборот дела уже никого не удивляет. Мы уже знаем про то, что число 0.1 не может быть передано точно ни в одном из вещественных типов, и про преобразования Single в Extended и обратно. При этом постоянно происходят округления, и эти округления приводят к тому, что мы получаем в результате не ноль, а «почти ноль».

    Пример пятый - сюрпириз от Microsoft

    Изменим в предыдущем примере тип переменной R с Single на Double. Значение, выводимое программой, станет 1.44327637948555E-16. Вполне логичный и предсказуемый результат, так как тип Double точнее, чем Single и, следовательно, все вычисления более точны, мы просто обязаны получить более точный результат. Хотя, разумеется, абсолютная точность (то есть ноль), для нас остаётся недостижимым идеалом.

    А теперь - вопрос на засыпку. Изменится ли результат, если мы заменим Double на более точный Extended? Ответ не такой однозначный, каким его хотелось бы видеть. В принципе, после такой замены вы должны получить -6.7762635780344E-20. Но в некоторых случаях от замены Double на Extended результат не изменится, и вы снова получите 1.44327637948555E-16. Это зависит от операционной системы.

    Всё дело в использовании «неполноценного» Extended. При запуске программы любая система устанавливает такое управляющее слово сопроцессора, чтобы Extended был полноценным. Но затем программа вызывает много разных функций Windows API. Какая-то (или какие-то) из этих многочисленных функций в некорректно работают с управляющим словом, меняя его значение и не восстанавливая при выходе. Такая проблема встречается, в основном, в Windows 95 и старых версиях Windows 98. Также имеются сведения о том, что управляющее слово может портиться и в Windows NT, причём эффект наблюдался не сразу после установки системы, а лишь через некоторое время, после доустановки других программ. Проблема именно в некорректности поведения системных функций; значение управляющего слова, устанавливаемое системой при запуске программы, всегда одинаково. Эта проблема известна: например, в исходных кодах VCL можно найти сохранение управляющего слова сопроцессра перед вызовом некоторых API-функций с последующим его восстановлением. Комментарии сообщают, что функция может изменить значение управляющего слова, поэтому необходимо его сохранение и восстановление.

    Таким образом, приходим к неутешительному выводу: к тем проблемам с вещественными числами, которые обусловлены особенностями их аппаратной реализации, добавляются ещё и баги Windows. Правда, радует то, что в последнее время эти баги встречаются крайне редко - видимо, новые версии системы ведут себя более ответственно. Тем не менее, полностью исключать такую возможность нельзя, особенно если ваша программа будет использоваться на устаревшей технике с устаревшими системами (например, в образовательных учреждениях, финансирование которых оставляет желать лучшего). Чтобы наш пример всегда выдавал правильное значение -6.7762635780344E-20, достаточно поставить в начале нашей процедуры Set8087CW(Get8087CW or $0100), и программа в любой системе будет использовать сопроцессор в режиме максимальной точности. (Если вы используете старые версии Delphi, эту строку можно заменить на Set8087CW(Default8087CW), если, конечно, значения по умолчанию прочих флагов управляющего слова вас устраивают.)

    Раз уж мы заговорили об управляющем слове, давайте немного поэкспериментируем с ним. Изменим первую строчку на Set8087CW(Get8087CW and $FCFF or $0200). Тем самым мы переведём сопроцессор в режим 53-ёхразрядной точности представления мантиссы. Теперь в любой системе мы увидим 1.44327637948555E-16, несмотря на использование Extended. Если же мы изменим первую строчку на Set8087CW(Get8087CW and $FCFF), то будем работать в режиме 24-ёхразрядной точности. Соответственно, в любой системе будет результат -7.3015691270939E-8.

    Заметим, что при загрузке в 10-байтный регистр сопроцессора числа типа Extended в режиме пониженной точности «лишние» биты не обнуляются. Только результаты математических операций представляются с пониженной точностью. Кроме того, при сравнении двух чисел также учитываются все биты, независимо от точности. Поэтому код

    Var R:Double; // или Single

    Begin
    R:=0.1;
    if R=0.1 then
    Label1.Caption:="Равно"
    else
    Label1.Caption:="Не равно"
    end; при выборе любой точности даст «Не равно».

    Пример шестой - машинное эпсилон

    Когда мы имеем дело с вычислениями с ограниченной точностью, возникает такой парадокс. Пусть, например, мы считаем с точностью до трёх значащих цифр. Прибавим к числу 1.00 число 1.00*10 -4 . Если бы всё было честно, мы получили бы 1.0001. Но у нас ограничена точность, поэтому мы вынуждены округлять до трёх значащих цифр. В результате получается 1.00. Другими словами, мы прибавляем к единице некоторое число, большее нуля, а в результате из-за ограниченной точности получаем снова единицу. Наименьшее положительное число, которое при добавлении его к единице даёт результат, не равный единице, называется машинным эпсилон.

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

    Прежде чем искать машинное эпсилон программно, попытаемся найти его из теоретических соображений. Итак, мантисса типа Extended содержит 64 разряда. Чтобы закодировать единицу, старший бит мантиссы должен быть равен 1 (денормализованная запись), остальные биты - нулю. Очевидно, что при такой записи наименьшее из чисел, для которых вполняется условие x>1, получается, когда самый младший бит мантиссы тоже будет равен единице, т.е. x=1.00...001 (в двоичном представлении; между точкой и младшей единицей 62 нуля). Таким образом, машинное эпсилон равно x-1, т.е. 0.00...001. В более привычной десятичной форме записи это будет 2 -63 , т.е. примерно 1.084*10 -19 .

    Теперь напишем программу для отыскания машинного эпсилон.

    Var R:Extended;
    begin
    R:=1;
    while 1+R/2>1 do
    R:=R/2;
    Label1.Caption:=FloatToStr(R)
    end;

    В результате на экране появится число 1.0842021724855E-19 в полном соответствии с теоретическими выкладками (если в вашей системе присутствует описанный выше баг с переводом процессора в режим пониженной точности, вместо этого числа вы получите 2.22044604925031E-16, т.е. 2 -52 . Чтобы этого не происходило, исправьте значение управляющего слова).

    А теперь заменим тип Extended на Double. Результат не изменится. На Single - опять не изменится. Но такое поведение лишь на первый взгляд может показаться странным. Давайте подробнее рассмотрим выражение 1+R/2>1. Итак, все вычисления (в том числе и сравнение) сопроцессор выполняет с данными типа Extended. Последовательность действий такова: число R загружается в регистр сопроцессора, преобразуясь при этом к типу Extended. Дальше оно делится на 2, а затем к результату прибавляется 1, и всё это в Extended, никакого обратного преобразования в Single или Double не происходит. Затем это число сравнивается с единицей. Очевидно, что результат сравнения не должен зависеть от исходного типа R.

    Заключение

    В этой статье я постарался объяснить внутреннее устройство вещественных чисел с точки зрения процессоров Intel и упомянуть некоторые проблемы, которые с ними связаны. На самом деле все проблемы сводятся к двум: во-первых, не всякое вещественное число может быть представлено точно, и, во-вторых, не всякое вещественное число, представимое в виде конечной десятичной дроби, представимо в виде конечной двоичной дроби. Вторая проблема, наверное, приносит больше неприятностей начинающим пользователям, так как она менее очевидна. Рецепты преодоления этих проблем я сознательно не излагаю, так как оптимальный вариант очень сильно зависит от конкретной задачи. Человеку же, понявшему причины появления проблем, не составит труда в каждом конкретном случае подобрать наиболее приемлемое решение. В этом, собственно, и заключается разница между программистом и ламером: первый разбирается в задаче и находит для неё решение, второй умеет только кидать на форму готовые компоненты и передирать куски чужого кода. А эту статью я писал для начинающих программистов, а не для начинающих ламеров, отсюда и такой стиль.

    Огромное спасибо Елене Филипповой за помощь в поиске информации.

    Григорьев Антон

    Тип данных

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

    Целый тип

    Язык Delphi поддерживает семь целых типов данных: shortint, smailint, Longint, Int64, Byte, word и Longword, описание которых приведено в табл. 1.1.

    Таблица 1.1. Целые типы

    Тип

    Диапазон

    Формат

    Shortint

    128-127

    8 битов

    Smallint

    32 768 - 32 767

    16 битов

    Longint

    2 147 483 648 - 2 147 483 647

    32 бита

    Int64

    2 63 - 2 63 - 1

    64 бита

    Byte

    0-255

    8 битов, беззнаковый

    Word

    0-65 535

    16 битов, беззнаковый

    Longword

    0 - 4 294 967 295

    32 бита, беззнаковый

    Object Pascal поддерживает и наиболее универсальный целый тип - Integer, который Эквивалентен Longint.

    Вещественный тип

    Язык Delphi поддерживает шесть вещественных типов: Reai48, single, Double, Extended, comp, Currency. Типы различаются между собой диапазо-ном допустимых значений, количеством значащих цифр и количеством байтов, необходимых для хранения данных в памяти компьютера (табл. 1.2).

    Таблица 1.2. Вещественные (дробные) типы

    Тип

    Диапазон

    Значащих цифр

    Байтов

    Real48

    2.9x 10 -39 -1.7x10 38

    11-12

    06

    Single

    1.5 x 10 -45 -3.4х 10 38

    7-8

    04

    Double

    5.0x10- 324 -1.7x10 308

    15-16

    08

    Extended

    3.6x10- 4951 -1.1 х10 4932

    19-20

    10

    Comp

    2 63 +1 - 2 63 -1

    19-20

    08

    Currency

    922 337 203 685 477.5808 --922 337 203 685 477.5807

    19-20

    08

    Язык Delphi поддерживает и наиболее универсальный вещественный тип - Real, который э квивалентен Double.

    Символьный тип

    Язык Delphi поддерживает два символьных типа: Ansichar и Widechar:

    • тип Ansichar - это символы в кодировке ANSI, которым соответствуют числа в диапазоне от 0 до 255;
    • тип widechar - это символы в кодировке Unicode, им соответствуют числа от 0 до 65 535.

    Object Pascal поддерживает и наиболее универсальный символьный тип - Char, который эквивалентен Ansichar.

    Строковый тип

    Язык Delphi поддерживает три строковых типа: shortstring, Longstring

    • WideString:
    • тип shortstring представляет собой статически размещаемые в памяти компьютера строки длиной от 0 до 255 символов;
    • тип Longstring представляет собой динамически размещаемые в памяти строки, длина которых ограничена только объемом свободной памяти;
    • тип WideString представляет собой динамически размещаемые в памяти строки, длина которых ограничена только объемом свободной памяти. Каждый символ строки типа WideString является Unicode-символом.

    В языке Delphi для обозначения строкового типа допускается использование идентификатора string. Тип string эквивалентен типу shortstring.

    Логический тип

    Логическая величина может принимать одно из двух значений True (истина) или False (ложь). В языке Delphi логические величины относят к типу Boolean.

    Переменная

    Переменная - это область памяти, в которой находятся данные, которыми оперирует программа. Когда программа манипулирует с данными, она, фактически, оперирует содержимым ячеек памяти, т. е. переменными.

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

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

    Следует обратить внимание на то, что компилятор языка Delphi не различает прописные и строчные буквы в именах переменных, поэтому имена SUMMA, Summa и summa обозначают одну и ту же переменную.

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

    ах2 + bх + с = 0

    вполне логично присвоить имена а, b, с, x1 и х2. Другой пример. Если в программе есть переменные, предназначенные для хранения суммы покупки и величины скидки, то этим переменным можно присвоить имена

    TotalSumm и Discount или ObSumma и Skidka.

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

    В общем виде инструкция объявления переменной выглядит так:

    Имя: тип;

    где:

    • имя - имя переменной;
    • тип - тип данных, для хранения которых предназначена переменная.

    Пример:

    а: Real; b: Real; i: Integer;

    В приведенных примерах объявлены две переменные типа real и одна переменная типа integer.

    В тексте программы объявление каждой переменной, как правило, помещают на отдельной строке.

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

    а,b,с: Real; x1,x2: Real;

    Константы

    В языке Delphi существует два вида констант: обычные и именованные.

    Обычная константа - это целое или дробное число, строка символов или отдельный символ, логическое значение.

    Числовые константы

    В тексте программы числовые константы записываются обычным образом, т. е. так же, как числа, например, при решении математических задач. При записи дробных чисел для разделения целой и дробных частей используется точка. Если константа отрицательная, то непосредственно перед первой цифрой ставится знак "минус".

    Ниже приведены примеры числовых констант:

    123 0.0

    524.03 0

    Дробные константы могут изображаться в виде числа с плавающей точкой. Представление в виде числа с плавающей точкой основано на том, что любое число может быть записано в алгебраической форме как произведение числа, меньшего 10, которое называется мантиссой, и степени десятки, именуемой порядком.

    "2.4"

    "Д"

    Здесь следует обратить внимание на константу " 2.4". Это именно символьная константа, т. е. строка символов, которая изображает число "две целые четыре десятых", а не число 2,4.

    Логические константы

    Логическое высказывание (выражение) может быть либо истинно, либо ложно. Истине соответствует константа True, значению "ложь" - константа False.

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

    Именованная константа - это имя (идентификатор), которое в программе используется вместо самой константы.

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

    константа = значение;

    где:

    • константа - имя константы;
    • значение - значение константы.

    Именованные константы объявляются в программе в разделе объявления констант, который начинается словом const. Ниже приведен пример объявления именованных констант (целой, строковой и дробной).

    const

    Bound = 10;

    Title = "Скорость бега";

    pi = 3.1415926;

    После объявления именованной константы в программе вместо самой константы можно использовать ее имя.

    В отличие от переменной, при объявлении константы тип явно не указывают. Тип константы определяется ее видом, например:

    • 125 - константа целого типа;
    • 0.0 - константа вещественного типа;
    • " выполнить " - строковая константа;
    • " \" - символьная константа.


    В предыдущих уроках мы между делом знакомились с типами данных. Всё это время речь шла о простых типах. Сегодня мы обобщим пройденное ранее, а также познакомимся с новым материалом, который необходимо знать в рамках темы "Простые типы данных". Осмысленно подходить к выбору типов данных для используемых в программах переменных необходимо по разным причинам. Во-первых, имея под рукой многообразие доступных типов и умело ими распоряжаясь, можно сократить объём памяти, требуемый программе для работы. Экономию в 1-2 байта никто не заметит, но если речь идёт о больших объёмах данных, эти байты могут вылиться во вполне реальные мегабайты. Во-вторых, разумный выбор типов данных позволяет избежать некоторых ошибок, причём как со стороны программиста (на этапе создания программы), так со стороны пользователя (во время использования программы).

    Простые типы данных - общее представление

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

    • целочисленные;
    • вещественные;
    • логические;
    • строковые (символьные).

    Следует отметить, что все эти типы за исключением вещественного, упорядочены. Что это значит? А это значит, что в рамках данного типа значения расположены не в произвольном порядке, а в порядке возрастания. Зная об этом, в некоторых случаях можно исключить в своей программе лишний код. Поясню на примере, как именно упорядочены значения в этих типах данных:

    Целочисленный тип - содержит числовые значения, целые числа. Числа упорядочены по возрастанию: ..., -2, -1, 0, 1, 2, 3, ...
    Логический тип - содержит всего 2 значения - True, False, которые тоже упорядочены: False, True (следует из соответствия False - 0, True - 1).
    Символьный тип - символы кодовой таблицы. Поскольку каждому символу соответствует свой код, то символы расположены в порядке увеличения кода. К примеру, буквы латинского алфавита A, B, C, D, ... идут в кодовой таблице именно так, т.к. чем дальше от начала алфавита, тем больший код имеет буква. То же самое касается и арабских чисел в кодовой таблице - они идут по порядку: 0, 1, 2, ..., 8, 9. Это позволяет делать такие сравнения, как, например "A" < "Z" (это истинно).

    Из того, что перечисленных типы данных упорядочены, следует, что все значения образуют конечную последовательность. Это соответствует нашим представлениям о типах данных - все они имеют свои ограничения. К примеру, нет числового типа данных, который позволил бы хранить сколь угодно большое число. "Большие" типы есть, но "число" "бесконечность" они хранить не могут.

    Функции и процедуры для порядковых типов данных

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

    Pred() - функция возвращает предыдущее значение для выражения, указанного в качестве единственного аргумента.

    Примеры: Pred(5) = 4, Pred("E") = "D", Pred(True) = False.

    Succ() - функция, обратная для Pred() - возвращает следующее значение.

    Примеры: Succ(5) = 6, Succ("E") = "F", Succ(False) = True.

    Ord() - возвращает порядковый номер значения в списке значений типа данных. С этой функцией мы уже встречались при работе со строками - с её помощью мы узнавали код символа.

    Примеры: Ord("A") = 65, Ord(True) = 1.

    Low() - возвращает минимальное значение указанного типа данных.

    Примеры: Low(Byte) = 0, Low(Boolean) = False, Low(Char) = #0 (символ с кодом 0).

    High() - возвращает максимальное значение указанного типа данных.

    Примеры: High(Byte) = 255, High(Boolean) = True, High(Char) = #255 (в русской локали это символ "я").

    Ну и ещё две процедуры, с которыми мы уже знакомы:

    Dec() - уменьшает значение на единицу.

    Inc() - увеличивает значение на единицу.

    Не забывайте о втором необязательном параметре этих процедур.

    Пользовательские типы данных

    На основе порядковых типов данных программист может создать свои собственные типы - перечислимые и интервальные. Они будут рассмотрены ниже.

    Целочисленные типы

    Как следует из названия, целочисленные типы позволяют хранить целые числа. Среди них есть типы, которые хранят числа со знаком (т.е. положительные или отрицательные), а есть и такие, которые хранят только положительные. Чем большее количество значений может содержать тип, тем больше памяти он занимает. Рассмотрим целочисленные типы данных.

    Сначала рассмотрим беззнаковые типы, т.е. те, которые позволяют хранить только положительные числа и ноль:

    Byte - значения 0..255 - занимает в памяти 1 байт.

    Word - значения 0..65535 - 2 байта.

    LongWord - значения 0..4294967295 - 4 байта.

    Теперь типы со знаком (отрицательные числа записываются со знаком минус "-" впереди, неотрицательные могут записываться как со знаком "+", так и без него):

    ShortInt - значения -128..127 - 1 байт.

    SmallInt - значения -32768..32767 - 2 байта.

    LongInt - значения -2147483648..2147483647 - 4 байта.

    Int64 - значения -2 ^53 ..2 ^53 -1 - 8 байт.

    Существуют также 2 общих типа, которые находят своё отражение в вышеперечисленных. Рекомендуется использовать именно эти типы, т.к. компилятор "заточен" под них и создаёт более быстрый и эффективный код:

    Integer - значения -2147483648..2147483647 - 4 байта.

    Cardinal - значения 0..4294967295 - 4 байта.

    Следует отметить, что целые числа могут быть представлены не только в десятичной, но и в шестнадцатеричной системе счисления, т.е. в виде $xxxxxxxx, где x - один из символов 0, 1, ..., 8, 9, A, B, ..., E, F. К примеру, все цвета (точнее, их коды) представляются именно в виде шестнадцатеричных чисел.

    Логические типы

    С логическими выражениями и с логическим типом данных мы уже знакомы - это тип Boolean , принимающий значения True и False . Помимо Boolean существуют следующие логические типы: ByteBool , WordBool и LongBool . Однако последние введены лишь для обспечения совместимости с другими языками и системами программирования. Использовать рекомендуется только тип Boolean. Логическое значение в памяти занимает 1 байт. На самом деле, конечно, достаточно и одного бита, но оперировать ячейками меньше байта, мы, к сожалению, не можем.

    Символьные типы

    Символьные типы обеспечивают хранение отдельных символов. Основной тип данных - Char , который содержит символы с кодами 0..255 . Существуют ещё типы AnsiChar и WideChar . Тип AnsiChar эквивалентен типу Char , т.е. по сути это один и тот же тип. Занимает в памяти 1 байт. Для кодирования символов используется код ANSI (American National Standards Institute ). Тип WideChar кодируется международным кодом Unicode и занимает в памяти 2 байта. Таблица Unicode включает символы практически всех языков мира.

    Вещественные типы

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

    Real (он же Double ) - значения от 5.0x10 ^-324 до 1.7x10 ^308 , точность - 15-16 цифр, занимает в памяти 8 байт.

    Real48 - значения от 2.9x10 ^-39 до 1.7x10 ^38 , точность - 11-12 цифр, 6 байт памяти.

    Single - значения от 1.7x10 ^-45 до 3.4x10 ^38 , точность - 7-8 цифр, 4 байта.

    Extended - от 3.6x10 ^-4951 до 1.1x10 ^4932 , точность - 19-20 цифр, 10 байт памяти.

    Comp - от -2x10 ^63 +1 до 2x10 ^63 -1 , точность - 19-20 цифр, 8 байт.

    Currency - от -922337203685477.5808 до 922337203685477.5807 , точность - 19-20 цифр, в памяти занимает 8 байт.

    Как и в случае с целыми числами, перед вещественными числами может стоять знак "+" или "-".

    Существует 2 формы записи вещественных чисел - с фиксированной точкой и с плавающей .

    Запись с фиксированной точкой представляет собой обычную запись, в которой целая и дробная части отделены друг от друга точкой/запятой.

    Запись с плавающей точкой подразумевает запись порядка числа, который отделяется от самого числа буквой "E" (запись "e" тоже допустима). Например, запись 1.5e2 означает число 1.5 с порядком +2, т.е. это 1.5x10 ^2 = 150.

    Типы Comp и Currency были введены специально для произведения точных денежных расчётов. При этом, тип Comp , как видно из значений границ диапазона, хранит целые числа, поэтому при задании чисел с дробной частью они автоматически преобразуются в ближайшее целое число.

    Перечислимые типы данных

    От рассмотрения готовых типов данных перейдём к типам, которые могут быть созданы самим программистом. Один из вариантов, как было отмечено выше, - это перечислимый тип.

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

    Значения типа данных перечисляются через запятую, а весь этот набор заключается в круглые скобки. Описание типа должно производиться в специальном разделе раздела описаний - разделе описания типов. Этот раздел предваряется ключевым словом type . Т.е. запись идёт приблизительно так же, как и описание переменных или констант, только вместо var и const пишется type . Сам тип описывается следующим образом: название типа, далее знак равенства и далее само значение. В случае с перечислимым типом это будет набор возможных значений.

    Примечание: практически все типы данных в Object Pascal принято называть с буквы "T" (сокращённо от "Type"). Это не закон языка - просто одно из правил хорошего тона. Зная, что "T***" - это тип, вы никогда не ошибётесь, в противном же случае название можно спутать, например, с названием переменной.

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

    type TMonth = (Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec ) ; var M: TMonth; {...} M:=Jun;

    Обратите внимание, что после описания перечислимого типа в программе не может быть переменных, название которых совпадает с названием значений объявленного типа. В нашем примере не может быть переменных "Jan", "Feb" и т.д. При попытке присвоения переменной перечислимого типа значение, не указанное в списке, компилятор выдаст ошибку, поэтому ошибиться не представляется возможным.

    Раздел type существует как в модуле всей формы (в этом разделе изначально описана сама форма: TForm1 = class(TForm) ... ), так и в любой подпрограмме. Область действия типа, соответственно, определяется местом в программе, в котором он описан.

    Интервальные типы данных

    Интервальные типы данных (также их называют ограниченными) получаются из имеющихся типов путём ограничения диапазона значений. Интервал задаётся двумя константами - начальной и конечной границей. При каждом присвоении значения переменной выполняется проверка соответствия нового значения указанному диапазону. Если значение не попадает в диапазон, выдаётся сообщение об ошибке. Во время выполнения программы задание недопустимого значения к ошибке не приводит, зато значение переменной может стать неверным.
    Ограниченный тип данных можно создать только на основе простого упорядоченного типа. Значение второй константы (т.е. правой границы) должно быть больше значения первой (левой границы).
    Ограниченные типы данных также описывают в разделе type . Формат записи похожий, только между константами-границами ставятся две точки.

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

    type TDay = 1 ..31 ; TMonth = 1 ..12 ; TYear = 1900 ..2100 ;

    Помните, что использование ограниченного типа данных не уменьшит объём занимаемой памяти. Это следует из того, что задание интервала - это всего лишь условное задание возможных значений из общего набора значений данного типа.

    Заключение

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

    a - латинская) - только адрес статьи (URL);
    {{статья:122}} - полноценная HTML-ссылка на статью (текст ссылки - название статьи).

    Текст программы на языке Delphi формируется с помощью букв, цифр и специальных символов. Буквы - это прописные и строчные символы латинского алфавита и символ подчеркивания: a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z _ Цифры представлены стандартной арабской формой записи: 0 1 2 3 4 5 6 7 8 9 Специальные символы + - * / = , . : ; " () { } @ # $ & ^ применяются в основном в качестве знаков арифметических операций, разделителей, ограничителей и т. д. Из специальных символов формируются составные символы: =. . (. .) (* *) // : = Они служат, в частности, для обозначения операций типа "не равно", "больше или равно", указания диапазонов значений, комментирования программы, т. д. Алфавит

    Одно и то же число можно записать самыми разными способами, например: 15 { целое } 15. 0 { вещественное с фиксированной точкой } 1. 5 E 01 { вещественное с плавающей точкой } $F { шестнадцатиричное } В языке Delphi имеется возможность применять все способы записи, но чаще всего используют целые и вещественные числа. Целые числа состоят только из цифр и знака + или –. Если знак опущен и число не равно 0, то оно рассматривается как положительное, например: 0 { 0 интерпретируется как целое число } 17 { положительное целое число } -39 { отрицательное целое число } Числовые типы данных

    Вещественные числа содержат целую и дробную части, разделенные точкой: 0. 0 { 0 интерпретируется как вещественное число } 133. 5 { положительное вещественное число } -0. 7 { отрицательное вещественное число } Вещественные числа могут быть представлены в двух формах: с фиксированной и плавающей точкой. Форма с фиксированной точкой совпадает с обычной записью чисел: 27800 { точка в конце числа опущена } 3. 14 Форма с плавающей точкой (экспоненциальный вид) используется при работе с очень большими или очень малыми числами. В этой форме число, стоящее перед E, умножается на 10 в степени, указанной после E: 7. 13 E+14 { 7. 13 x 1014 } 1. 7 E-5 { 1. 7 x 10 -5 } 3. 14 E 00 { 3. 14 x 100 = 3. 14} Число, стоящее перед буквой E, называется мантиссой, а число после буквы E - порядком. Числовые типы данных

    С помощью комментариев вы можете пояснить логику работы своей программы. Комментарий пропускается компилятором и может находиться в любом месте программы. Комментарием является: { Любой текст в фигурных скобках } (* Любой текст в скобках со звездочками *) // Любой текст от двойной наклонной черты до конца строки Если за символами { или (* сразу идет знак доллара $, то текст в скобках считается не комментарием, а директивой компилятора. Примеры таких директив: {$OPTIMIZATION ON} {$WARNINGS ON} {$RANGECHECKS OFF} Комментарии

    Программа в процессе выполнения всегда обрабатывает какиелибо данные. Данные могут представлять собой целые и дробные числа, символы, строки, массивы, множества и др. Так компьютер всего лишь машина, для которой данные - это последовательность нулей и единиц, он должен абсолютно точно "знать", как их интерпретировать. По этой причине все данные в языке Delphi подразделены на типы. Тип данных показывает, какие значения принимают данные и какие операции можно с ними выполнять. Каждому типу данных соответствует определенный объем памяти, который требуется для размещения данных. Например, в языке Delphi существует тип данных Byte. Данные этого типа принимают значения в целочисленном диапазоне от 0 до 255, могут участвовать в операциях сложения, вычитания, умножения, деления, и занимают 1 байт памяти. Типы данных

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

    Рассмотрим форму описания переменных, констант и типов. Описание типов: type =; Описание констант: Const: =; Описание переменных: Var: ; Форма описания данных

    Пример записи констант: const Delphi. Language = "Object Pascal"; Kylix. Language = Delphi. Language; Yard = 914. 4; Foot = 304. 8; Seconds. In. Minute = 60; Seconds. In. Hour = Seconds. In. Minute * 60; // Задаем константу Seconds. In. Day = Seconds. In. Hour * 24; // как выражение При объявлении константы можно указать ее тип: Const Percent: Double = 0. 15; File. Name: string = "HELP. TXT"; Такие константы называются типизированными; их основное назначение - объявление константных значений составных типов данных. Описание констант

    Кроме стандартных типов данных язык Delphi поддерживает типы, определенные программистом. Новый тип данных определяется с помощью зарезервированного слова type, за которым следует идентификатор типа, знак равенства и описание. Например, можно определить новый тип: type TUnicode = Wide. Char; TFloat = Double; TDirection = (North, South, East, West); Нетрудно заметить, что идентификаторы новых типов в примере начинаются заглавной буквой T (первая буква слова type). Такое соглашение о типах программиста принято разработчиками среды Delphi, но оно не является строгим. Тем не менее, мы рекомендуем его придерживаться, так как оно способствует более легкому восприятию исходного текста программы. Описание типов

    Целые тип диапазон Shortint Smallint Integer Longint Cardinal Int 64 Byte Word Longword -128. . 127 знаковый 8 -bit -32768. . 32767 знаковый 16 -bit -2147483648. . 2147483647 знак. 32 -bit 0. . 4294967295 без знака 64 -bit -263. . 263 -1 знаковый 64 -bit 0. . 255 без знака 8 -bit 0. . 65535 без знака 16 -bit 0. . 4294967295 без знака 32 -bit формат Простые типы данных

    тип диапазон знач. Real 48 Single Double Extended 5. 0 x 10 -324. . 1. 7 x 10308 15 -16 2. 9 x 10 -39. . 1. 7 x 1038 11 -12 1. 5 x 10 -45. . 3. 4 x 1038 7 -8 5. 0 x 10 -324. . 1. 7 x 10308 15 -16 3. 6 x 10 -4951. . 1. 1 x 104932 19 -20 цифр Comp – 9223372036854775808. . 9223372036854775807 19– 20 Currency – 922337203685477. 5808. . 922337203685477. 5807 19– 20 Вещественные типы байт 8 6 4 8 10 8 8

    Символьные типы применяются для описания данных, значением которых является буква, цифра, знак препинания и другие символы. Существуют два фундаментальных символьных типа данных: Ansi. Char и Wide. Char. Они соответствуют двум различным системам кодировки символов. Данные типа Ansi. Char занимают один байт памяти и кодируют один из 256 возможных символов расширенной кодовой таблицы ANSI, в то время как данные типа Wide. Char занимают два байта памяти и кодируют один из 65536 символов кодовой таблицы Unicode. Кодовая таблица Unicode - это стандарт двухбайтовой кодировки символов. Первые 256 символов таблицы Unicode соответствуют таблице ANSI, поэтому тип данных Ansi. Char можно рассматривать как подмножество Wide. Char Символьный тип

    Фундаментальные типы данных: Тип данных Объем памяти (байт) Ansi. Char 1 Wide. Char 2 Обобщенный тип данных: Тип данных Объем памяти (байт) Char 1 (но может стать эквивалентом wide) Логический тип данных Var good_file: boolean; Булевские типы данных Byte. Bool, Word. Bool и Long. Bool введены в язык Delphi специально для совместимости с другими языками, в частности с языками C и C++. Все булевские типы данных совместимы друг с другом. Символьный и логический тип данных

    Пример. Описание константы и переменной символьного типа. const ch_p=’a’; //символьные константы ch_l: char=’f’; ch_k: wide. Char=’ 5’; var ch_l: char; //символьная переменная В программе значения переменных и констант символьных типов заключаются в апострофы (не путать с кавычками!), например: Symbol: = "A"; // Symbol присваивается буква A Пример символьных типов данных

    Строки – динамический массив символов. String – длина не более 256 символов. Wide. String - длина более 256 символов. Информация считается строкой, если она закрыта в одинарные кавычки: ‘Mary It Bread’ – строка « Mary It Bread’ – не строка Пример. Определить константу и переменную строкового типа. Const С_wether=’Холодно…’; Var s 1: C_wether; s 2: string; Строковый тип данных

    Перечисляемый тип. Перечисляемый тип данных представляет собой список значений, которые может принимать переменная этого типа. Каждому значению поставлен в соответствие идентификатор, используемый в программе для указания этого значения. Пример. type TColors = (red, white, blue); TMonth=(jnu, feb, mar, april, may, jun, jul, Agu, sep, oct, nov, dec); TDirection = (North, South, East, West); var Month: TMonth; Direction: TDirection; begin Direction: = North; end. Перечислимый тип данных

    На самом деле за идентификаторами значений перечисляемого типа стоят целочисленные константы. По умолчанию, первая константа равна 0, вторая - 1 и т. д. Существует возможность явно назначить значения идентификатор type TSize. Unit = (Byte = 1, Kilobyte = 1024 * Byte, Megabyte = Kilobyte * 1024, Gigabyte = Megabyte * 1024); Максимальная мощность перечисляемого типа составляет 65536 значений, поэтому фактически перечисляемый тип задает некоторое подмножество целого типа word и может рассматриваться как компактное объявление сразу группы целочисленных констант со значениями 0, 1 и т. д.

    Интервальные типы данных Интервальный тип данных задается двумя константами, ограничивающими диапазон значений для переменных данного типа. Обе константы должны принадлежать одному из стандартных порядковых типов (но не вещественному и не строковому). Пример: type TDigit = 0. . 9; digit = "0". . "9"; dig 2 = 48. . 57; var Digit: TDigit; month: 1. . 12; begin Digit: = 5; Digit: = 10; // Ошибка! Выход за границы диапазона End; Интервальный или тип-диапазон.

    Различие между таким способом создания типа и обычным (без слова type) проявится при изучении массивов, записей и классов. Забежим вперед и приведем пример: type TType 1 = array of Integer; TType 2 = type TType 1; var A: TType 1; B: TType 2; begin B: = A; // Ошибка! end. В примере переменные A и B оказываются несовместимы друг с другом из-за слова type в описании типа TType 2. Если же переменные A и B принадлежат простым типам данных, то оператор присваивания будет работать. Специальные типы данных

    Массив - это составной тип данных, состоящий из фиксированного числа элементов одного и того же типа. Для описания массива предназначено словосочетание array of. После слова array в квадратных скобках записываются границы массива, а после слова of - тип элементов массива: Type TStates = array of string; TCoordinates = array of Integer; const Coordinates: TCoordinates = (10, 20, 5); { 3 integers } var States: TStates; { 50 strings } Symbols: array of Char; { 81 characters – без определения типа} Чтобы получить доступ к отдельному элементу массива, нужно в квадратных скобках указать его индекс, например Symbols: =‘ё’; Обратите внимание, что инициализация элементов массива происходит в круглых скобках через запятую. Массивы

    Объявленные выше массивы являются одномерными, так как имеют только один индекс. Одномерные массивы обычно используются для представления линейной последовательности элементов. Если при описании массива задано два индекса, массив называется двумерным, если n индексов - n-мерным. Двумерные массивы используются для представления таблицы, а n-мерные - для представления пространств. Вот пример объявления таблицы, состоящей из 5 колонок и 20 строк: var Table: array of array of Double; То же самое можно записать в более компактном виде: var Table: array of Double; Чтобы получить доступ к отдельному элементу многомерного массива, нужно указать значение каждого индекса, например Table или в более компактной записи Многомерные массивы Table

    Пример. Описание двухмерного динамического массива элементов типа byte в переменной сon. var сon: array of byte; Пример. Многомерные массивы. var Mbon: array of byte; //четырехмерный Type Tmy_mas= array of byte; //тип – двухмерный массив var Mbon 1: array of Tmy_mas; //четырехмерный (двухмерный массив двухмерных массивов) C: array of Real; //трехмерный динамический массив Mbon – элемент массива Мbon Mbon 1 – элемент массива Мbon 1 С - // первый элемент динамического массива Примеры динамических массивов

    Множество - это составной тип данных для представления набора некоторых элементов как единого целого. Область значений множества - набор всевозможных подмножеств, составленных из его элементов. Все элементы множества должны принадлежать однобайтовому порядковому типу – базовому типу. Для описания множественного типа используется словосочетание set of, после которого записывается базовый тип множества: type TLetters = set of "A". . "Z"; var Letters: TLetters; Symbols: set of Char; В выражениях значения элементов множества указываются в квадратных скобках: , , ["A", "B", "C"]. Если множество не имеет элементов, оно называется пустым: . Пример: const Vowels: TLetters = ["A", "E", "I", "O", "U"]; Begin Letters: = ["A", "B", "C"]; Symbols: = ; { пустое множество } End. Количество элементов множества называется мощностью. Мощность множества в языке Delphi не может превышать 256. Множества

    Src="https://сайт/presentation/20070619_133096976/image-25.jpg" alt="При работе с множествами допускается использование операций отношения (=, , >=, При работе с множествами допускается использование операций отношения (=, >=,). Два множества считаются равными, если они состоят из одних и тех же элементов. Порядок следования элементов в сравниваемых множествах значения не имеет. Два множества A и B считаются не равными, если они отличаются по мощности или по значению хотя бы одного элемента. Выражение Результат True = True = True = True Операции принадлежности (>=, = B равно True, если все элементы множества B содержатся в множестве A. Выражение A

    Операция in. Используется для проверки принадлежности элемента указанному множеству. Обычно применяется в условных операторах. 5 in = True 5 in = False Операция in позволяет эффективно и наглядно выполнять сложные проверки условий, заменяя иногда десятки других операций. Например, оператор if (X = 1) or (X = 2) or (X = 3) or (X = 5) or (X = 7) then можно заменить более коротким: if X in then Операцию in иногда пытаются записать с отрицанием: X not in S. Такая запись является ошибочной, так как две операции следуют подряд. Правильная запись имеет вид: not (X in S). Операция вхождения in

    Объединение множеств (+). Объединением двух множеств является третье множество, содержащее элементы обоих множеств. + = + = Пересечение множеств (*). Пересечение двух множеств - это третье множество, которое содержит элементы, входящие одновременно в оба множества. * = * = Разность множеств (–). Разностью двух множеств является третье множество, которое содержит элементы первого множества, не входящие во второе множество. – = – = В язык Delphi введены две стандартные процедуры Include и Exclude, которые предназначены для работы с множествами. Процедура Include(S, I) включает в множество S элемент I. Процедура Exclude(S, I) исключает из множества S элемент I. Они дублирует операцию –/+ с той лишь разницей, что работают с одним элементом и делают это более эффективно. Объединение, пересечение, разность

    О чём не пишут в книгах по Delphi Григорьев А. Б.

    3.2.2. Вещественные типы Delphi

    В Delphi существует четыре вещественных типа: Single , Double , Extended и Real . Их общий формат одинаков (рис. 3.1, а).

    Знак - это всегда один бит. Он равен нулю для положительных чисел и единице для отрицательных. Что же касается размеров мантиссы и экспоненты, то именно в них и заключается различие между типами.

    Прежде чем перейти к конкретным цифрам, рассмотрим подробнее тип Real , сделав для этого небольшой экскурс в историю. Real - это стандартный тип языка Паскаль, присутствовавший там изначально. Когда создавался Паскаль, процессоры еще не имели встроенной поддержки вещественных чисел, поэтому все операции с данными типа Real сводились к операциям с целыми числами. Соответственно, размер полей в типе Real был подобран так, чтобы оптимизировать эти операции.

    а) общий вид вещественного числа

    б) Двоичное представление числа типа Single

    Рис. 3.1. Хранение вещественного числа в памяти

    Микропроцессор Intel 8086/88 и его улучшенные варианты - 80286 и 80386 - также не имели аппаратной поддержки вещественных чисел. Но у систем на базе этих процессоров была возможность подключения так называемого сопроцессора. Эта микросхема работала с памятью через шины основного процессора и обеспечивала аппаратную поддержку вещественных чисел. В системах средней руки гнездо сопроцессора обычно было пустым, т. к. это уменьшало цену (разумеется, вставить туда сопроцессор не было проблемой). Для каждого центрального процессора выпускались свои сопроцессоры, маркировавшиеся Intel 8087, 80287 и 80387 соответственно. Были даже сопроцессоры, выпускаемые другими фирмами. Они работали быстрее, чем сопроцессоры Intel, но появлялись на рынке позже. Тип вещественных чисел, поддерживаемый сопроцессорами, не совпадает с Real . Он определяется стандартом IEEE (Institute of Electrical and Electronics Engineers).

    Чтобы обеспечить в своих системах поддержку типов IEEE, Borland вводит в Turbo Pascal типы Single , Double и Extended . Extended - это основной для сопроцессора тип, a Single и Double получаются из него очень простым усечением. Система команд сопроцессора допускает работу с этими типами: при загрузке числа типа Single или Double во внутренний регистр сопроцессора последний конвертирует их в Extended . Напротив, при выгрузке чисел этих типов из регистра в память сопроцессор усекает их до нужного размера. Внутренние же операции всегда выполняются с данными типа Extended (впрочем, из этого правила есть исключение, на котором мы остановимся позже, после детального рассмотрения формата различных типов). Single и Double позволяют экономить память. Ни один из них также не совпадает с типом Real . В системах с сопроцессорами новые типы обрабатываются заметно (в 2–3 раза) быстрее, чем Real (это с учетом того, что тип Real после соответствующего преобразования также обрабатывался сопроцессором; если же сравнивать обработку типа Extended на машине с сопроцессором и Real на машине без сопроцессора, то там на отдельных операциях достигалась разница в скорости примерно в 100 раз). Чтобы программы с этими типами можно было выполнять и в системах без сопроцессора, была предусмотрена возможность подключать к ним программный эмулятор сопроцессора. Обработка этих типов эмулятором была медленнее, чем обработка Real .

    Начиная с 486-й серии Intel берет курс на интеграцию процессора и сопроцессора в одной микросхеме. Процент брака в микросхемах слишком велик, поэтому Intel идет на хитрость: если у микросхемы брак только в сопроцессорной части, то на этом кристалле прожигаются перемычки, блокирующие сопроцессор, и микросхема продается как процессор 80486SX, не имеющий встроенного сопроцессора (в отличие от полноценной версии, которую назвали 80486DX). Бывали и обратные ситуации, когда сопроцессор повреждений не имел, зато процессор был неработоспособен. Такие микросхемы превращали в "сопроцессор 80487". Но это уже из области экзотики, и, по имеющейся у нас информации, до России такой сопроцессор не дошел.

    Процессор Pentium во всех своих вариантах имел встроенный блок вычислений с плавающей точкой (FPU - Floating Point Unit), и отдельный сопроцессор ему не требовался. Таким образом, с приходом этого процессора тип Real остался только для обратной совместимости, а на передний план вышли типы Single , Double и Extended . Начиная с Delphi 4, тип Real становится синонимом типа Double , а старый 6-байтный тип получает название Real48 .

    Примечание

    Существует директива компилятора {$REALCOMPATIBILITY ON/OFF} , при включении которой (по умолчанию она отключена) Real становится синонимом Real48 , а не Double .

    Размеры полей для различных вещественных типов указаны в табл. 3.1.

    Таблица 3.1. Размеры полей в вещественных типах

    Тип Размер типа, байты Размер мантиссы, биты Размер экспоненты, биты
    Single 4 23 8
    Double 8 52 11
    Extended 10 64 15
    Real 6 40 7

    Другие параметры вещественных типов, такие как диапазон и точность, можно найти в справке Delphi.

    Из книги Язык программирования С# 2005 и платформа.NET 2.0. автора Троелсен Эндрю

    Типы, характеризуемые значениями, ссылочные типы и оператор присваивания Теперь изучите следующий метод Main() и рассмотрите его вывод, показанный на рис. 3.12.static void Main(string args) { Console.WriteLine("*** Типы, характеризуемые значением / Ссылочные типы ***"); Console.WriteLine(-› Создание p1"); MyPoint

    Из книги Советы по Delphi. Версия 1.0.6 автора Озеров Валентин

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

    Из книги Интернет решения от доктора Боба автора Сворт Боб

    Типы, характеризуемые значениями, и ссылочные типы: заключительные замечания Чтобы завершить обсуждение данной темы, изучите информацию табл. 3.8, в которой приводится краткая сводка основных отличий между типами, характеризуемыми значением, и ссылочными типами.Таблица

    Из книги Delphi. Трюки и эффекты автора Чиртик Александр Анатольевич

    Из книги Фундаментальные алгоритмы и структуры данных в Delphi автора Бакнелл Джулиан М.

    1.3.3. Delphi и CGI В данной главе я расскажу, как написать простое Дельфи CGI приложение, без использования Web Modules или других Client/Server модулей.Во первых аббревиатура CGI означает Common Gateway Interface, и это только имя для передачи информации от клиента серверу. На клиентской стороне это

    Из книги Мир InterBase. Архитектура, администрирование и разработка приложений баз данных в InterBase/FireBird/Yaffil автора Ковязин Алексей Николаевич

    2.1.1. Delphi и HTML Мой главный инструмент разработчики это Дельфи, и мы напишем Delphi Database HTML Expert в данной главе. Дельфи позволяет подсоединяться практически к базе данных любого формата. С помощью BDE к Парадоксу и dBASE, с помощью ODBC например к Access, и с помощью SQL Links к большим DBMS типа

    Из книги Виртуальная библиотека Delphi автора

    9.3. Использование OLE в Delphi Как и многие современные среды программирования, Delphi поддерживает возможность автоматизированной разработки приложений, работающих с различными СОМ-сервисами или серверами. Для более глубокого понимания принципов работы приложений,

    Из книги Описание языка PascalABC.NET автора Коллектив РуБоард

    Типы массивов в Delphi В Delphi имеется три типа поддерживаемых языком массивов. Первый - стандартный массив, который объявляется с помощью ключевого слова array. Второй тип был впервые введен в Delphi 4 в качестве имитации того, что было давным-давно доступно в Visual Basic, - динамический

    Из книги Язык программирования ABC PASCAL автора Цветков Александр Станиславович

    Вещественные типы данных К вещественным типам (их еще называют типами чисел с плавающей точкой) относятся FLOAT и DOUBLE PRECISION. Сразу следует предостеречь читателя от использования типа FLOAT - его точность недостаточна для хранения большинства дробных значений. Особенно не

    Из книги автора

    Вопросы по Delphi 2.0 Что нового в Delphi 2.0 по сравнения с Delphi 1.0? Выпущенная в феврале 1995 года версия Delphi 1.0 стала первым инструментом для Windows, комбинирующим оптимизирующий компилятор, механизмы визуальной разработки Two-Way-Tools и масштабируемую архитектуру обработки баз данных.

    Из книги автора

    Что нового в Delphi 2.0 по сравнения с Delphi 1.0? Выпущенная в феврале 1995 года версия Delphi 1.0 стала первым инструментом для Windows, комбинирующим оптимизирующий компилятор, механизмы визуальной разработки Two-Way-Tools и масштабируемую архитектуру обработки баз данных. Сегодня сотни

    Из книги автора

    Из книги автора

    Из книги автора

    Вещественные типы Ниже приводится таблица вещественных типов, содержащая их размер, количество значащих цифр и диапазон допустимых значений: Тип Размер, байт Количество значащих цифр Диапазон значений real 8 15-16 -1.8?10308 .. 1.8?10308 double 8 15-16 -1.8?10308 ..

    Похожие статьи