Оверлеи (VROOMM)

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

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

Оверлеи используются только в 16-разрядных программах DOS. В приложениях Windows для сокращения объема используемой памяти вы можете пометить сегменты как Discardable (выгружаемые).

Работа программ с оверлеями

Программа управления оверлеями (VROOMM, или Virtual Run-time Object-Oriented Memory Manager) выполняет за вас большую часть работы по организации оверлеев. В обычных оверлейных системах модули группируются в базовый и набор оверлейных модулей. Подпрограммы в данном оверлейном модуле могут вызывать подпрограммы из этого же модуля и из базового модуля, но не из других модулей. Оверлейные модули перекрывают друг друга, т.е. одновременно в памяти может находиться только один оверлейный модуль, и все они при активизации занимают один и тот же участок физической памяти. Общий объем памяти, необходимой для запуска данной программы, определяется размером базового, плюс максимального оверлейного модуля. Эта обычная схема не обеспечивает достаточной гибкости. Она требует полного учета всех возможных обращений между модулями программы и, соответственно, планируемой вами, группировки оверлеев. Если вы не можете разбить вашу программу в соответствии со взаимозависимостью обращений между ее модулями, то вы не сможете и разбить ее на оверлеи. Схема VROOMM совершенно иная. Она обеспечивает динамический свопинг сегментов. Основной единицей свопинга является сегмент. Сегмент может состоять из одного или нескольких модулей. И что еще более важно, любой сегмент может вызывать любой другой сегмент. Вся память делится на базовую область и область свопинга. Как только встречается вызов функции, которая не находится ни в базовой, ни в области свопинга, сегмент, содержащий вызываемую функцию, помещается в область свопинга, возможно, выгружая оттуда при этом другие сегменты. Это мощное средство - подобное виртуальной программной памяти. От вас больше не требуется разбивать код на статические, отдельные оверлейные блоки. Вы просто запускаете программу!

Что происходит, когда возникает необходимость поместить сегмент в область свопинга? Если эта область имеет достаточно свободного места, то данная задача выполняется просто. Если же нет, то из области свопинга, чтобы искомая свободная область освободилась, должен быть выгружен один или более сегментов. Как выбрать сегменты для выгрузки? Действующий здесь алгоритм очень сложен. Упрощенная версия его такова: если в области свопинга имеется неактивный сегмент, то для выгрузки выбирается он. Неактивными считаются сегменты, в которых в текущий момент нет выполняемых функций. В противном случае берется активный сегмент. Удаление сегментов из памяти продолжается до тех пор, пока в области свопинга не образуется достаточно свободной памяти для размещения там требуемого сегмента. Такой метод называется динамическим свопингом.

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

После загрузки оверлея в память он помещается в оверлейный буфер, который расположен в памяти между сегментом стека и дальней динамически распределяемой областью. По умолчанию размер оверлейного буфера вычисляется и устанавливается при загрузке программы, но его можно изменить при помощи глобальной переменной _ovrbuffer. Если достаточный размер памяти недоступен, то появляется либо сообщение об ошибке DOS ("Program too big to fit in memory" - "Программа слишком велика для имеющейся памяти").

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

При использовании оверлеев память распределяется, как показано на следующем рисунке:

Распределение памяти для оверлейных структур

Модель MEDIUM Модель LARGE класс CODE Резидентный класс CODE код Эти сегменты класс OVRINFO Данные для класс OVRINFO генерируются управления компоновщиком оверлеями автоматически класс STUBSEG Один сегмент класс STUBSEG stub для каждого оверлейного сегмента _DATA _DATA Ближняя динами- класс DATA класс DATA чески распреде- ляемая область и ближняя куча Отдельный стек совместно ^ сегмент ^ используют сег- стек стека стек мент данных оверлейный буфер оверлейный буфер (распределяется (распределяется при загрузке) при загрузке) дальняя дальняя динамически динамически распределяемая распределяемая v область v область Модель HUGE Резидентный класс CODE код Эти сегменты Данные для класс OVRINFO генерируются управления компоновщиком оверлеями автоматически Один дополни- класс STUBSEG тельный сег- мент для каждого оверлейного сегмента . . . Несколько сегментов данных Отдельный сегмент ^ стека стек оверлейный буфер (распределяется при загрузке) дальняя динамически распределяемая v область

Оптимальное использования оверлеев Borland C++

Для полного использования преимуществ оверлейных структур, создаваемых Borland C++, сделайте следующее:

Требования

При создании оверлеев следует помнить несколько простых правил, а именно:

