9.1. Представление вещественных чисел
В главе 4 мы обсуждали, как целочисленные типы используются для представления подмножества математических целых чисел. Вычисления с целочисленными типами могут быть причиной переполнения — это понятие не име-ет никакого смысла для математических целых чисел — а возможность пере-полнения означает, что коммутативность и ассоциативность арифметических
операций при машинных вычислениях не гарантируются.
Представление вещественных чисел в компьютерах и вычисления с этими представлениями чрезвычайно проблематичны — до такой степени, что при создании важных программ полезно консультироваться со специалистами. В этой главе будут изучены основные понятия, связанные с использованием ве- щественных чисел в вычислениях; чрезвычайная легкость написания в про-грамме вычислений с вещественными числами не должна заслонять глубин-ные проблемы.
Прежде всего обратим внимание на то, что десятичные числа не всегда можно точно представить в двоичной нотации. Например, нельзя точно пред-ставить в виде двоичного числа 0.2 (одну пятую), а только как периодическую I двоичную дробь:
0.0011001100110011..
Существуют два решения этой проблемы:
• Представлять непосредственно десятичные числа, например, каждому десятичному символу ставить в соответствие четыре бита. Такое представление называется двоично-кодированным десятичным числом (BCD — binary-coded decimal).
• Хранить двоичные числа и принять как факт то, что некоторая потеря точности иногда может случаться.
Представление BCD приводит к некоторому перерасходу памяти, потому что с помощью четырех битов можно представить 16 разных значений, а не 10, необходимых для представления десятичных чисел. Более существенный не-достаток состоит в том, что это представление не «естественно», и вычисление с BCD выполняется намного медленнее, чем с двоичными числами. Таким образом, мы ограничимся обсуждением двоичных представлений; читателя, интересующегося вычислениями с BCD, можно отослать к таким языкам, как Cobol, которые поддерживают числа BCD.
Числа с фиксированной точкой
Для простоты последующее обсуждение будет вестись в терминах десятичных чисел, но оно справедливо и для двоичных. Предположим, что мы можем представить в 32-разрядном слове памяти семь цифр: пять до и две после десятичной точки:
12345.67, -1234.56, 0.12
Такое представление называется представлением с фиксированной точкой. Преимущество чисел с фиксированной точкой состоит в том, что количество знаков после запятой, которое определяет абсолютную ошибку, фиксировано. Если перечисленные выше числа обозначают доллары и центы, то любая ошибка, вызванная ограниченным размером слова памяти, не превышает одного цента. Недостаток же состоит в том, что точность представления, то есть относительная ошибка, которая определяется числом значащих цифр, является переменной. Первое число использует все семь цифр представления, имеющихся в распоряжении, тогда как последнее число использует только две цифры. Хуже то, что переменная точность представления означает, что многие важные числа, такие как сумма $1532 854.07, которую вы выиграли в лотерее, или размер $0.00572 вашего подоходного налога, вообще никак нельзя представить.
Числа с фиксированной точкой используются в приложениях, где существенна абсолютная ошибка в конечном результате. Например, бюджетные вычисления обычно делаются с фиксированной точкой, так как требуемая точность представления известна заранее (скажем, 12 или 16 цифр), а бюджет должен быть сбалансирован до последнего цента. Числа с фиксированной точкой также используются в системах управления, где для взаимодействия датчиков и силовых приводов с компьютером используются слова или поля фиксированной длины. Например, скорость можно представить 10-битовым полем с диапазоном значений от 0 до 102.3 км/час; один бит будет представлять 0.1 км/час.
Числа с плавающей точкой
Ученые, которым приходится иметь дело с широким диапазоном чисел, часто используют так называемую научную нотацию
123.45 х 103, 1.2345 х 108, -0.00012345 х 107 12345000.0 х 104
Как можно использовать эту нотацию на компьютере? Сначала обратите внимание на то, что здесь присутствуют три элемента информации, которые должны быть представлены: знак, мантисса (123.45 в первом числе) и экспонента.
На первый взгляд кажется, что нет никакого преимущества в представлении чисел в научной нотации, потому что для представления мантиссы нужна разная точность: пять цифр в первом и втором числах и по восемь цифр для двух других чисел.
Однако, как можно заметить, конечные нулевые цифры мантиссы, большей 1.0 (и ведущие нулевые цифры мантиссы, меньшей 1.0), можно отбросить, если изменить значение (не точность!) экспоненты. Другими словами, мантиссу можно неоднократно умножать или делить на 10 до тех пор, пока она находится в форме, которая использует максимальную точность представления; при каждой такой операции экспонента будет уменьшаться или увеличиваться на 1 соответственно. Например, последние два числа можно записать с помощью мантиссы из пяти цифр:
-0.12345 х104 0.12345 х1012
Для вычислений на компьютере удобно, когда числа представляются в такой
стандартной форме, называемой нормализованной, в которой первая ненулевая цифра является разрядом десятых долей числа. Это также позволяет сэкономить место в представлении, поскольку десятичная точка всегда находится в одной и той же позиции, и ее не нужно представлять явно. Представление называется с плавающей точкой, потому что десятичная точка «плавает» влево или вправо до тех пор, пока число не будет представлено с максимальной точностью.
В чем основной недостаток вычислений, использующих числа с плавающей точкой? Рассмотрим число 0.12345 х 10'°, которое является нормализованной формой с плавающей точкой для числа
1 234 500 000
и предположим, что таким образом банк представил ваш депозит в размере
$1 234 567 890
Управляющий банком был бы горд тем, что относительная ошибка:
67 890
1 234 567 890
является очень малой долей процента, но вы оправданно потребовали бы ваши $67 890, которые составляют абсолютную ошибку.
Однако в научных вычислениях относительная ошибка намного важнее абсолютной погрешности. В программе, которая контролирует скорость ра-кеты, требование может состоять в том, чтобы ошибка не превышала 0,5%, Хотя это составляет несколько километров в час во время запуска, и несколь-ко сотен километров в час при приближении к орбите. Вычисления с плавающей точкой используются гораздо чаще, чем с фиксированной точкой, пото-му что относительная точность требуется намного чаще, чем абсолютная. По Этой причине в большинстве компьютеров есть аппаратные средства, которые Непосредственно реализуют вычисления с плавающей точкой.
Представление чисел с плавающей точкой
Числа с плавающей точкой хранятся как двоичные числа в нормализованной форме, которую мы описали:
-0.101100111 х215
При типичной реализации на 32-разрядном компьютере 1 бит выделяется для знака, 23 бита — для мантиссы и 8 битов — для экспоненты. Поскольку для хранения одной десятичной цифры требуется Iog2 10 = 3.3 бита, то точность представления составит 23/3.3 = 7 цифр. Если необходима большая точность, то с помощью 64-разрядного двойного слова с 52-разрядной мантиссой можно получить приблизительно 15 цифр точности представления.
Существует «трюк», с помощью которого можно увеличить количество представимых чисел. Так как все числа с плавающей точкой нормализованы и первая цифра нормализованной мантиссы обязательно 1, эту первую цифру можно не представлять явно.
Экспонента со знаком представляется со смещением так, чтобы представление было всегда положительным, и помещается в старшие разряды слова после знакового бита. Это позволяет упростить сравнения, потому что можно воспользоваться обычными целочисленными сравнениямии не выделять специально поля экспоненты со знаком. Например, 8-разрядное поле экспоненты со значениями в диапазоне 0 .. 255 представляет экспоненты в диапазоне -127 .. 128 со смещением 127.
Мы можем теперь расшифровать битовую строку как число с плавающей точкой. Строка
1 1000 1000 0110 0000 0000 0000 0000 000
расшифровывается следующим образом.
• Знаковый бит равен 1, поэтому число отрицательное.
• Представление экспоненты равно 1000 1000 = 128 + 8 = 136. Удаление смещения дает
136-127 = 9
• Мантисса равна 0.10110 ... (обратите внимание, что восстановлен скрытый бит), т. е.
1/2+1/8+.1/16 = 11/16
• Таким образом, хранимое число равно 29 х 11/16 = 352.
Как и для целых чисел, для чисел с плавающей точкой переполнение (overflow) происходит, когда результат вычисления слишком большой:
(0.5x2™) • (0.5 х 280) = 0.25 х 2150
Так как самая большая экспонента, которая может быть представлена, равна 128, происходит переполнение. Рассмотрим теперь вычисление:
(0.5 х2-70) • (0.5 х 2-80) = 0.25 х 2-150
Говорят, что при вычислении происходит потеря значимости (underflow), когда результат слишком мал, чтобы его можно было представить. Вы можете воскликнуть, что такое число настолько мало, что его можно принять равным нулю, и компьютер может интерпретировать потерю значимости именно так, но на самом деле потеря значимости часто говорит об ошибке, которая требует обработки или объяснения.
- Глава 1
- 1.2. Процедурные языки
- 1.3. Языки, ориентированные на данные
- 1.4. Объектно-ориентированные языки
- 1.5. Непроцедурные языки
- 1.6. Стандартизация
- 1.7. Архитектура компьютера
- 1.8. Вычислимость
- 1.9. Упражнения
- Глава 2
- 2.2. Семантика
- 2.3. Данные
- 2.4. Оператор присваивания
- 2.5. Контроль соответствия типов
- 2.7. Подпрограммы
- 2.8. Модули
- 2.9. Упражнения
- Глава 3
- 3.1. Редактор
- 3.2. Компилятор
- 3.3. Библиотекарь
- 3.4. Компоновщик
- 3.5. Загрузчик
- 3.6. Отладчик
- 3.7. Профилировщик
- 3.8. Средства тестирования
- 3.9. Средства конфигурирования
- 3.10. Интерпретаторы
- 3.11. Упражнения
- Глава 4
- 4.1. Целочисленные типы
- I: Integer; -- Целое со знаком в языке Ada
- 4.2. Типы перечисления
- 4.3. Символьный тип
- 4.4. Булев тип
- 4.5. Подтипы
- 4.6. Производные типы
- 4.7. Выражения
- 4.8. Операторы присваивания
- 4.9. Упражнения
- Глава 5
- 5.1. Записи
- 5.2. Массивы
- 5.3. Массивы и контроль соответствия типов
- Подтипы массивов в языке Ada
- 5.5. Строковый тип
- 5.6. Многомерные массивы
- 5.7. Реализация массивов
- 5.8. Спецификация представления
- 5.9. Упражнения
- Глава 6
- 6.1. Операторы switch и case
- 6.2. Условные операторы
- 6.3. Операторы цикла
- 6.4. Цикл for
- 6.5. «Часовые»
- 6.6. Инварианты
- 6.7. Операторы goto
- 6.8. Упражнения
- Глава 7
- 7.1. Подпрограммы: процедуры и функции
- 7.2. Параметры
- 7.3. Передача параметров подпрограмме
- 7.4. Блочная структура
- 7.5. Рекурсия
- 7.6. Стековая архитектура
- 7.7. Еще о стековой архитектуре
- 7.8. Реализация на процессоре Intel 8086
- 7.9. Упражнения
- Глава 8
- 8.1 . Указательные типы
- 8.2. Структуры данных
- 8.3. Распределение памяти
- 8.4. Алгоритмы распределения динамической памяти
- 8.5. Упражнения
- Глава 9
- 9.1. Представление вещественных чисел
- 9.2. Языковая поддержка вещественных чисел
- 9.3. Три смертных греха
- Вещественные типы в языке Ada
- 9.5. Упражнения
- Глава 10
- 10.1. Преобразование типов
- 10.2. Перегрузка
- 10.3. Родовые (настраиваемые) сегменты
- 10.4. Вариантные записи
- 10.5. Динамическая диспетчеризация
- 10.6. Упражнения
- Глава 11
- 11.1. Требования обработки исключительных ситуаций
- 11.2. Исключения в pl/I
- 11.3. Исключения в Ada
- 11.5. Обработка ошибок в языке Eiffei
- 11.6. Упражнения
- Глава 12
- 12.1. Что такое параллелизм?
- 12.2. Общая память
- 12.3. Проблема взаимных исключений
- 12.4. Мониторы и защищенные переменные
- 12.5. Передача сообщений
- 12.6. Язык параллельного программирования оссаm
- 12.7. Рандеву в языке Ada
- 12.9. Упражнения
- Глава 13
- 13.1. Раздельная компиляция
- 13.2. Почему необходимы модули?
- 13.3. Пакеты в языке Ada
- 13.4. Абстрактные типы данных в языке Ada
- 13.6. Упражнения
- Глава 14
- 14.1. Объектно-ориентированное проектирование
- В каждом объекте должно скрываться одно важное проектное решение.
- 14.3. Наследование
- 14.5. Объектно-ориентированное программирование на языке Ada 95
- Динамический полиморфизм в языке Ada 95 имеет место, когда фактический параметр относится к cw-типу, а формальный параметр относится к конкретному типу.
- 14.6. Упражнения
- Глава 15
- 1. Структурированные классы.
- 15.1. Структурированные классы
- 5.2. Доступ к приватным компонентам
- 15.3. Данные класса
- 15.4. Язык программирования Eiffel
- Если свойство унаследовано от класса предка более чем одним путем, оно используется совместно; в противном случае свойства реплицируются.
- 15.5. Проектные соображения
- 15.6. Методы динамического полиморфизма
- 15.7. Упражнения
- 5Непроцедурные
- Глава 16
- 16.1. Почему именно функциональное программирование?
- 16.2. Функции
- 16.3. Составные типы
- 16.4. Функции более высокого порядка
- 16.5. Ленивые и жадные вычисления
- 16.6. Исключения
- 16.7. Среда
- 16.8. Упражнения
- Глава 17
- 17.2. Унификация
- 17.4. Более сложные понятия логического программирования
- 17.5. Упражнения
- Глава 18
- 18.1. Модель Java
- 18.2. Язык Java
- 18.3. Семантика ссылки
- 18.4. Полиморфные структуры данных
- 18.5. Инкапсуляция
- 18.6. Параллелизм
- 18.7. Библиотеки Java
- 8.8. Упражнения