лекции (2003) (Глазкова) (1160821), страница 5
Текст из файла (страница 5)
В Ada и во многих современных языках программирования есть механизм возбуждения исключительной ситуации при ошибке квазистатического контроля и есть специальные средства, которые позволяют среагировать на эту исключительную ситуацию.
Система типов и подтипов позволяет достаточно гибко управлять произвольным представлением.
Одна из целей работы с без знаковой арифметикой – моделирование низкоуровневых операций. А для программ на языке Ada это очень актуально, т.к. Ada может рассматриваться как язык системного программирования. С этой точки зрения не хватает операций по mod. И в Ada 95 появились типы по mod, которые можно рассматривать как обобщение без знаковой арифметики:
type T is mod 256;
Это означает, что тип Т – это целочисленный ТД, арифметика с которым происходит по mod 256. Иначе говоря значения типа Т находятся от 0 до 255. При этом квазистатического контроля не происходит. Наиболее эффективно операции происходят, когда число, стоящее под mod, – это степень 2. В языках C# и Java есть такой специальный ТД – byte.
В каких случаях имеет смысл под mod использовать не степень 2? Это имеет смысл использовать в хэш-таблицах. Внутренняя реализация хэш-таблицы – это массив
0,1,2…N-1. Хэш-функция выдает значения от 0 до N-1.
Каждому ключу сопоставляется значение хэш-функции, которое дает индекс, куда помещается элемент с соответствующим ключом.
Наиболее хорошая хэш-функция равномерно сводит множество ключей в множество от 0 до N-1. Она получается, когда N –простое число.
Рассмотрим теперь альтернативное представление целочисленных ТД – десятичный ТД.
В базовой архитектуре системы INTEL уже есть двоично-десятичные числа. Для представления одной десятичной цифры надо 4 бита.
При двоично-десятичном представлении в 1 байт упаковываются 2 десятичных числа. Например, число 999 – это поле из 1.5 байт, число 1000 – это поле из 2 байт.
В 60-е вычисления подразделялись на научные и коммерческие (бизнес - приложения). В ряде коммерческих приложений возникла экологическая ниша для двоично-десятичного представления (это очень практично с точки зрения ввода-вывода). В языке C# есть понятие десятичного числа. Там есть специальный встроенный тип decimal – это представление десятичных чисел с точностью до 28 знаков. В языке Ada можно вводить десятичные типы:
type T is delta digits 28;
- десятичный тип, в котором указаны 28 цифр.
Чем еще удобны десятичные числа? Очень многие финансовые расчеты удобно выполнять в терминах десятичных чисел. Хотя, если посмотреть с точки зрения минимальности языковых конструкций, десятичные ТД являются избыточными. Как мы видим , языки C# и АДА не построены по принципу минимальности языковых конструкций.
Пункт 2. Логический тип данных.
Начиная с ALGOL 60, во всех ЯП кроме Си был логический ТД.
Тип данных BOOLEAN включает две константы -TRUE и FALSE.
ЯП различаются тем, есть ли неявные преобразования из BOOLEAN к целому ТД и наоборот или нет ( из рассматриваемых ЯП только в языке С++ есть такие неявные преобразования.)
Набор операций с логическим ТД везде один и тот же.
Рассмотрим пример:
A1 and A2 … and An.
В каком порядке должны вычисляться операнды этой конъюнкции? С математической точки зрения, операция and коммутативна. В программировании же операции вычисления могут иметь побочный эффект (операция меняет состояние среды – например :функция меняет значения своих параметров, ввод/вывод, изменение значений глобальных переменных и т.д.).
Если операция имеет побочный эффект, то порядок выполнения важен.
Рассмотрим пример:
While (A[i]<>X) and (i<=N) do … i=i+1;
Здесь очень важно, в каком порядке вычисляются операнды. Если i>N, то левый операнд вычислять не нужно. Но если операнды вычисляются слева направо, то этот цикл на языке Pascal ошибочен (в любом случае i станет больше N и будет ошибка).
В языке Ada попытались ввести набор операций and, or, … , про которые известно, что порядок вычисления определяется компилятором. Как только в конъюнкции/дизъюнкции операнд равен false/true, она дальше не вычисляется.
Современные ЯП идут по пути языка Си, про все логические операции которого известно, что
-
они выполняются слева направо
-
как только в конъюнкции (дизъюнкции) операнд равен false/true их вычисление прекращается.
Учитывая это, программист может писать корректные программы.
Лекция 6
Пункт 3. Вещественный ТД
Вещественные ТД нужны для математических расчетов. Отличие вещественных ТД от целых в том, что они представляют реализацию математической абстракции дробного числа.
Вещественные ТД можно подразделить на типы с плавающей точкой и с фиксированной. Во всех языках, которые мы рассматриваем, есть плавающие вещественные ТД, а в языке Ada есть ещё и фиксированные ТД. Плавающие ТД используют представление вида S*M*B^p, где S – определяет знак числа (S=+/-1), M – мантисса (М принадлежит [1/2,1) ); в ней хранятся значащие цифры, B – основание, p – порядок числа. В нормализованном представлении вещественных чисел старший бит в мантиссе всегда равен 1, за исключением числа 0. В представлении с плавающей точкой все числа имеют нефиксированную точность. Для каждого значения порядка может существовать 2^M значений. С помощью представления с плавающей точкой можно представлять как очень маленькие, так и очень большие числа, но точность представления при этом будет разная. Самые первые ЯП (АЛГОЛ 60, ФОРТРАН) были созданы для вычисления арифметических выражений, но вопросу точности вычислений не уделялось практически никакого внимания. Поэтому, например, отклонение расчетной орбиты летательных аппаратов от реальной могло составлять несколько витков, именно за счет погрешности вычислений. Погрешность появляется за счет того, что мантисса и порядок представляются конечным числом битов. Нужна была разработка специальных алгоритмов, которые позволили бы минимизировать ошибку, и чтобы на разных архитектурах программа давала бы результаты с одной и той же точностью.
Представление плавающих чисел все-таки менялось от архитектуры к архитектуре.
Для создателей языка АДА точность вычислений была одной из важных проблем программирования. В 1985 создатели языка АДА придумали модель числовых расчетов, которая базируется на понятии модельных чисел. Вместо того, чтобы фиксировать какое-то представление и программисту задавать число битов в мантиссе, программист должен указать только относительную точность.
Они ввели некий общий вещественный тип данных, базовая форма описания которого выглядит следующим образом:
Type T is digits N, где N – натуральная константа, указывающая, с какой точностью в десятичных числах будут производиться расчеты.
Например,
type FLOAT is digits 6
Это означает, что все вычисления с таким типом данных дают гарантию точности 6 десятичных знаков.
Т.о., при определении вещественного типа данных определяется точность мантиссы в десятичных знаках, а компилятор уже должен подбирать соответствующее представление таким образом, чтобы обеспечить эту точность.
Пусть b – число битов в мантиссе, тогда
b=[N*(log10 по основанию 2) + 1],
где N – точность мантиссы в десятичных знаках. Порядок p в этом случае меняется в диапазоне
-4*b <= p <= 4*b
Для представления одной десятичной цифры надо log10 по основанию 2 ~= 3.3 бита. Модельные числа таковы: на представление мантиссы надо b битов, а порядок p:
-4*b <= p <= 4*b
Модельные числа представляют абстрактную модель вычислений, которая выполняется на любой архитектуре, а компилятор должен для соответствующего модельного диапазона чисел подобрать допустимые числа, т.е. те реализации, которые есть на компьютере.
Вспомним, например, архитектуру процессоров INTEL, там есть 4 байтовое представление чисел
1 бит – знак || 23 бита – мантисса || 8 битов – порядок (-128..127).
В реализации языка Си так представляется тип FLOAT.
Существуют и более точные представления вещественных чисел, а компилятор должен выбрать требуемое.
Если задать такую точность, что ни одно из допустимых представлений не подходит, тогда у разработчика компилятора есть две возможности: либо компилятор должен реализовать программную эмуляцию соответствующих типов, либо компилятор должен сообщить программисту, что требуемая точность не поддерживается.
В 80-е гг. появился новый стандарт IEEE-754 для вычислений с плавающей точкой, который поддерживался профессиональным сообществом инженеров. Этот стандарт оказался очень уместным и все производители практически без изменений реализовали его.
Учитывая то, что все современные архитектуры так или иначе реализуют этот стандарт, в современных ЯП вводится как минимум 2 типа вещественных чисел: с 4 и 8 байтовой реализацией. Так например, в языке Java есть тип float – 4 байтовое представление в формате IEEЕ-754 и тип double – 8 байтовое представление. В стандарте IEEE-754 предусмотрена выдача константы NaN при переполнении и потере точности. Эта константа такова, что NaN*С -> NaN, где * - любая математическая операция, а С - конечная константа.
Из-за различия разрядных сеток на разных архитектурах порядок числа NaN может быть разным. И, как следствие, вычисление, прошедшее на одной архитектуре, может не пройти на другой. В этом случае само приложение должно корректно реагировать на выдачу числа NaN. Для решения этой проблемы в современных ЯП принята полностью стандартизованная реализация.
Представление вещественных чисел с плавающей точкой наиболее распространенное, но не единственное. В ряде случае возникают числа, которые с одной стороны ведут себя как вещественные ( с точки зрения множества значений), а с другой стороны они являются целочисленными (Операции с ними выполняются с одной и той же точностью). Откуда возникают такие числа? Рассмотрим программы, которые занимаются обработкой внешних данных, которые поступают с каких-то физических датчиков. Такие данные – это физический сигнал, который переводится в цифровую форму при помощи АЦП. АЦП характеризуется разрядностью регистра. Например, 12 битный АЦП выдает 2^12 значений. Он представляет физическую величину в виде 2^12 значений. Он выдает целое число, соответствующее вещественному числу в физической модели. Чтобы корректно работать с такими числами, программист должен написать специальную программу перевода целых чисел 0..2^12 в некий диапазон min..max, где min и max вещественные константы, которые представляют собой минимальное и максимальное значения оцифрованного сигнала. Подобного рода программы часто возникают в системах реального времени. Например, как мы уже говорили, создатели языка Аda ввели специальные delta-типы для решения подобного рода задач.
Type T is delta H range MIN..MAX;
Для delta-типа мы должны задать погрешность, т.е. разницу между двумя соседними дискретными сигналами (т.е. H), и диапазон MIN..MAX, где MIN,MAX - это константы какого-то вещественного типа. Компилятор сам подбирает для delta-типа соответствующее представление. При этом все delta-типы по операциям совместимы с вещественными числами. Программист работает с delta-типами так же, как и с вещественными числами, а перевод из одного представления в другое осуществляет компилятор.
Преимущества и недостатки таких представлений:
+ программист освобожден от рутинной работы,
- много времени тратится на перевод из одного представления в другое (т.к. нет машинных операций с delta-типами) и не всегда оптимально компилятор может запрограммировать такие операции. Именно поэтому delta-типы есть только в языке Аda (из рассматриваемых языков). Все остальные языки эту проблему игнорируют (работу с такими типами данных программисту придется организовывать вручную).
Пункт 4. Символьный тип данных.
СТД служит для представления текстов. Строго говоря, мы используем последовательность символов. Во многих ЯП есть тип данных string – составной ТД, который встроен в базис языка. Компьютеры изначально были созданы для проведения математических расчетов, но со временем их роль меняется. Они становятся универсальными обработчиками текстовой информации. Работа с символами является не менее важным свойством компьютеров, чем работа с числами. Но любые символы в компьютере так или иначе представляются целым типом данных. Здесь возникает понятие Char Set. Это понятие одновременно означает и набор символов, и соответствующую кодировку. Набор символов для каждого языка зафиксирован, а кодировка – это отображение набора символов в целочисленный диапазон. Проблема кодировки встала, когда люди стали передавать информацию с помощью символов. В промышленных масштабах эта проблема встала, когда появился телеграф. Кодировка Морзе – это одна из первых систем кодировок. Первые системы кодировки были в телетайпных и телеграфных устройствах.
Проблемы кодировки в компьютерах.
В разных архитектурах – различные кодировки.
В основном стояла проблема представления англоязычных терминов. Первая стандартная система представления ASCII7 – американский стандартный код для представления информации. Он и стал в конечном итоге стандартом представления англоязычной информации на компьютере.
В этой системе кодировки представляются базовые китайские символы с кодом до 32, цифры 0..9, латинские буквы a..z (как прописные, так и строчные), базовые знаки препинания и т.д.
Для представления в такой системе кодировки достаточно 7 битов (0..127). Как только центр тяжести применения компьютеров стал перемещаться к обработке данных (текстовой информации), все более возрастала необходимость представления национальных кодировок. Появились базовые системы кодировки ISO-LATIN-1 и