Генерация оверлеев во время компоновки полностью не зависит от управления сегментами во время исполнения программы; компоновщик не включает автоматически каких-либо кодов для управления оверлеями. Действительно, с точки зрения компоновщика программа управления оверлеями является просто одним из подлежащих компоновке участков кода. Единственное предположение, которое делает компоновщик, состоит в том, что программа управления оверлеями воспринимает вектор прерываний (обычно INT 3FH), через который происходит управление динамической загрузкой. Такой уровень "прозрачности" упрощает создание пользовательских программ управления оверлеями, наилучшим образом управляющих требованиям конкретной прикладной программы.

Оверлеи и обработка исключительных ситуаций

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

Конструкция обработки исключительной ситуации включает в себя написанный пользователем блок try/catch и __try/__except. Кроме того, компилятор также может включать обработчики исключительных ситуаций и блоки с локальными динамическими переменными, спецификациями исключительных ситуаций и некоторые выражения new/delete.

Если вы пытаетесь использовать в оверлее вышеуказанные конструкции обработки исключительных ситуаций, компоновщик идентифицирует функцию и модуль следующим сообщением: Error: Illegal local public in функция in module модуль

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

Особенно внимательно нужно строить оверлейную программу, которая использует множественное наследование. Попытка создать оверлейный модуль, который определяет или использует конструкторы или деструкторы классов с множественным наследованием может привести к тому, что компоновщик будет генерировать следующее сообщение об ошибке: Error: Illegal local public in класс: in module модуль

Когда генерируется такое сообщение, идентифицированный компоновщиком модуль не следует делать оверлейным.

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

Использование оверлеев

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

Параметр -Yo распространяется на все модули и библиотеки, следующие за ней в командной строке компилятора BCC. Отменить ее можно, задав -Yo-. Эти два параметра являются единственными параметрами командной строки, которые могут следовать после имен файлов. Например, для того, чтобы сделать оверлейным модуль OVL.C, но не библиотеку GRAPHICS.LIB, можно использовать любую из следующих командных строк: BCC -ml - Yo ovl.c -Yo- graphics.lib или BCC -ml graphics.lib -Yo ovl.c

Если при запуске компоновщика TLINK явно задана компоновка файла .EXE, то в командной строке компоновщика должен задаваться параметр /o.

Предположим, вы хотите иметь оверлейную структуру в программе, состоящей из трех модулей: MAIN.C, O1.C и O2.C. Оверлеями должны являться модули O1.C и O2.C. (Программа MAIN.C содержит зависящие от текущего времени подпрограммы и обработчики прерываний и потому должна оставаться резидентной). Предположим, что данная программа использует модель памяти large.

Следующая команда позволяет выполнить данную задачу: BCC -ml -Y main.c -Yo o1.c o2.c

В результате получится выполняемый файл MAIN.EXE с двумя оверлеями.

Разработка программ с оверлеями

Этот раздел содержит важные сведения о разработке программ с оверлеями с хорошими характеристиками.

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

Вы обязаны компилировать все оверлейные модули с параметром -Y, что обеспечит оверлейную структуру генерируемого кода.

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

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

Очевидно, что решение здесь заключается в увеличении размера оверлейного буфера до таких размеров, чтобы в любой момент времени в нем помещались все часто вызывающие друг друга оверлеи. Это можно сделать, установив через глобальную переменную _ovrbuffer требуемый размер в параграфах. Например, для установки размера оверлейного буфера равным 128К, включите в ваш код следующий оператор: unsigned _ovrbuffer = 0x2000;

Общей формулы для определения идеального размера оверлейного буфера не существует.

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

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

Отладка оверлейных программ

Большинство отладчиков либо имеет весьма ограниченные средства отладки программ с оверлейной структурой, либо вообще не имеет таких средств. Иначе дело обстоит с интегрированным со средой разработки программ отладчиком Borland C++ и автономным отладчиком фирмы Borland (Turbo Debugger). Оба эти отладчика полностью поддерживают пошаговую отладку и установку точек останова в оверлеях совершенно "прозрачным" для вас способом. Благодаря использованию оверлеев вы имеете возможность легко разрабатывать и отлаживать громоздкие прикладные программы - как в интегрированной среде, так и при помощи Turbo Debugger.

Внешние подпрограммы в оверлеях

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

Если подпрограмма на языке ассемблера выполняет вызов любой оверлейной функции, то такая подпрограмма должна иметь объявление FAR и устанавливать границу стека при помощи регистра BP. Например, если OtherFunc - это оверлейная функция в другом модуле, и ее вызывает подпрограмма на языке Ассемблера ExternFunc, то тогда ExternFunc должна быть дальней (FAR) и устанавливать границы стека, как показано ниже: ExternFunc PROC FAR push bp ; сохранить bp mov bp,sp ; установить стек sub sp,LocalSize ; распределить локальные ; переменные ... call OtherFunc ; вызов другого оверлейного ; модуля ... mov sp,bp ; освобождение локальных ; переменных pop bp ; восстановление BP RET ; возврат ExternFunc ENDP

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

Эти требования остаются теми же в случае, когда ExternFunc делает косвенные ссылки на оверлейные функции. Например, если OtherFunc вызывает оверлейные функции, но сама не является оверлейной, то ExternFunc должна быть FAR и также должна устанавливать границы стека.

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

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

Свопинг

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

В обоих случаях есть две возможности: программа управления оверлеями может либо обнаруживать наличие дополнительной или расширенной памяти самостоятельно и затем брать на себя управление этой памятью, либо использовать уже обнаруженную и распределенную часть такой памяти. В случае расширенной памяти обнаружение памяти не во всех случаях выполняется удачно, поскольку многие программы кэширования памяти и программы организации виртуального диска могут использовать эту память, не делая об этом никаких отметок. Чтобы избежать этих проблем, вы должны сообщить программе управления оверлеями начальный адрес расширенной памяти и какой участок ее можно безопасно использовать. Borland С++ предусматривает две функции, которые позволяют вам инициализировать расширенную и дополнительную память - _OvrInitEms и _OvrInitExt.

Математические операции

Ниже рассматриваются возможности работы с числами с плавающей точкой и объясняется, как использовать математические операции с комплексными числами.

Операции ввода-вывода с плавающей точкой

Ввод вывод с плавающей точкой требует компоновки с подпрограммами преобразования, используемыми функциями printf и scanf. Чтобы уменьшить размер выполняемого файла, форматы с плавающей точкой автоматически не компонуются. Однако такая компоновка автоматически выполняется при использовании в программе математической подпрограммы или получении адреса некоторого числа с плавающей точкой. Если не выполняется ни одно из этих действий не выполняется, то отсутствие форматов с плавающей точкой может дать в результате ошибку ввода-вывода. Правильно оформить программу можно, например, следующим образом: /* Подготовка к выводу чисел с плавающей точкой */ #include <stdio.h> #pragma extref _floatconvert void main() { printf(*d = %f\n", 1.3); }

Сопроцессор

Си работает с двумя числовыми типами: целыми (int, short, long и т.д.) и с плавающей точкой (float double и long double). Процессор вашего компьютера легко справляется с обработкой чисел целых типов, однако числа с плавающей точкой отнимают больше времени и усилий.

Семейство процессоров iAPx86 имеет сопутствующее ему семейство математических сопроцессоров - 8087, 80287 и 80387. Мы будем обозначать все семейство математических сопроцессоров 80x87 термином "сопроцессор". (В случае процессора 80487 вы имеете математический сопроцессор уже встроенным в основной.)

Процессор 80х87 представляет собой специальный аппаратно реализованный числовой процессор, который можно установить на вашем PC. Он служит для выполнения с большой скоростью команд с плавающей точкой. При большом количестве в вашей программе операций с плавающей точкой вам, безусловно, нужен сопроцессор. Блок центрального процессора в вашем компьютере осуществляет интерфейс с 80х87 по специальным шинам интерфейса.

Эмуляция платы 80х87

По умолчанию в Borland C++ устанавливается параметр генерации кода "эмуляция" (параметр компилятора режима командной строки -f). Этот параметр предназначен для программ, которые могут вообще не иметь операций с плавающей точкой, а также для программ, которые должны выполняться и на машинах, на которых сопроцессор 80х87 не установлен.

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

Использование кода 80х87

Если вы планируете использовать вашу программу исключительно на машинах с установленным математическим сопроцессором 80х87, то можно сэкономить около 10К памяти программы, опустив из нее логику автоматического определения присутствия процессора 80х87 и эмулятора. Для этого следует просто выбрать параметр генерации кода операций с плавающей точкой при наличии сопроцессора 80х87 (или параметр компилятора режима командной строки -f87). Borland C++ в этом случае скомпонует вашу программу с библиотекой FP87.LIB (вместо EMU.LIB).

Получение кода без операций с плавающей точкой

При отсутствии в программе операций с плавающей точкой вы можете сэкономить немного времени компиляции, выбрав значение параметра генерации операций с плавающей точкой None ("отсутствуют") (или параметр компилятора командной строки -f-). Тогда Borland C++ не будет выполнять компоновку ни с библиотекой EMU.LIB, ни с FP87.LIB, ни с MATHx.LIB.

Параметр быстрых вычислений с плавающей точкой

Borland C++ имеет параметр быстрых вычислений с плавающей точкой (параметр компилятора режима командной строки -ff). Выключить этот параметр можно при помощи параметра командной строки -ff-. Его назначение состоит в выполнении некоторой оптимизации, противоречащей правильной семантике языка Си. Например: double x; x = (float)(3.5*x);

Для вычисления по обычным правилам x умножается на 3.5, давая точность результата double, которая затем усекается до точности float, после чего x записывается как double. При использовании параметра быстрых вычислений с плавающей точкой произведение типа long double преобразуется непосредственно в double. Поскольку лишь очень немногие программы чувствительны к потере точности при преобразовании от более точного к менее точному типу с плавающей точкой, то данный параметр используется по умолчанию.

Переменная операционной среды 87

При построении программы с эмуляцией сопроцессора 80x87, которая устанавливается по умолчанию, ваша программа станет автоматически проверять наличие сопроцессора 80х87 и использовать его, если он установлен в машине.

Существует ряд ситуаций, в которых вам может понадобиться отменить режим автоматического определения наличия сопроцессора по умолчанию. Например, ваша собственная исполняющая система может иметь сопроцессор 80х87, но вам требуется проверить, будет ли программа работать так, как вы предполагали, в системе без сопроцессора. Либо ваша программа предназначена для работы в системе, совместимой с PC, но данная конкретная система возвращает логике автоматического определения наличия сопроцессора неверную информацию (либо при отсутствии сопроцессора 80х87 говорит, что он на месте, либо наоборот).

Borland C++ имеет параметр для переопределения логики определения наличия сопроцессора при загрузке программы. Этот параметр - соответствующая переменная операционной среды системы 87. Переменная операционной среды 87 устанавливается в ответ на подсказку DOS при помощи команды SET: C>SET 87=N или C>SET 87=Y

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

Установка переменной операционной среды в значение Y означает, что сопроцессор на месте и вы желаете, чтобы программа его использовала. Программист должен знать следующее: если установить 87=Y, а физически сопроцессор 80х87 в системе не установлен, то система "зависнет".

Если переменная операционной среды 87 была определена (с любым значением), и вы желаете сделать ее неопределенной, введите в ответ на подсказку DOS: C>SET=

Непосредственно после знака равенства нажмите клавишу Enter, и переменная 87 станет неопределенной.

Регистры и сопроцессор 80х87

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

  1. В режиме эмуляции сопроцессора 80х87 циклический переход в регистрах, а также ряд других особенностей 80х87 не поддерживается.
  2. Если вы смешиваете операции с плавающей точкой и встроенные коды на языке Ассемблера, то при использовании регистров следует должны принимать некоторые меры предосторожности. Это связано с тем, что набор регистров сопроцессора 80х87 перед вызовом функции в Borland C++ очищается. Вам может понадобиться извлечь из стека и сохранить регистры сопроцессора 80х87 до вызова функции, использующей сопроцессор, если вы не уверены, что свободных регистров достаточно.

Отмена обработки особых ситуаций для операций с плавающей точкой

По умолчанию программа Borland C++ в случае переполнения или деления на ноль в операциях с плавающей точкой аварийно прерывается. Вы можете замаскировать эти особые ситуации для операций с плавающей точкой, вызывая в main _control87 перед любой операцией с плавающей точкой. Например: #include <floar.h> main() { _control87(MCW_EM,MCW_EM); ... }

Можно определить особую ситуацию для операции с плавающей точкой, вызвав функции _status87 или _clear87.

Определенные математические ошибки могут также произойти в библиотечных функциях, например, при попытке извлечения квадратного корня из отрицательного числа. По умолчанию в таких случаях выполняется вывод на экран сообщений об ошибке и возврат значения NAN (код IEEE "not-a-number" - "не число"). Использование NAN (нечисловых значений) скорее всего приведет далее к возникновению особой ситуации с плавающей точкой, которая в свою очередь вызовет, если она не замаскирована, аварийное прерывание программы. Если вы не желаете, чтобы сообщение выводилось на экран, вставьте в программу соответствующую версию matherr. #include <math.h> int cdecl matherr(struct exception *e) { return 1; /* ошибка обработана */ }

Любое другое использование matherr для внутренней обработки математических ошибок недопустимо, так как она считается устаревшей и может не поддерживаться последующими версиями Borland C++.

Математические операции с комплексными числами

Комплексными называются числа вида x + yi, где x и y - это вещественные числа, а i - это корень квадратный из -1. В Borland C++ всегда существовал тип: struct complex { double x, y; };

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

Для работы с комплексными числами в С++ достаточно включить файл complex.h. В complex.h для обработки комплексных чисел переопределены:

Библиотека complex активизируется только при наличии аргументов типа complex. Таким образом, для получении комплексного квадратного корня из -1 используйте: sqrt(complex(-1)) а не sqrt(-1)

Использование двоично-десятичной арифметики (BCD)

Borland C++, также как и большинство прочих компьютеров и компиляторов, выполняет математические вычисления с числами в двоичном представлении (то есть в системе счисления с основанием 2). Это иногда путает людей, привыкших исключительно к десятичной математике (в системе счисления с основанием 10). Многие числа с точным представлением в десятичной системе счисления, такие как 0.01, в двоичной системе счисления могут иметь лишь приближенные представления.

В большинстве прикладных программ двоичные числа предпочтительны, однако в некоторых ситуациях ошибка округления в преобразованиях между системами счисления с основаниями 2 и 10 нежелательна. Наиболее характерным случаем здесь являются финансовые или учетные задачи, где предполагается сложение центов. Рассмотрим программу, складывающую до 100 центов и вычитающую доллар: #include <stdio.h> int i; float x =0.0; for (i = 0; i < 100; ++i) x += 0.01; x -= 1.0; print("100*.01 - 1 = %g\1",x);

Правильным ответом является 0.0, однако ответ, полученный данной программой, будет малой величиной, близкой к 0.0. При вычислении ошибка округления, возникающая во время преобразования 0.01 в двоичное число, накапливается. Изменение типа x на double или long double только уменьшает ошибку вычисления, но не устраняет ее вообще.

Для решения этой проблемы Borland C++ предлагает специфический для C++ тип bcd (двоично-десятичный), объявленный в файле bcd.h. В случае двоично-десятичного представления число 0.01 будет иметь точное значение, а переменная x типа bcd даст точное исчисление центов. #include <bcd.h> int i; bcd x = 0.0; for (i = 0; i < 100; ++i) x += 0.01; x -= 1.0; cout << "100*0.1 - 1 = " << x << "\n";

При этом необходимо учитывать следующие особенности типа bcd:

Преобразования двоично-десятичных чисел

Тип bcd - это определяемый тип, отличный от float, double или long double. Десятичная арифметика выполняется только когда хотя бы один операнд имеет тип bcd.

Для преобразования двоично-десятичного числа обратно к обычной системе счисления с основанием 2 (тип float, double или long double), служит функция-элемент real класса bcd. Функция real выполняет все необходимые преобразования к типам long, double или lognd double, хотя такое преобразование не выполняется автоматически. Функция real выполняет все необходимые преобразования к типу long double, который может быть затем преобразован к другим типам при помощи обычных средств языка Си. Например: /* Печать чисел BCD */ #include <bcd.h> #include <iostream.h> #include <stdio.h> void main(void) { bcd a = 12.1; double x = real(a); /* преобразование для печати printf("\na = %Lg", real(a)); printf("\na = %g", (double)real(a)); cout << "\na =" << a; /* рекомендуемый метод

Отметим, что поскольку printf не выполняет контроль типа аргументов, спецификатор формата должен иметь L, если передается значение real(a) типа long double.

Число десятичных знаков

Вы можете задать, сколько десятичных знаков должно участвовать в преобразовании из двоичного типа в bcd. Это число является вторым, необязательным аргументом в конструкторе bcd. Например, для преобразования $1000.00/7 в переменную bcd, округленную до ближайшего цента, можно записать: bcd a = bcd(1000.00/7, 2) где 2 обозначает два разряда после десятичной точки. Таким образом: 1000.00/7 = 142.85714 bcd(1000.00/7, 2) = 142.860 bcd(1000.00/7, 1) = 142.900 bcd(1000.00/7, 0) = 142.000 bcd(1000.00/7, -1) = 140.000 bcd(1000.00/7, -2) = 100.000

Округление происходит по банковским правилам, что означает округление до ближайшего целого числа, причем в случае одинакового "расстояния" до ближайшего целого в прямую и обратную сторону округление выполняется в сторону четного. Например: bcd(12.335, 2) = 12.34 bcd(12.245, 2) = 12.34 bcd(12.355, 2) = 12.36 Такой метод округления задается стандартом IEEE.

Назад | Содержание | Вперед

Copyright © CIT