Page 1


ПРО

граммист

СОДЕРЖАНИЕ

№12 (март) 2011

Издается с марта 2010. Выходит ежемесячно №12, март 2011 г.

Редакция: Выпускающий редактор Сергей Бадло Литературный редактор Utkin Редакторы JTG, Василий Мединцев, Natali-Ka, Алексей Шульга, Егор Горохов Редактор-корректор Ян Липлавский

Дизайн и верстка:

Ян Липлавский, Natali-Ka, Сергей Бадло

Авторский состав:

Utkin, Ян Липлавский, Мират Каденов, Олег Кутков, Владимир Яковлев, Виталий Белик, Сергей Бадло

Официальный сайт журнала: www.procoder.info

Контакты:

Авторские статьи направляйте на maindatacentr@gmail.com Вопросы и предложения для редакции reddatacentr@gmail.com Вопросы и предложения администратору info@procoder.info

Информационная поддержка:

Международная Академия Информатизации (МАИН) РК www.academy.kz Журнал «Радиолюбитель» www.radioliga.com Журнал «VR-ONLINE» www.vr-online.ru V.K. сайт... www.kotoff.info Free Legal Soft Group www.flsoft.ru Некоммерческая сеть AirNet-Berdyansk www.airnet.sytes.net Электронная электротехническая библиотека www.electrolibrary.info

СЛОВО РЕДАКТОРА

Нам исполнился год! ............................................ с.3 ПОДВЕДЕНИЕ ИТОГОВ

Конкурс статей 2010 ............................................ с.4 НЕВЕРОЯТНО, НО ФАКТ

Любопытные факты ............................................... с.5 VIP ПЕРСОНА

Интервью с Мовилой Вячеславом Николаевичем ..................... с.10 АЛГОРИТМЫ

Арифметика длинных чисел ....................................... с.12 2D ГРАФИКА

Эффект плавного затемнения ..................................... с.20 ЛАБОРАТОРИЯ

Немного о DSP. Часть 2 ......................................... с.25 Компилятор домашнего приготовления. Часть 5 .................... с.30 Подключение микроконтроллера к GPS приемнику ................... с.44 АРХИВ

Кроссплатформенный осциллограф на GTK+/Cairo ................... с.52 ЮМОР

Чего только не бывает .......................................... с.56

Примечание:

Издание некоммерческое. Все материалы, товарные знаки, торговые марки и логотипы, упомянутые в журнале, принадлежат их владельцам. Статьи, поступающие в редакцию, рецензируются. Мнение авторов не всегда совпадает с мнением редакции. Перепечатка материалов журнала и использование их в любой форме, в том числе в электронных СМИ, возможны только с разрешения редакции. Тираж неограничен. Формат A4, 58 стр.

Идея создания журнала: Алексей Шульга

Обложка номера:

Дизайн Яна Липлавского

Архив номеров журнала!


ПРО

граммист

НАМ ИСПОЛНИЛСЯ ГОД! ЛЮБОПЫТНЫЕ ФАКТЫ

№12 (март) 2011

Уважаемые читатели! Нам 1 год! Возраст, можно сказать, детский. Но, в то же время, для некоммерческого проекта уже серъезный. Издание прошло много испытаний и еще многое предстоит сделать, реализовать, планов у нас «громадье». Впереди новые интересные интервью, практические материалы и уникальные разработки. Мы благодарны всем тем, кто поддержал журнал в его первых шагах и кто стал за это время друзьями нашего общего проекта. Благодарны авторам и читателям, чьи опыт и знания, пожелания и отзывы помогли начинающим и профессионалам своего дела. Кроме того, если у вас есть желание и возможность написать для нас статью, мы всегда будем рады получить от вас письмо на адрес редакции maindatacentr@gmail.com. И конечно-же поздравляем прекрасных представительниц нашей профессии с праздником Весны, с 8 марта! По итогам голосования, проводимого нами конкурса статей ушедшего 2010 года, лидирует статья Горского Александра Владимировича «Беспроводная сеть масштаба микрорайона». С чем и поздравляем счастливца и дарим ему небольшой памятный сувенир (см. стр. 4 выпуска).

В юбилейном выпуске...

Общие требования к материалам

Рубрика «VIP персона». У нас в гостях специалист

У

техники, Мовила Вячеслав Николаевич.

(используется

в области телекоммуникаций и вычислительной Как использовать длинную арифметику в своих

проектах? Как работать с числами произвольной точности? На эти и другие вопросы ответит Utkin в рубрике «Алгоритмы». Хотите

подчеркнуть

индивидуальность

Липлавский. Рубрика

«Лаборатория»

порадует

Ян

нас

Виталия Белика по приготовлению компилятора.

В рубрике «Архив» Олег Кутков расскажет о

кроссплатформенной

осциллографа на GTK+ / Cairo.

версии

к

ПО

«SCRIBUS»)

и

редакторов,

есть

некоторый

и

содержать

название

статьи,

авторах, экскурс или введение, об

используемых

средствах

теоретическую

и/или

практическую часть, заключение и ресурсы к

проектах? Читаем статью Владимира Яковлева. И

создании

разделами

разработки,

конечно же, очередной кулинарный рецепт от

требований

статья должна иметь выраженную структуру с информацию

продолжением материала про DSP-обработку от Мирата Каденова. Как использовать GPS в своих

свободное

труда

сведения об

своих

дебютирует

категоричных

желательный минимум:

рубрику «2D графика», где со статьей «Эффекты затемнения»

нет

облегчения

программ? Обратите внимание на сегодняшнюю плавного

нас

оформлению, но в связи с особенностями верстки

статье; текст

статьи

без

табуляции

в

формате

OpenOffice, VK WordPad, MS Word или обычным текстовым файлом, шрифт Arial 10;

все рисунки, таблицы должны быть подписаны и иметь упоминание в тексте;

рисунки к статье должны прилагаться в виде отдельных файлов в формате PNG, TIF; для

снятия

скриншотов

рекомендуем

использовать freeware программу G.A.P;

разделы статьи отделять двумя <ENTER>; электронный

адрес

для

maindatacentr@gmail.com.

Рубрики журнала (плавающие)

По

ИТ новости

замечаниям. Шаблон для написания статьи можно

Алгоритмы

• • • •

Отдел тестирования и общие вопросы Wi-Fi сети

Лаборатория (проекты от этапа ТЗ до сдачи)

Юмор (специфические хохмы программеров)

СЛОВО РЕДАКТОРА

присланным

рецензию

и

материалам

корреспонденции

корректирует

автор

статью

получает

согласно

взять тут.

С уважением,

выпускающий редактор, член-корреспондент МАИН

Сергей Бадло

СОДЕРЖАНИЕ

3


СОДЕРЖАНИЕ

4


ПРО

ЛЮБОПЫТНЫЕ ФАКТЫ

№12 (март) 2011

граммист

Человечеству давно известно одно из чудесных свойств серебра – антибактериальное. Наверняка вам или вашим знакомым случалось проделывать опыт с грязной водой в прозрачном стакане и последующим помещением туда серебрянной цепочки или ложки. Самое интересное в том, что к серебру у бактерий не может выработаться устойчивость. Именно этот эффект используют при изготовлении капель из коллоидного раствора серебра, а также тончайших пленок в качестве покрытия разнообразных поверхностей, например на плитке или бумаге. Основной сложностью при этом является обеспечение стабильности нанопокрытия. Однако исследователям из Бар-Иланского Университета (Израиль) совместно с коллегами из Красноярского Института химии и химических технологий удалось решить эту проблему с помощью ультразвуковых волн: наночастицы погружаются на глубину 1 микрон от поверхности бумаги, что обеспечивает стабильность покрытия. Если на такую бумагу попадают болезнетворные микробы, то они погибают в течении трех часов, что позволяет использовать ее скажем для хранения продуктов питания. Когда же сие чудо до нас еще дойдет? Но, это не все новости на сегодня. Поехали дальше… Сергей Бадло

by raxp http://raxp.radioliga.com Исскуственный глаз по подобию человеческого разрабатывают

ученые

университета

и

из

Северо-западного

Университета

Иллинойса.

Принципиальным отличием разработки является размещение

фотодатчиков

на

криволинейной

поверхности. знатоки фотосъемки непонаслышке

знают, что матрицы современных цифровиков и видеокамер четкость

обеспечивают

только

обусловлено

по

центру

плоскими

максимальную

фотографий.

матрицами

и

Это

при

фокусировке изображения выпуклыми линзами объективов Для

неизбежно

четкого

возникают

изображения

искажения.

приходится

использовать дорогую юстировку и системы из нескольких линз. Ученые поместили кремниевые

фотоприемники на вогнутую полусферическую мембрану,

что

позволило

избавиться

от

полноценный аналог потерянного органа, но и

новые возможности: широкое и одновременно четкое

поле

зрения,

увеличение

или

даже

инфракрасное видение. Последние достижения в области

создания

искусственного

глаза

опубликованы в свежем выпуске Proceedings of the National Academy of Sciences.

искажений. Кроме того, в новом образце камеры

Транзистор с двойным плавающим затвором в

фокусировки изображения, благодаря гибкости

использовали

реализована возможность «зума» и «горячей» мембраны, которая может менять свою форму и регулировать

увеличение.

Первоначально

мембраны и линза камеры-глаза плоские, но они выгибаются по мере заполнения полости камеры

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

и согласования в изменении формы линзы и

матрицы-мембраны. Камера-глаз, без сомнения, найдет широкое применение в робототехнике и, конечно, как имплантат в медицине. Причем, в

последнем качестве, человек получит не только

НЕВЕРОЯТНО, НО ФАКТ

качестве

ственного

оперативной

и

университета

Существующая

постоянной

исследователи

из

Северной

энергонезависимая

памяти

Государ-

Каролины. память

использует один плавающий затвор, который

хранит заряд для обозначения 1 или 0 (один бит информации). вид

памяти

длительное

Новый

может время

хранить информацию

в энергонезависимом

режиме

или

быстро

работать с данными в

СОДЕРЖАНИЕ

5


ПРО

ЛЮБОПЫТНЫЕ ФАКТЫ

№12 (март) 2011

граммист

энергозависимом,

электро-

на достоверность бухгалтерские и финансовые

может заменить и жесткие диски и оперативную

некоторых штатах США несоответствие данных

энергию.

т.е.

Таким

потребляя

образом,

новое

устройство

память, а также существенно ускорить загрузку компьютера. Запрет

на

продажу

телевизоров,

данные, результаты выборов и многое другое. В

закону Бенфорда даже является формальной уликой в суде.

не

Папа Римский Бенедикт XVI «благословил»

течение года в России. По словам министра,

опубликован на официальном сайте Ватикана.

поддерживающих цифровое ТВ, будет введен в Игоря

Щеголева,

благодаря

переходу

цифровое

общение

на

Как

вещание,

с

телевизионной

пристав-

не

только

и

так сложно, если у него

бегать опасности «пог-

ния

ную реальность». Пон-

с

USB.

ружения

Компания

тифик

Samsung выпускает USB-

к

себе

вок

российского

водства,

которых шивать

единого

с

запра-

портала

госус-

информацию

с

луг, оплачивать товары банковской

пользоваться нией.

картой

и

IP-телефо-

ка.

не

сети

виртуального

гаджет WIS09ABGNX размером с флешку, представляющий собой конвертор USB-Wi-Fi, работающий в диапазоне от 2.4 до 5 ГГц и

в

802.11a/b/g/n. Таким образом, можно включить телевизор в Вашу

зуемый

локальную

сеть

и

получить

возможность

2011

просмотра

напрямую с компьютера...

году

пройдет

на

YouTube.

даже

Ватикан

запустил

собст-

iPhone.

ATmel

Именно

ческий закон Бенфорда,

с

так.

выпускает

роконтроллер

что

Wi-Fi?

Фирма

128RFA1*

со

мик-

ATmega

встроен-

распределение первых цифр в числах каких-либо

ным трактом 2.4 ГГц http://www.atmel.com/dyn/

неравномерно. Цифры от 1 до 4 в таких наборах

ном

именно

статистика

реального

рождаемости

мира или

смертности, номера домов и т.п.) на первой позиции встречаются гораздо чаще, чем цифры

от 5 до 9. Практическое применение этого закона заключается в том, что по нему можно проверять

НЕВЕРОЯТНО, НО ФАКТ

5

венное приложение для

Да,

из

в

также есть свой канал

математи-

данных

Всемирный

мая. У Бенедикта XVI

телевизионных каналов и видео, которые будут транслироваться

существует

наборов

общения

Ватиканом,

ATmega

гласит,

папа

день общения, органи-

поддерживающий протокол 64/128-bit WEP, WPA/WPA2 стандарта IEEE домашнюю

целом.

на достоверность финан-

который

соци-

а говорил о феномене

распределения

данные?

челове-

Римский не упоминал,

цифр позволят проверять совые

создавать

Конкретной

альной

Какой математический закон

и

рактеристики

произ-

можно

оста-

отражали реальные ха-

приста-

помощью

призвал

сетях, которые бы не

Уже существуют образцы телевизионных

параллель-

профайлы в социальных

прави-

тельства.

в

ваться верным самому

услугам

электронного

был

нул необходимость из-

телеканалам, но и к сети Интернет

обращения

папа Римский подчерк-

сетевую

есть поддержка считыва-

помощью

ки можно будет получать доступ

в

Текст

Однако, в то же время,

свой

модель? Оказывается не

к интерактивным техно–

соцсетях.

превратить

телевизор

граждане получат доступ логиям

в

products/product_card.asp?part_id=4692. кристалле

трансивер

со

с

чувствительностью

AVR

ядром

следующими от

-100

На

од-

расположен

параметрами:

дБм,

выходной

мощностью -17 дБм, скорости потока до 2 Мбит и

возможностью криптозащиты 128-битным AES. Напряжение питания 1.8-3.6 В, ток потребления

СОДЕРЖАНИЕ

6


ПРО

ЛЮБОПЫТНЫЕ ФАКТЫ

№12 (март) 2011

граммист

Выпуск

двухъядерного

тактовой

частотой

до

3D-процессора

1.2

гигагерца

с

готовит

компания Nvidia. Мобильный чип Tegra 2 нового поколения разработан специально для устройств

со стереоскопическими дисплеями, наподобие консоли

Nintendo

3DS

и

смартфонов

с

3D-

экранами. Запуск производства процессора в

линейке Tegra намечен примерно на второй квартал этого года. Роскосмос работу

планирует

по

модулей

в

созданию

атомных

космических

этом

году

начать

стандартизированных

силовых

аппаратов.

установок

Ядерный

для

реактор,

используемый в качестве источника энергии для ионного

двигателя,

способен

вывести

космонавтику на качественно новый уровень. контроллера 4 мА, трансивера от 14 мА, в спящем режиме до 250 нА. Более

трети

всех

файлов,

размещенных

в

крупнейших торрент-трекерах и P2P-сетях (peerto-peer) – фальшивки. Такие результаты были получены

группой

европейских

ученых

из

университета Карлоса 3-го в Мадриде в ходе анализа

трекеров.

действий Как

пользователей

пользователей

считают

торрент-

исследователи:

получают

часть

деньги

от

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

Такие

торренты-пустышки,

клиенты

по

загружают

названию

похожие на те, что ищут пользователи. Это

не

очередная

лентикулярные гребнях

фотография

облака,

воздушных

волн

и

объему

НЛО. Это

образующиеся или

в

между

на

двумя

слоями воздуха. Характерной особенностью этих

облаков является то, что они не двигаются, сколь бы ни был силен ветер:

Принцип

ионизации

работы

газа

двигателя

заключается

и

электростатическим

его

полем

в

разгоне

до

высоких

скоростей, превышающих 210 км/с, что намного больше,

чем

у

классических

химических

ракетных двигателей (3-4.5 км/с). В настоящее время

ионные

двигатели

довольно

широко

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

со слабой тягой, поскольку ионный двигатель требует

очень

много

электроэнергии,

измеряемой в сотнях киловатт-часов. Директор

РКК «Энергия» Виталий Лопота расказал, что первые запуски реакторов мощностью от 150 до

500 кВт можно будет осуществить в 2020 году. На реализацию проекта потребуется около 17 млрд. руб. Кроме того, корпорация работает над концепцией

атомного

космического

буксира,

который может более чем в два раза сократить

количество расходов на выведение грузов на орбиту.

Оказывается, что некоторые из светофоров в Йоханерсбурге

(Южная

Африка)

оснащены

встроенными GSM модемами с безлимитными SIM

картами

от

мобильных

операторов,

на

случай потери связи с основной сетью. Местный предприимчивый народ повадился извлекать эти карточки и использовать их в своих целях для совершения

безлимитных

* Комментарий редакции. Спецификация

на

ресурсы журнала.

НЕВЕРОЯТНО, НО ФАКТ

упомянутый

звонков.

микроконтроллер

включена

Что в

СОДЕРЖАНИЕ

7


ПРО

граммист

ЛЮБОПЫТНЫЕ ФАКТЫ

№12 (март) 2011

миллионов

лет

назад

превратилась

в

Сверхновую. Оставшись без компаньона, Зета Змееносца стрелой полетела прочь со скоростью 24 км/сек, сметая со своего пути и сжимая

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

Знаете ли вы, что радиоизотопные батареи уже

с

толщиной

с

человеческий

волос?

Радиоизотопные батареи могут вырабатывать в

шесть раз большую плотность мощности, чем

химические. Хотя подобные источники вызывают некоторое беспокойство, однако ядерная энергия

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

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

Mozilla

объявили,

что

популярный плагин Skype Toolbar стал одной из

причин падений браузера Firefox и запретили его использование в браузере. Skype же в свою очередь

извинился

перед

пользователями

и

заявил, что в новой версии плагина все проблемы с

совместимостью

разработчиков Toolbar

в

будут

устранены.

черный

список,

Mozilla

свой

Firefox

Команда

внесла а

Skype

также

заблокировала возможность его установки на свой браузер. Следует отметить, что дополнение Skype

Toolbar

непосредственно предоставляет

для

Firefox

разрабатывается

компанией

Skype.

возможности

для

Оно

поиска

телефонных номеров на веб-сайтах. Как правило, такая

ссылка

выглядит

примерно

так:

<a

href="callto:username">Позвони мне!</a>. Астрономам удалось

НАСА

поймать

объектив

след

движется

в

в

звезды

Зеты Змееносца, которая прочь

от

космосе

своей

взор-

вавшейся соседки. Зета

Змееносца – голубой супергигант, звезда, по массе

превышающая

более

крупной

Солнце

примерно

в

двадцать раз. Когда-то она крутилась вокруг еще звезды,

НЕВЕРОЯТНО, НО ФАКТ

но

та

несколько

спутники

и

подводные

системы.

также

полупроводниковых

Инновация

заключается не только в размерах батареи, но в

характеристиках.

Вместо твердого полупроводника используется

жидкость. Критический момент в применении радиоактивного излучения структуру

источника

в

повреждает

твердого

том,

вещества,

полупроводником

проблему

минимизировать.

что

часть

кристаллическую а

с

жидким

можно

Первый в мире костыль с электрошокером и

фонариком выпустила американская компания. Помимо

способным дорогу.

того,

он

защитить

При

регулироваться

ходьбе

снабжен

вас

и

высота

устройством

подсветить

автоматически

палки

под

вас

вам

может

рост,

мощный светодиодный фонарик покажет вам в темноте куда идти. А

если на вас нападут хулиганы

грабители, сможете

достойный

или

то

дать

вы

им

отпор,

благодаря

мощному

величиной

заряда**

электрошокеру

с

до миллиона вольт.

Высокоэнергетические вывода

грузов

в

космос

излучатели

хотят

для

использовать

специалисты НАСА. Вместо химических реакций сжигания

концепция

топлива

на

лучевого

борту

ракеты

теплового

новая

двигателя

предполагает разгон ракет с помощью лазерного или

микроволнового

луча.

Эта

технология

СОДЕРЖАНИЕ

8


ПРО

граммист

№12 (март) 2011

сделает

возможным

два-пять

раз

ЛЮБОПЫТНЫЕ ФАКТЫ

создание

многоразовых

одноступенчатых ракет и позволит выводить в больше

полезного

груза,

что

значительно удешевит их отправку на низкую околоземную

орбиту.

Лучевой

тепловой

двигатель оснащен системой фокусировки СВЧ или

лазерных

лучей,

которая

направляет

энергию излучения на теплообменник на борту ракеты.

Теплообменник

жидкому

превращая

топливу, его

выталкивается

в

из

передает всего

водороду,

сопла.

Таким

образом,

горячий

** Комментарий редакции. Да

уж

...пробивное

энергию

скорее

напряжение

газ,

воздуха

при

который

получается реактивная тяга, которая не требует огромных

баков

с

окислителем,

тяжелых

двигателей и большого объема топлива. Для максимальной помощью

производиться

безопасности

лучевой в

любой

системы

высокогорных

районах.

Cовременные

массивы

из

запуск

с

должен

пустынных

коммерческие

лазеры

помещаются в контейнер и позволяют создать нескольких

сотен

излучателей.

Каждый из них может иметь индивидуальную

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

комнатной

температуре 20°C, 760 мм рт.ст и влажности 80% равняется 31.7

кВ/см, для миллиона вольт получаем ~31 см ...нехилая искорка есть сомнения в адекватности данных по костылю. Но, следует отметить, что тут важен ток. Пока спецификация на костылек не попадалась.

Наше

законодательство

простых

смертных

разрешает

электрошокеры

для

с

выходной мощностью не более 3 Дж нас

сек (1 Дж/с = 1 Вт), для работников УВД

разрешены мощностью до 10 Вт. Для

сравнения, Advanced TASER M26 имеет 26 ватт с энергией 1.75 Дж при напряжении 50 кВ и частотой повторения 15...18 Гц.

НЕВЕРОЯТНО, НО ФАКТ

СОДЕРЖАНИЕ

9


ПРО

ИНТЕРВЬЮ С МОВИЛОЙ ВЯЧЕСЛАВОМ НИКОЛАЕВИЧЕМ

№12 (март) 2011

граммист

Доброго времени суток, уважаемые читатели журнала «ПРОграммист». Сегодняшний наш гость – Вячеслав Николаевич Мовила, профессионал с огромнейшим опытом в сфере телекоммуникаций и вычислительной техники на ИТ-рынке. Как читатели, вы знакомы лишь с малой частью разработок постоянного автора нашего журнала. Надеемся, плодотворное сотрудничество продолжится, и читателей ожидают новые интересные материалы. Данное интервью было проведено в режиме вопрос-ответ… Сергей: Здравствуйте, Вячеслав Николаевич.

завод

ГАММА

Сегодня я хотел бы поговорить с вами о

бор.

Очень

Добро пожаловать в рубрику «VIP персона». ваших

планах,

разработках…

немного

о

жизни. Вот ваша биография представлена на личной

страничке

http://movila.site11.com,

но, все же, расскажите немного о себе? Вячеслав:

Здравствуйте,

Сергей.

Спасибо

за

приглашение. Да все как у всех. Детский сад, школа,

техникум,

армия,

институт

учился, много работал.

много

практически

и

наладом

дышащий,

обанкротившийся много

Радиопри-

разработчиков,

инженеров, научных работников поуходило на «рынок».

Вячеслав: Наверное, просто повезло – фирма, которую мы создали с друзьями, разработала проект

пункт».

«Автоматизированный Это

переговорный

оказалась

востребованная

разработка. Она и позволила удержаться «на плаву».

Сергей: Относительно недавно, вы сменили

Сергей:

на новую площадку, каковы причины?

ленности. Хотелось бы Вам что-то изменить?

хостинг своих проектов. Как дался переход Вячеслав:

Не

совсем

http://movilavn.narod.ru

так.

Старый

сохранился,

я

сайт

его

не

бросил и поддерживаю. Сайт на новом хостинге

был создан для того, чтобы «вживую» поработать

с CMS Joomla! Могу сказать, что данная CMS полностью оправдала все мои ожидания. На главной странице сайта http://movila.site11.com я изложил

создания

свои впечатления и выводы после

на

ней

7-ми

тематических направлений. Сергей:

Чем

Вы

реальной жизни? Вячеслав:

Много

сайтов

любите читаю,

различных

заниматься имею

в

неплохую

библиотеку – собирал много лет. Шахматы, но, к сожалению, сейчас очень мало времени. Сергей:

Как

удалось

выжить

в

условиях

после 91-го года, когда рухнула «почти вся» инфраструктура

электронной

промыш-

ленности? Громко конечно сказано, но у нас на

Украине,

в

радиоэлектронная чила

огромный

частности

Запорожье,

промышленность пинок

в

полу-

солнечное

сплетение», достаточно вспомнить бывший

VIP ПЕРСОНА

Как

Вы

состояние Вячеслав:

оцениваете

современное

радиоэлектронной

В

глобальном

промыш-

масштабе

очень

положительно. Каждый день появляется что-то новое. Используя современные комплектующие и средства разработки можно осуществить все

идеи, о которых и не мог подумать еще 5 лет

назад! Если же говорить о России и странах СНГ,

то здесь дела обстоят с точностью до наоборот – плачевно. Сергей:

Как

относятся

ваши

вашим увлечениям, работе?

близкие

к

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

Вячеслав: В 90-х были статьи в журнале «Радио» и «Вестник связи».

Сергей: Многие из наших читателей давно увлекаются рованием,

сионально.

электроникой

многие

начинающим?

А

и

программи-

занимаются

что

вы

профес-

посоветуете

Вячеслав: Учиться – благо сейчас много хорошей

СОДЕРЖАНИЕ

10


ПРО

и

ИНТЕРВЬЮ С МОВИЛОЙ ВЯЧЕСЛАВОМ НИКОЛАЕВИЧЕМ

№12 (март) 2011

граммист

доступной

Разрабатывать

литературы

для

начинающих.

существует

возможность

собственные

сегодняшний

день

проекты.

На

отладить проект в виртуальном режиме, не беря

в руки паяльник, в среде симуляции, например,

Сергей:

Какие

издания

печатные

являются

для

ориентиром?

электронные

вас

идеалом,

такой как «Proteus». Далее, конечно, необходима

Вячеслав:

не

http://www.radio.ru, журнал «Chip» http://ichip.ru,

доводка и отладка на «живую». Но и тут можно создавать

макет

фирменный

с

нуля,

Start-Kit.

а

приобрести

Например,

контроллеров AVR существует «AVR-Dragon». Сергей:

Занимались

ли

вы

для

препода-

вательской деятельностью? Есть ли у Вас

Основные

и

электронные

журнал «Вестник связи» http://vestnik-sviazy.ru.

Сергей: Пятерка ваших ежедневных просмотров интернет-ресурсов?

ученики?

Вячеслав: из форумов стоит выделить:

Вячеслав: Нет. Но был руководителем дипломных

http://electronix.ru;

http://dumpz.ru.

проектов у 7-ми студентов.

Сергей: Как мы уже знаем из общения с

http://kazus.ru;

Вами, ваши работы связаны с DSP. Не могли

Из сайтов:

читателям – с чего началось, в какой области

Вячеслав:

Сергей: Боретесь ли вы с плагиатом?

бы

вы

подробнее

рассказать

нашим

ведутся разработки?

Разработки

с

использованием

DSP

TMS320 связаны с текущей работой. Из-за чего – необходима

32-х

разрядная

архитектура,

скорость работы, математические библиотеки, надежность.

Сергей: Периодически просматривая отзывы

о журнале на различных блогах, форумах, мы часто натыкаемся на высказывания в

роде: «…в журнале для программистов не

ресурсы

перечислены ниже. Печатные: журнал «Радио»

http://rusdoc.ru; http://gaw.ru.

Вячеслав: Нет. Бесполезное это дело. Сергей: Каковы планы на будущее или... может уже претворяете в жизнь?

Вячеслав: Есть новые проекты. Есть желание написать цикл статей о DSP TMS320.

должно быть электроники». Но, ведь это

Сергей: И резюмируя нашу встречу: чтобы

электроника уже неразрывны. Каково ваше

проектам и нашим читателям в частности?

реалии личное

жизни,

программирование

отношение

к

и

электронике

в

программировании и к тематике журнала?

Вячеслав: Действительно – для программистов, занимающихся, разработками

например,

статьи

об

только

электронике

Web-

Вы

хотели

Вячеслав:

пожелать

Работать

и

некоммерческим

учиться,

никогда

не

унывать. Помнить – много больших и успешных проектов начинались энтузиастами в гаражах.

не

интересны. Но ведь существует большая группа программистов, разрабатывающая программное обеспечение

для

устройств

на

базе

микроконтроллеров. Для них необходимы знания электроники

и

тонкостей

программирования

аппаратной части микроконтроллеров и ПК. Для этой

группы

программистов

Ваш

журнал,

безусловно, является необходимым и полезным.

VIP ПЕРСОНА

Вячеслав Николаевич,

спасибо вам за интересное интервью.

Желаем вам и вашим близким здоровья, счастья, успехов во всем.

Наши читатели с нетерпением будут

ждать ваших новых публикаций.

СОДЕРЖАНИЕ

11


ПРО

АРИФМЕТИКА ДЛИННЫХ ЧИСЕЛ

№12 (март) 2011

граммист

Рано или поздно программист сталкивается с задачей, когда имеющийся стандартный числовой тип не позволяет хранить весь необходимый для работы программы диапазон значений. Решается это обычно полумерами – как правило, требуется одна-две операции и, в подавляющем большинстве случаев, это целочисленные сложение и умножение. Многие утверждают, что имеющихся встроенных числовых типов данных достаточно, и нет задач, для которых требуются «длинные» числа. Однако, это не так. Навскидку – математика, астрономия, исследовательская работа (например, шифрование и сжатие информации). Скажу даже более, такие задачи не распространены как раз по причине слабой поддержки длинных чисел. Впрочем, разработчики многих современных языков программирования постепенно осознают это и «длинная» арифметика имеется в ряде языков высокого уровня – Scheme, Python, Ruby и т.д. Прочие языки имеют соответствующие библиотеки. Статья рассматривает основные вопросы, связанные с созданием собственного набора функций и процедур для работы с так называемыми длинными числами. Utkin http://procoder.info

В-третьих,

E-mail: utkin295@yandex.ru

длинной

арифметики

в

собственных программах. Зачем это надо? Во-первых,

это

просто

интересно.

своих

Во-вторых,

имеющиеся библиотеки не всегда удовлетворяют нашим

строгим

указанных

выше

используются

в

требованиям. языках

Например,

в

программирования

основном

целочисленная

длинная арифметика, а как же вещественные

числа? Точность, прежде всего, никаких игр с плавающими числами, округлениями и т.д.

АЛГОРИТМЫ

хотел

математика

В данной статье речь пойдет о том, как сделать поддержку

я

бы

продемонстрировать

некоторые аспекты чисел, чтобы показать, что –

это

не

самостоятельная

дисциплина, а часть логики (то есть арифметика может быть выражена логически).

Существуют еще как минимум пара причин, по которым я взялся за эту работу. Первое – это имеющиеся

в

сети

библиотеки,

часто

не

универсальны, либо и вовсе ограничены (часто не имеют даже четырех основных действий). Я буду

рассматривать

числами

диапазон

в

произвольной значений

содержащим

произвольное

их

дальнейшем точности

которых

типом

число

и,

разрядов.

не

работу

с

числами,

ограничен

допускающими Второе

при

СОДЕРЖАНИЕ

12


ПРО

АРИФМЕТИКА ДЛИННЫХ ЧИСЕЛ

№12 (март) 2011

граммист

поиске

и

опробовании

различных

библиотек,

имеющихся в сети, я натолкнулся на очень неприятный

исходник,

содержащий

вирус,

свойствами напоминающий Induc.

производителей, часто имеют свои собственные наборы

команд,

конкурентами,

иногда

что

не

совместимые

затрудняет

с

создание

программы, которая бы работала на большинстве

компьютеров. Если Вы заметили, я стараюсь

Учимся считать заново

получить арифметику, полностью оторванную от

Да, нам придется это делать самостоятельно.

конкретной платформы…

Современные процессоры могут работать только

Ну, мы немного отвлеклись – вернемся к нашей

и есть отражение в типах «классических» языков

строке, но также есть определенные нюансы.

с определенными типами чисел – собственно они программирования

вроде

С++

и

Паскаль.

Программист дает приказы процессору – сложи два и два и результат засунь сюда. Для длинной арифметики

процессор,

даже

«имеющий

встроенный математический сопроцессор», не

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

работы будем осуществлять аналогично: тому чему нас учили в школе, а именно – в столбик.

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

продумать

структуры

данных

иными словами как мы будем выражать числа. Я

предлагаю весьма простое решение – каждый

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

сократить

время

на

отладку

структуре. Другое представление это хранение в

Связаны они с тем, что наши числа ограничены только

выделяемой

памятью

и

не

имеют

ограничения на число разрядов. В связи с этим

числа произвольной точности обладают очень

неприятным для меня свойством – они могут увеличиваться в обе стороны. Иными словами из

имеющегося числа в результате операции можно получить

другое

отличаться

от

число,

исходного

которое

как

может

количеством

разрядов в дробной, так и в целой частях чисел. Строка устроена так, что в нее относительно

легко можно добавить символы в конец, но никак не в начало строки (то есть операция такая конечно имеется, но вот скорость ее выполнения в

большинстве

лучшего).

случаев

Можно

оставляет

конечно

желать

резервировать

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

алгоритмов. Минус – неэффективность хранения,

Строка, по сути, динамический массив символов,

шестнадцатеричной системой счисления.

что если воспользоваться структурой, которая

например,

в

сравнении

с

той

же

Поскольку основной единицей информации в компьютере

является

байт,

предлагаемая

система задействует далеко не всю выделяемую

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

вещественными

немаловажная

числами

причина:

вещественная

арифметика

реализуется.

Алгоритм

почему

также

длинная

практически

работы

не

требует

и это подтолкнуло меня на интересную мысль, могла

бы

начало, что

два

и

получили

не

надо.

Альтернативное

вычисления

с

развитие

использованием

процессоров видеокарт, однако и здесь не все гладко. Основной минус – видеокарты разных

АЛГОРИТМЫ

и

в

конец?

Ничего

более

динамических

массива

соединены

байтовой последовательностью.

Байтовые последовательности

при

тогда

программисты

так

«головами» в одной точке. М-м-м, назовем это

поступили философски – неудобно делать? А

Поэтому

телодвижений

оригинального я не придумал, как представить,

Давайте

работу.

лишних

увеличиваться в обе стороны? То есть как в

дополнительных суетливых движений и в целом замедляет

без

подробней

рассмотрим

работу

с

алгоритмов

работы

с

байтовыми последовательностями – это поможет проектировании

числами. Итак, как правило, программист не имеет представления, как располагаются данные его

программы

определенные

(если

усилия

для

не

прикладывает

этого)

в

памяти

СОДЕРЖАНИЕ

13


ПРО

граммист

№12 (март) 2011

машины.

Вот

образец,

АРИФМЕТИКА ДЛИННЫХ ЧИСЕЛ

некоторого

состояния

памяти машины, где задействованные ячейки выделены цветом:

последовательность может увеличиваться в обе стороны, относительно условного центра. Динамические

Расположение информации в памяти

массивы

в

последовательности

модели

байтовой

Обратите внимание на расположение индексов

относительно друг друга. Чтобы рассмотреть подробно, давайте представим число в байтовой последовательности Далее,

выделим

цветом

наши

динамические

массивы, условно назовем их левой и правой

частями динамического массива (Left и Rigth). Как

они

расположены

и

в

каком

порядке

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

нас не должно. Пусть этим занимается менеджер

(следующее

изображение).

Как видно, разряды в левой части байтовой

последовательности (динамический массив Left), располагаются

задом

наперед,

отношению к правой части. Число

6268913569710

зеркально

в

последовательности

по

байтовой

памяти. Собственно с этой целью мы и будем использовать байтовую последовательность – нам не интересно как будет располагаться число в

конкретных ячейках памяти, но нам надо четко представлять ности,

чтобы

модель не

байтовой

путаться

в

последовательдальнейшем.

Обратите внимание, что по определению массива

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

Это

видно,

если

представить

массивы отдельно друг от друга.

динамические

Число 6268913569710 в динамических массивах Зачем это нужно? Дело

Расположение динамических массивов

в том, что вычисления

мы будем производить так же, как и любой другой

человек

это

делает в уме или на бумажке.

То

есть

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

Например,

последовательности – то есть все логично – левый

есть с самим собой) – в результате образуется

представляем динамические массивы в байтовой край

слева,

показывает

правый

рисунок

справа.

При

предыдущий,

этом где

как

они

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

последовательности

динамические

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

АЛГОРИТМЫ

сложение,

возьмем

наше

6268913569710 и сложим с 6268913569710 (то число,

в

превышает

котором

предыдущее

количество и

в

разрядов

зависимости

от

ситуации нам удобно добавлять данные, как в

левую, так и в правую части. Забегая вперед, скажу,

что

большая

часть

операций

производится с правой частью – так удобней, проще и приятней.

СОДЕРЖАНИЕ

14


ПРО

АРИФМЕТИКА ДЛИННЫХ ЧИСЕЛ

№12 (март) 2011

граммист

Однако,

теперь

нам

необходимо

как-то

обращаться к нашим разрядам, как минимум,

Готовимся к работе

чтение/запись. Делать это удобно с конца правой

Прежде

надо

Здесь все просто, можно использовать один байт

части (вообще право/лево все это условно, но же

как-то

представлять

абстракцию).

Иными словами, если мы наше число поделим на

10, то в байтовой последовательности разряд с

нулевым индексом будет содержать цифру 1. Делается

это

простым

пересчетом

индексов

динамических массивов, главное, аккуратность и внимательность.

Нумерация разрядов числа в рамках модели байтовой последовательности

всего,

байтовая

последовательность

является хранилищем разрядов. А как же знак? (я бы рекомендовал слово вместо байта) или

булеву величину. Затруднение может вызвать, разделение числа на целую и дробную часть. Можно

конечно

использовать

последовательности,

но

как

две

байтовые

показывают

различные эксперименты – число по своей сути

абстракция единая, а целая и дробная часть это характеристика отношению

соразмерности

к

другому

в

числа

рамках

по

десятичной

системы. Пока на этом можно не заострять внимание и принять данную концепцию на веру.

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

инициализация байтовой последовательности (освобождение

всех

выделение

с

массивов);

байт

последовательности; выделение

байт

последовательности;

элементов

левой

части

с

правой

обоих

байтовой части

освобождение байт слева (изменение размера динамического массива); освобождение

байт

справа

(изменение

байта

(индексация

размера динамического массива); изменение справа); чтение

справа);

указанного

указанного

определение

числа

последовательности; копирование

байта байт

(индексация в

одной

последовательности в другую.

байтовой байтовой

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

числе,

последовательности уровень

концепция

это

абстракции,

тот

автоматизировать этот процесс.

АЛГОРИТМЫ

самый

который

вещественных эквивалентен умноженных счисления)

чисел

результат

делению

на

в

положительной,

10

(как

двух

и

целых

основание

определенной так

будет

всегда

чисел,

системы

степени

(как

отрицательной).

Еще

конкретней – 6.5 / 3.7 эквивалентно 65 / 37, что эквивалентно 650 / 370 или эквивалентно 0.65 / 0.37.

Таким

образом,

вещественными

операции

числами

над

производятся

полностью аналогично операциям над числами целыми.

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

хранить

информацию

есть

незначащие

только

о

числе

действительных разрядов в дробной части (то нули

мы

хранить

не

собираемся), все разряды пусть находятся в едином

хранилище

последовательности. определенные

Эта

хитрость

преимущества,

байтовой

дает

нам

проявляющиеся

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

части

результата,

в

тоже

проводить умножение двух целых чисел.

время

байтовой

Однако, на этом еще не все. Нам ведь надо как-то

позволяет

способом – будем получать числа из строки и

нижний

получать числа. Воспользуемся самым простым

вводить число также в строку. Таким образом,

СОДЕРЖАНИЕ

15


ПРО

граммист

№12 (март) 2011

работа

будет

АРИФМЕТИКА ДЛИННЫХ ЧИСЕЛ

осуществляться

примерно

следующим образом*: •

получим числа из строк (обычно операции

произведем

производятся над двумя и более числами); расчеты;

необходимые

математические

переведем результат из нашего типа в строку.

Строка очень удобна в том плане, что ее удобно передавать дальше – для вывода на экран или в файл.

четырех основных арифметических действий нет никакой

разницы

между

целыми

и

вещественными числами. Поэтому мы можем

складывать целые числа, а уже потом определять Однако

есть

еще

один

вариант

разрядов.

Например,

657,13

1,2689. Преобразуем числа:

+

Что мы сделали? Мы просто выровняли число в

обоих

числах

Но

давайте

вернемся

к

незначащими разрядами еще не все. Вот пример, 6,2 + 7,91

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

старший разряд. В результате при сложении разрядов

6

+

разряда)

7

+

1

(перенос

образуется

из

перенос

в

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

всегда. Поэтому в байтовой последовательности

результата необходимо всегда формировать на 1 разряд больше. То есть перед операцией:

осуществлять сложение относительно положения разделителя

бумаги.

процессу беззнакового сложения. Дополнение

младшего

Ранее я уже упоминал, что при проведении

разрядов

листочке

старших

Сложение

запятую.

если бы это делал я с цифрами, написанными на

относительно

разделителя разрядов в целой и дробной частях. Еще пример: 0,01 + 569

После:

В случае же, если переноса в старший разряд не

произойдет, то ничего страшного в этом нет, концепция

байтовой

последовательности

позволяет нам работать с числом как с массивом разрядов,

для

которого

можно

добавлять

и

убирать разряды как с левой (старшая часть числа), так и с правой (младшей) части числа.

Концепция

байтовой

последовательности

позволяет нам добавлять в числа разряды с обеих

сторон. Забегая вперед - речь идет только о беззнаковом знаков

сложении.

наших

чисел

Сложение

это

с

просто

учетом

составная

процедура, которая в зависимости от знаков, производит либо сложение, либо вычитание. То

есть сложение осуществляется точно так же, как * Комментарий автора. Кстати,

мое

мнение

касательно

первого

пункта.

Данная

арифметика предназначена для использования людьми, поэтому предполагает

некоторые

удобства.

Например,

я

реализовал

получения числа из строки, где в качестве разделителя целой и

дробной частей могут выступать и точка и запятая. Работа здесь

проста – определяется ближайший из знаков – точка или запятая, если таковой найден, то именно он и считается разделителем разрядов в целой и дробной частях числа.

АЛГОРИТМЫ

Здесь

следует

левую/правую

объясниться

части

байтовой

не

путайте

последователь-

ности и целую и дробную части числа – это разные понятия разных абстракций. Они между

собой никак не связаны и одно и то же число

может быть представлено разными способами – например,

только

последовательности числа

из

задействовать

левой

(обычно

строки

обе

частью при

нет

байтовой

получении

необходимости

части

байтовой

последовательности) или только правой частью (например,

после

нескольких

байтовой

последовательности

содержать

дробную

разного

вида

число

не

операций над числом). При этом как бы в

располагалась, оно может быть и только целым и часть.

Установить,

как

расположено число можно (анализируя размеры

СОДЕРЖАНИЕ

16


ПРО

АРИФМЕТИКА ДЛИННЫХ ЧИСЕЛ

№12 (март) 2011

граммист

массивов Left и Rigth байтовой последователь-

Эта

Теперь рассмотрим указанные действия более

Кроме того, преследуется еще одна цель – мы

умножение

разрядов, куда должен помещаться результат.

ности), но, как правило, это не требуется. подробно.

Дело

сложение

и

в

том,

операции

что

вычитание

очень

похожие

и

на

поэтому

понимание

процесса

алгоритмов

остальных

арифмети-

операция

подразумевает,

что

операция

сложения применяется к первому операнду. сразу

формируем

некоторое

количество

сложения поможет в дальнейшем составлении

4. Добавим в результат разряд слева

ческих операций.

То есть один незначащий нуль перед разрядами

собственных

результата.

1. Рассмотрим частные случаи Сложение больших чисел может быть занятием длительным,

однако

довольно-таки

часто

возникает ситуация, когда один из операндов

является нулем. В таком случае ответ очевиден – это

оставшийся

Он

ответственен

за

размещение

переноса при сложении самых старших разрядов

операнд.

Правило

это

реализуется эмпирически: то есть сам алгоритм

чисел. Дело в том, что какие бы выровненные

числа Вы бы не складывали результат всегда будет содержать максимум столько же разрядов

сколько и выровненные числа + 1 разряд. Если

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

и так способен посчитать операцию с такими

5. Определим число разрядов в слагаемых

быстрей,

из

Здесь можно определить общее число разрядов в

байтовая

сложить число разрядов в левой и правой частях

параметрами,

если

однако

сделать

точно

знать,

это

что

можно

один

операндов есть нуль. Сделать это просто – нуль в нашем

случае

последовательность части

разрядов

не

пустая

(то

есть

левая

содержат).

Все

и

правая

операции

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

работы будет нуль, то это будет нуль такого вида

любом

из

байтовой разрядов

выровненных

операндов

последовательности).

в

числе

нам

(можно

Знать

необходимо,

сложение осуществляется поразрядно.

число

так

как

(а я уже указывал, что одно и то же число можно

6. Сложение

последовательности). Нуль другого вида можно

Сам процесс сложения несложен, есть только

по-разному

представить

в

рамках

байтовой

получить, только если вручную принудительно устанавливать

разряды

в

байтовой

пара нюансов...

последовательности (это возможно, потому что

6.1. Необходимо позаботиться о переносе, при-

не будет и вычисление пройдет по основному

том,

число это просто запись). Такой нуль распознан алгоритму (что, конечно же, медленней). 2.

Выровняем

оба

разделителя разрядов Здесь

реализуется,

числа

то,

что

относительно мы

чем на самом раннем этапе сложения. Дело в что

фактически

во

время

сложения

участвуют 3 цифры – разряды операндов и

перенос. Для самых младших разрядов перенос

равен нулю (однако, это не означает, что его складывать не надо).

подробно

6.2. Читаем соответствующие разряды чисел.

в обоих числах. Этот шаг связан с компьютерной

игры с разрядами чисел обычно не поощряются.

рассматривали до этого – выравнивание разрядов реализацией, выравнивать

то

есть,

разряды

человеку

при

нет

сложении,

смысла

он

предполагает во время операции сложения.

это

3. Подготовим результат Просто скопируем первый операнд в результат.

АЛГОРИТМЫ

Здесь необходимо одно замечание. В математике Поэтому мы читаем соответствующие разряды в

байтовой последовательности. Это важно для понимания

алгоритмов. 6.3.

работы

Осуществляем

реноса.

этого

и

сложение

последующих

разрядов

и

пе-

CОДЕРЖАНИЕ

17


ПРО

АРИФМЕТИКА ДЛИННЫХ ЧИСЕЛ

№12 (март) 2011

граммист

6.4. Осуществляем анализ результата. Дело в том, что мы храним число в виде разрядов.

Однако, возможна ситуация когда получится

число более 9, а это уже два разряда, а не один. Поэтому если перед нами результат более 9, то

сложение.

Таким

образом,

незначащие

нули

увеличивают время необходимое на сложение

длинных чисел. Да и незначащие нули занимают

байты, а у нас хранение чисел и так реализовано не самым эффективным образом.

нам необходимо взять только младший разряд

Вычитание

никогда

Практически полностью аналогично сложению,

двухзначного

числа.

не

разряд

превысит

можно

результата

следующего

К

19,

получить,

10.

счастью,

поэтому

если

младший

вычесть

Соответственно

шага

результат

перенос

сложения

будет

из

для

равен

единице. Если же результат сложения разрядов

будет менее 10, тогда все в порядке – результат содержит

всего

осуществлять

нет

один

разряд

необходимости

следующий перенос равен нулю). 6.5.

Далее

в

соответствующий

и

перенос

(то

есть

разряд

ре-

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

После

закончились,

того

как

разряды

остался

завершающий

чисел этап

сложения. Необходимо учесть перенос сложения самых

старших

запишем

его

в

разрядов

старший

операндов. разряд

Просто

результата

(помните пункт 4?). В зависимости от того, каким

образом реализована запись разрядов в байтовой последовательности, всегда

(если

байтовую

перенос

скорость

можно

записи

последовательность

вносить

разрядов

нас

в

полностью

устраивает), а можно сначала проверять есть ли

за исключением переноса. Здесь перенос не

добавляется, а вычитается из разряда первого операнда.

Ну

и

последний

шаг

это

нужно по

данный

число

с

большим

смотри

пункт

3),

то

время

разрядов в самом большом числе. То есть, чем меньше

разрядов

АЛГОРИТМЫ

в

числах,

тем

быстрей

не

больше

первого

это

– 0 (перенос для самых младших разрядов равен

нулю). Вторая операция так 1 – 0 – 1 (перенос из младшего разряда). Вот алгоритм: •

выровняем

оба

числа

относительно

разделителя разрядов. Здесь все аналогично операции сложения. Обратите внимание, мы не

рассматриваем

частный

случай,

когда

второй операнд равен нулю. Можете это сделать сами, однако мой прошлый опыт показывает, что сложение с нулем возникает

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

нулем

(что

еще

реже)

мы

договорились, что второй операнд не может •

быть больше первого; подготовим

результат.

Аналогично

сложе-

нию, однако далее мы не будем добавлять разряд,

на

сложения напрямую зависит от того, сколько

модулю

Текущая же операция будет выглядеть так 13 – 6

поразрядные операции над результатом (первым операндом,

второй

единицы. И следующий перенос будет единица.

количеством

Если учесть что сам процесс сложения – это

что

числа не хватает, то вычитаем из 13, а не из

не

незначащих нулей перед значащими разрядами.

уверенным,

случае 13 – 06. Поскольку в младшем разряде

многочисленных сложений (например, в цикле), получите

всегда

осуществлен всегда. Например, 13 – 6, в нашем

обязателен, однако есть вероятность, что после Вы

быть

собой). Это гарантирует нам, что перенос будет

гашение

пункт

операция

предполагает, что они могут быть равны между

он там есть, то есть когда последний перенос был Вообще-то

сложение,

Пока будем считать, что второй операнд всегда

пункту 7 – гасить нуль можно лишь тогда когда нуля.

и

операнд по модулю всегда не больше первого.

незначащего нуля. Также можно привязать к больше

как

пока рассматриваем беззнаковое вычитание, то

он (то есть перенос больше нуля). 8.

Также

применяется к первому операнду. Поскольку мы

поскольку

природа

вычитания

такова, что не образует новых разрядов в •

результирующем числе; определим

число

разрядов

во

втором

операнде. Нужно для поразрядного вычи-

СОДЕРЖАНИЕ

18


ПРО

АРИФМЕТИКА ДЛИННЫХ ЧИСЕЛ

№12 (март) 2011

граммист

тания, то есть все как в сложении;

Иными

словами,

нам

и для сложения и для вычитания – перенос

станет

известно,

какое

установим перенос в нуль. Это общее правило для

младших

вычитание

разрядов

осуществляется

сложение

от

и

младших

разрядов к старшим) всегда равен нулю;

поразрядное вычитание. Также разобьем на подпункты

для

операции:

осуществим

лучшего

прочтем

вычитание**

понимания

разряды

разряда

сути

чисел,

первого

опе-ранда минус разряда второго операнда и минус перенос, внесем разряд в результат;

погасим незначащие разряды.

провести

число

больше,

то

беззнаковое вычитание производится из него. Знак результата наследуется из первого числа (если

Вы

помните,

то

в

алгоритме

идет

предварительное копирование первого операнда в результат). Вычитание

с

учетом

знаков

операндов

выполняется еще проще. Что есть вычитание? Это

сложение

первого

операнда

со

вторым

операндом. Да, это так. Любое вычитание можно выразить таким образом: а – b = a + (-b), где (-b)

Посткриптум Вот

необходимо

сравнение двух чисел между собой. Как только

есть операция смена знака числа. То есть, нам

собственно

и

все.

На

фоне

сложения

выглядит даже короче, но только потому, что

нужно

поменять

знак

второго

операнда

выполнить сложение с учетом знаков чисел.

и

сложение мы рассматривали более подробно, так

Во всей нашей истории с вычитанием, с учетом

друг на друга, и на сложении мы рассмотрели

история…

как сложение, вычитание и умножение похожи класс подобных операций.

Но речь пока шла в основном о сложении и

знаков

чисел,

осталась

одна

интригующая

Продолжение следует...

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

учета

знака,

мы

можем

реализовать

сложение и вычитание с учетом знака. Рассмотрим

сначала

сложение,

а

потом

вычитание, с учетом знаков чисел. Сравним

знаки чисел, если они совпадают, то это обычное сложение

и

для

него

можно

использовать

сложение без учета знаков чисел. Иное дело, если знаки не совпадают. Тогда нам нужно

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

возможно только в случае, если второй операнд не больше первого операнда. ** Комментарий автора.

Вот здесь нам может не хватить разряда, в таком случае (то есть, когда результат вычитания менее нуля) сложим результат с 10

(чтобы получить один разряд для результата) и установим перенос в единицу (вообще для сложения и вычитания перенос может принимать только значения 1 и 0, но мы на этом не акцентировали внимание, потому, что во многом умножение похоже на данные

операции). В противном случае с разрядом все в порядке и переноса соответственно нет, то есть он принимает значение равное нулю.

АЛГОРИТМЫ

СОДЕРЖАНИЕ

19


ПРО

граммист

ЭФФЕКТ ПЛАВНОГО ЗАТЕМНЕНИЯ

№12 (март) 2011

Многие начинающие программисты достаточно часто хотят подчеркнуть индивидуальность своих программ и поэтому зачастую используют разнообразные графические эффекты (анимация при появлении/сворачивании окон и др.) и скины. Так же иногда убирают рамку у формы, что может привести к тому, что отсутствие пушистой тени от границ окна, которую прорисовывает Windows, плохо скажется на интерфейсе и форма станет «плоской» и попросту сольется с фоном. В этой статье я хочу показать, как можно без каких-либо дополнительных компонентов в среде Delphi сделать затемнение (а можно и осветление, да хоть покраснение!) формы, из которой вызывается другая форма, что будет гарантировать выделение вызываемой формы на фоне той, что ее вызывает. У такого «бескомпонентного» подхода есть очевидные плюсы: во-первых, не придется таскать с собой компонент, чтобы можно было скомпилировать проект на другом компьютере, а во-вторых, если со временем понадобится увеличить функциональность в отдельно взятом проекте (не будет чего-либо хватать), то вы легко сможете это сделать, не устраивая неразберихи с версиями компонентов. Эта статья призвана на примере показать, что можно достаточно легко реализовать такую полезную задумку самому, поэтому в ней все будет максимально просто объяснено и показано и, следовательно, даже начинающий программист не заблудится в коде.

Ян Липлавский

есть разные. Например, можно сделать снимок

by ex.cluz ex.cluz@yandex.ru

области окна (Screenshot), на которое хотим наложить

«туман»,

и,

обработав

его

специальным алгоритмом-фильтром, нарисовать

Введение

поверх затемняемого окна. Метод хорош, но если

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

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

анимация, то она «застынет» в том положении, которое было на момент этого снимка.

интерфейсов, а с другой, тем, что во всемирной

Есть и другой способ. Как вы, скорее всего,

решений, которые можно было бы использовать в

AlphaBlendValue* (меняется оно от 0 до 255),

паутине можно найти не так много готовых указанных целях.

Следовательно, эта статья адресована не какойто определенной группе лиц, а всем, кто хочет

улучшить вид своих программ, независимо от квалификации программиста. Материал

изложен

приводимый

код

просто

снабжен

и

доступно,

весь

комментариями.

А

поскольку автор – педагог, то в статье также рассмотрена

методология

решения

задач

программирования на конкретном примере и пройден полный путь: от постановки задачи и определения решения.

функциональности

до

готового

С чего начать? Для начала хорошо бы прикинуть, каким образом можно сделать то, что мы задумали. Способы

2D ГРАФИКА

знаете,

которое

у

каждой

отвечает

формы

за

есть

то,

свойство

насколько

она

прозрачна. А что если закрасить форму черным

цветом и присвоить AlphaBlendValue значение, например, 120, а потом показать поверх другой? Правильно, затемняемое окно станет примерно в

два раза темнее. Кроме того, анимация (если таковая присутствует) не остановится. Вот в таком

направлении

мы

с

вами

и

будем

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

обесцвечивания

но

рассмотрение прочих)

предметом данной статьи.

не

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

* Комментарий редакции.

Нужна проверка на версию Windows, т.к. AlphaBlendValue доступен, начиная с версий NT и выше...

СОДЕРЖАНИЕ

20


ПРО

ЭФФЕКТ ПЛАВНОГО ЗАТЕМНЕНИЯ

№12 (март) 2011

граммист

Определение функциональности

окно, поэтому ее свойство BorderStyle поставим в

В качестве цели поставим себе максимально

появления «тумана». Работая с ее свойством

простую

реализацию

(имеется

в

виду

по

количеству кода, а не по простоте эффекта) затемнения или засветления формы различными цветами

с

меняющимся

появления/исчезновения.

временем

плавного

Кроме того, для нашего удобства сделаем так, чтобы

для

получения

использовалась

желаемого

эффекта

одна-единственная

процедура.

Таким образом, цели сформулированы, осталось

определить необходимую нам функциональность. Чтобы

не

усложнять

ограничиться

пример,

следующими

я

предлагаю

параметрами,

которые будут передаваться процедуре: • • • •

bsDialog. Форма Form_Fog является основой для AlphaBlendValue,

мы

и

будем

получать

затемнение на форме Form_Main. Теперь

кинем

компонент

Timer

на

форму

Form_Fog и назовем его Timer_FogClose (зачем

он нам нужен, объясню позже). Также для пущей зрелищности поставим свойство WindowsState

формы Form_Main в wsMaximized, а свойство Position формы Form_Dialog в poScreenCenter. Вот и

вся

предварительная

подготовка,

теперь

сохранимся (Unit1 сохраните как Main, Unit2 как Fog, Unit3 как Dialog, а проект как FogTest), и можно приступить непосредственно к написанию кода.

Кодинг

какую форму надо затемнить; какую форму надо показать;

Договоримся

или нет;

относится, и будем писать процедуру появления

показать вызываемую форму как модальную какой должен быть цвет у «тумана»; насколько

сильно

процентах);

затемнять

форму

сколько времени отводится на затемнение формы и снятие этого «тумана».

Итак,

все,

перед

что

началом

определено, реализацию

в

не

долгий

Разработка

отладки Для

разработки

поэтому

программировать. Практика.

необходимо

работы

разработки

нам

Delphi

было

нашей

будем

ПО

и

и

понадобится

cadero.com/products/delphi.

задумки,

откладывать

ящик

7...2010

определить начнем

средства IDE

среда

http://www.embar-

главный

сразу,

модуль

что

кодом,

не

будем

который

к

засорять

нему

не

«тумана» в модуле формы Form_Fog. Это нам

позволит без особых проблем «привить» данный эффект

другому

использовать

наши

определимся

проекту,

с

чтобы

наработки.

Для

типами

в

нем

начала

переменных,

передающихся в процедуру «затемнения». Я бы предложил описать ее следующим образом: procedure FogShow(FormToFog, FormToShow: TForm; Modal: boolean; FogColor: TColor; FogPercent: byte; TimeToFog: word);

Из описания видно, что мы будем передавать в нее те параметры, о которых говорили выше в

пункте «Определение функциональности». Код

этот напишем в разделе public формы Form_Fog. Это

будет

разделе.

единственная

процедура

в

этом

Подготовка

Кто-то может подумать и спросить: «Если есть

Для начала создадим новый проект (VCL Forms

процедура

Application) и сразу же еще две формы (Form). Form1

обзовем

Form_Fog,

а

как

Form3

Form_Main,

как

Form2

Form_Dialog.

как

Первая

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

символизировать

2D ГРАФИКА

какое-либо

диалоговое

процедура

отдельную

«туман»

появления

его

процедуру

нужен

диалоговое

«тумана»,

исчезновения?» только

окно?

ориентироваться.

И

для

тогда,

На

для

А

то

где

зачем

закрытия, когда

него

и

этого-то

же

нам

если

открыто будем

нам

и

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

СОДЕРЖАНИЕ

21


ПРО

ЭФФЕКТ ПЛАВНОГО ЗАТЕМНЕНИЯ

№12 (март) 2011

граммист

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

описанию

нашей

процедуры

и

нажмем

Ctrl+Alt+C для того, чтобы среда сгенерировала нам

заготовку

этой

процедуры.

Для

ее

реализации потребуются некоторые константы и переменные, которые мы объявим в разделе private:

написание

нашей

главной

и

самой

процедуры. Она должна выглядеть так:

длинной

procedure TForm_Fog.FogShow(FormToFog, FormToShow: TForm; Modal: boolean; FogColor: TColor; FogPercent: byte; TimeToFog: word); begin // прозрачность уст-м в ноль (форма полностью прозрачна) AlphaBlendValue:=0;

const

Color:=FogColor;

// задаем цвет формы ("тумана")

// время между шагами возникновения/исчезновения "тумана"

FPercent:=FogPercent;

SLEEP_TIME=20;

// задаем время для возникновения/исчезновения "тумана" в мc

// интервал проверки на закрытие формы

FTime:=TimeToFog;

TIMER_INTERVAL=20;

// будем ориентироваться на эту форму для закрытия Form_Fog

// если у затемняемой формы BorderStyle:=bsNone

FormForIndication:=FormToShow;

DEFAULT_BORDERS_WIDTH=6;

if FormToFog.BorderStyle=bsNone then begin

var

// устанавливаем процент затемнения

// устанавливаем положение формы-тумана и ее размеры, если у

// форма-индикатор, по закрытии которой можно судить о том,

// затемняемой формы

// что "туман" не нужен и можно закрыть форму Form_Fog

BorderStyle:=bsNone

FormForIndication: TForm;

Top:=FormToFog.Top+DEFAULT_BORDERS_WIDTH;

// процент непрозрачности "тумана"

Left:=FormToFog.Left+DEFAULT_BORDERS_WIDTH;

FPercent: byte;

Height:=FormToFog.ClientHeight-DEFAULT_BORDERS_WIDTH*2;

// время для возникновения/исчезновения "тумана" в мc

Width:=FormToFog.ClientWidth-DEFAULT_BORDERS_WIDTH*2;

FTime: word;

end

В комментариях к коду указано, зачем нужна та

else begin // устанавливаем положение формы-тумана и ее размеры, если у

или иная константа или переменная. Теперь нужно

создать

событие

OnCreate

у

Form_Fog и записать туда следующий код:

// затемняемой формы

формы

BorderStyle<>bsNone Top:=FormToFog.Top+(FormToFog.Height-FormToFog.ClientHeight((FormToFog.Width-FormToFog.ClientWidth) div 2));

procedure TForm_Fog.FormCreate(Sender: TObject);

Left:=FormToFog.Left+((FormToFog.Width-

begin

FormToFog.ClientWidth) div 2);

// по умолчанию затемнение стоит на 40%

Height:=FormToFog.ClientHeight;

FPercent:=40;

Width:=FormToFog.ClientWidth;

// по умолчанию "туман" появляется за 240 мс

end;

FTime:=240;

if FormToFog.FormStyle = fsStayOnTop then begin

// форма должна быть прозрачной

// учитываем, что затемняемая форма может быть расположена

AlphaBlend:=true;

// поверх всех окон

// чтобы границы "тумана" были не видны

FormStyle:=fsStayOnTop;

BorderStyle:=bsNone;

FormToShow.FormStyle:=fsStayOnTop;

// цвет "тумана" по умолчанию (черный)

end else begin

Color:=$00000000;

FormStyle:=fsNormal;

// выключаем таймер

FormToShow.FormStyle:=fsNormal;

Timer_FogClose.Enabled:=false;

end;

// задаем интервал работы таймера

// запускаем таймер для отлова закрытия диалогового окна

Timer_FogClose.Interval:=TIMER_INTERVAL;

Timer_FogClose.Enabled:=true;

end;

Show;

Тут, в основном, идет установка параметров по умолчанию.

комментарии.

В

2D ГРАФИКА

коде

есть

Следующим

необходимые

шагом

будет

// запускаем показ «тумана»

// в зависимости от параметра Modal, либо просто // показываем диалоговое окно, либо делаем его модальным if Modal=true then FormToShow.ShowModal else FormToShow.Show; end;

СОДЕРЖАНИЕ

22 3


ПРО

ЭФФЕКТ ПЛАВНОГО ЗАТЕМНЕНИЯ

№12 (март) 2011

граммист

Этот код снабжен подробными комментариями,

успешно) идет расчет прозрачности «тумана».

нуждается. В нем присутствует вызов процедуры

возможных значений прозрачности в проценты.

поэтому в более детальном рассмотрении не

показа «тумана» (Show) – это как раз и есть его плавное появление. Реализуется оно в событии OnPaint. Так что создаем его и пишем простой код:

Коэффициент 2.55 берется при пересчете 255

Точно таким же образом создаем обработчик события OnClose формы Form_Fog:

procedure TForm_Fog.FormClose(Sender: TObject;

procedure TForm_Fog.FormPaint(Sender: TObject);

var Action: TCloseAction);

var

var

i: byte;

i: byte;

j: word;

j: word;

begin

begin

if FTime<SLEEP_TIME then

if FTime<SLEEP_TIME then Exit;

begin

j:=Round(FTime/SLEEP_TIME);

AlphaBlendValue:=Round(FPercent*2.55);

// количество шагов

for i:=j downto 0 do

Exit;

begin

end;

AlphaBlendValue:=i*Round(FPercent/FTime*SLEEP_TIME*2.55);

j:=Round(FTime/SLEEP_TIME);

// количество шагов

Sleep(SLEEP_TIME);

for i:=0 to j do

Application.ProcessMessages;

begin // защита от "перевыполнения"

end;

if (i*Round(FPercent/FTime*SLEEP_TIME*2.55))>254 then

end;

begin

Тут три отличия от обработчика OnPaint: во-

AlphaBlendValue:=255;

первых, в начальной проверке не присваивается

Exit;

прозрачность

end;

«тумана»,

потому

как

можно

просто закрыть форму, а при ее следующем

AlphaBlendValue:=i*Round(FPercent/FTime*SLEEP_TIME*2.55);

появлении прозрачность установится в ноль в

Sleep(SLEEP_TIME);

самом начале процедуры FogShow, во-вторых,

// чтобы программа не зависала

цикл будет выполняться не с возрастающим

Application.ProcessMessages;

значением i, а с уменьшающимся до нуля, и в-

end;

третьих, нет защиты от «перевыполнения».

end;

Перед началом цикла проверяется, а стоит ли

Итак,

сравнением

(диалогового окна). Каким образом? Помните, в

вообще

его

запускать.

времени,

Это

отведенного

реализовано на

эффект

(FTime), с временем длительности одного шага, которая задана константой (SLEEP_TIME). Если времени отведено меньше, то сразу показываем

«туман»

и

выходим,

не

прогоняя

цикл,

что

экономит время. Это также является защитой от возникновения

ошибок

в

случае,

если

переменной FTime присвоено нулевое значение. Также перед циклом вычисляется количество

шагов затемнения в зависимости от времени, указанного константы,

нами

при

отвечающей

вызове за

процедуры,

пропуск

значение

255

(что

может

случиться

при

округлении), а затем (если проверка проходит

2D ГРАФИКА

готово!

Осталось

модальной

только

формы

Он нам нужен как раз для этих целей. Создаем обработчик

события

OnTimer

таймера

Timer_FogClose и приводим его в такой вид:

procedure TForm_Fog.Timer_FogCloseTimer(Sender: TObject); begin if FormForIndication.Visible=false then begin Timer_FogClose.Enabled:=false;

времени

происходит проверка, не больше ли получаемое

все

закрытие

конце процедуры FogShow мы запустили таймер?

и

между этапами затемнения. В цикле сначала

почти

отловить

Close; end; end;

Тут мы проверяем, видно ли еще диалоговое

СОДЕРЖАНИЕ

23


ПРО

граммист

ЭФФЕКТ ПЛАВНОГО ЗАТЕМНЕНИЯ

№12 (март) 2011

окно. Если нет – выключаем таймер и запускаем закрытие формы с «туманом». Теперь

осталось

работоспособность

того,

лишь

что

мы

проверить

так

упорно

создавали. Для этого подключаем в модуле Main обе формы:

осветления, это смотря какой цвет передать в

процедуру) формы при показе модального окна. Поиграйте создадите

параметрами,

что-то

кидаем

кнопку

на

форму

Form_Main,

что-то

при

напоминающее

становится

эффект

как

на

сепии

старых

Form_Dialog, true,

Или эффекта «сумерек»:

Form_Dialog, true, clBlack,

Form_Fog.FogShow(Form_Main, Form_Dialog, true, clNavy, 30, 500);

50, 500);

Теперь можно запустить проект и полюбоваться выполненной работой! Заключение

2D ГРАФИКА

Например,

вы

$0000514F, 60, 500);

Form_Fog.FogShow(Form_Main,

прозрачностью

необычное.

возможно,

Form_Fog.FogShow(Form_Main,

туда следующее:

помощи

и,

вызове процедуры следующим образом создается фотографиях):

создаем для нее обработчик OnClick и пишем

При

с

(изображение

uses Fog, Dialog;

Затем

зрелищного эффекта: плавного затемнения (или

Экспериментируйте

и

не

забывайте,

что

некоторые эффекты можно создать проще, чем кажется на первый взгляд!

Все настолько просто, что литература не нужна. нехитрых

формы

мы

манипуляций добились

с

очень

Исходники

тестового

проекта

редактора

рассматриваемого эффекта приложены в виде ресурсов непосредственно в архиве с журналом.

СОДЕРЖАНИЕ

24


ПРО

НЕМНОГО О DSP. ЧАСТЬ 2

№12 (март) 2011

граммист

Доброго времени суток, уважаемые читатели журнала «ПРОграммист». На этот раз здесь будет много скучной математики, каких-то непонятных формул и рассуждений. Но, в итоге будет представлена программка, производящая обработку звука на лету. В общем, стоит прочитать и разобраться...

Мират Каденов

(раскрывая

by mirat http://mathdev.org/mirat

скобки

и

группируя

подобные).

Кстати, если умножить i на i, получится -1: (0, 1) * (0, 1) = (-1, 0). Можно проверить, что операции сложения

Комплексные числа

и

умножения

при

представлении остаются справедливыми:

таком

В школе нам говорили, что если дискриминант D квадратного уравнения меньше нуля, то оно не

имеет решений. В принципе верно, но только отчасти. чисел.

Решений

Но

комплексных

они

нет

есть

чисел.

среди

(их

вещественных

даже

Между

два)

среди

прочим,

= { вспоминая, что i*i = -1 } =

комплексные числа как раз и придумали для

Иногда удобно представлять комплексные числа

имело решения.

координатами.

того, чтобы любое алгебраическое уравнение Тут будет немного математики. Комплексное

число определяется как упорядоченная пара вида (a,b), где a,b – вещественные, a – называется

вещественной частью, b – мнимой. Комплексные числа

можно

складывать

следующим правилам:

и

умножать

по

как векторы на плоскости с соответствующими естественным

Тогда

образом

операция

сведется

к

сложения

сложению

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

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

чисел является то, что если второе число в паре

равно 0, то числа вида (a, 0) складываются и умножаются (предлагаю

как

вещественные

это

обычные

проверить).

числа

вещественные

Поэтому

можно

все

считать

комплексными с нулевой мнимой частью: a = (a, 0).

Но

с

какими-то

абстрактными

парами

оперировать неудобно, да еще надо помнить правило

умножения.

Оказывается,

что

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

виде: (a,b) = a + i*b, где i – это специальное число, называемое мнимой единицей, i = (0, 1). В

таком

виде

можно

легко

оперировать

комплексными числами, используя сложение и умножение так, как мы привыкли в алгебре

ЛАБОРАТОРИЯ

измерить и получить мнимое значение. Все физические величины, с которыми мы имеем

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

процессов в физике, например колебаний, и вот почему.

Для комплексных чисел существует еще одно

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

словами,

вектор

на

плоскости

СОДЕРЖАНИЕ

25


ПРО

НЕМНОГО О DSP. ЧАСТЬ 2

№12 (март) 2011

граммист

(комплексное число) можно представить как два числа t,

r – азимут и расстояние до точки. t –

называется

аргументом,

комплексного

числа.

тригонометрическом

а

r

Теперь

модулем

перепишем

представлении

умножения. Пусть даны два числа:

в

операцию

Где:

– начальная фаза,

– амплитуда.

– угловая скорость,

Рассмотрим комплексное число: Где

.

Если взять вещественную часть от , то как раз

получим наше положение маятника . Чем же это удобно? А тем, что при такой записи: описание

Тогда:

колебаний

простому

умножению

маятника

сводится

одного

комплексного

числа

другое ( = {применяя формулы приведения для синуса суммы и косинуса суммы } = Другими

словами,

при

к

(

).

)

на

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

умножении

двух

комплексных чисел модули перемножаются, а

Преобразование Фурье [1]

аргументы складываются.

Как известно, современный цифровой компьютер

Причем, если оба комплексных числа имели

компьютер с дискретными величинами. Вот на

модуль

поворот

1,

то

перемножение

единичного

вектора

их

есть

(первого

просто числа)

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

ролями. А вещественные числа – это векторы, лежащие на оси Ox. Еще

одна

вещь,

которая

нам

Формула_Эйлера: этой

формулы

получаем,

комплексное число:

устройство

сугубо

дискретное,

и

работает

входе мы имеем, к примеру, дискретный набор значений

– замеров величины какого-то сиг-

нала в равные промежутки времени: буфер из N семплов. Тогда с помощью преобразования Фурье

можно представить этот сигнал в виде суммы N комплексных чисел

:

понадобится

формула Эйлера http://ru.wikipedia.org/wiki/

Из

что

Эти чиcла любое

находятся по явным формулам:

А в чем смысл? Давайте рассматривать индекс n как отсчет времени. Тогда сигнала

можно представить как:

в

момент

времени

– это значение n.

Рассмотрим

величину сигнала в начальный момент времени n = 0:

Отсюда делаем вывод, что любое комплексное число

это

обычное

положительное

вещественное число r, повернутое на угол t умножением на

.

И вот пример: вспомним из школьной физики, что положение маятника можно описать такой формулой:

ЛАБОРАТОРИЯ

А теперь в момент времени n = 1:

Что изменилось? Если присмотреться, можно заметить,

что

комплесных чисел

представлял .

собой

сумму

СОДЕРЖАНИЕ

26


ПРО

А

НЕМНОГО О DSP. ЧАСТЬ 2

№12 (март) 2011

граммист

– это сумма тех же комплексных чисел

но домноженных на

,

, то есть повернутых

каждое на свой некоторый угол

.

мгновенным спектром сигнала. Собственно, делаем

Если продолжать отсчитывать время, то заметим,

Для преобразования Фурье, а точнее быстрого

своей

я

числа

вращаются, причем каждое со

частотой,

зависящей

от

номера

k.

И

значения сигнала в точности равны сумме этих повернутых

. Отсюда следует самый главный

вывод: каждое из чисел

отвечает какому-то

простому колебанию определенной частоты, а сигнал

в

целом

является

суммой

этих

колебаний и это выполняется в каждый момент времени n от 0 до N – 1.

То есть, мы представили наш сигнал на отрезке времени

от

0

до

N-1

как

сумму

простых

преобразования Фурье, работающего за N log N, буду

пользоваться

предыдущей

заметке

библиотекой была

FFTW.

В

представлена

программа, которая считывает звук порциями с устройства записи и сразу пишет в выход. Теперь расширим

ее.

Полный

пример

приложен

к

статье. В ней добавился код для инициализации

библиотеки FFTW и метод process() наконец обрел вместилище: void process() {

колебаний разных частот. В этом и заключается

double inv_n = 1.0 / (double) play_buf_size;

вся суть обработки звука: разложив сигнал в

набор колебаний, мы можем оперировать над

// конвертируем буфер из ALshort в double, делаем FFT и

гармониками) в отдельности. А затем по тем же

for (unsigned int i = 0; i < play_buf_size; ++i)

каждым из этих колебаний (они называются

// нормализуем результат

самым формулам можно собрать сигнал, который

dbuf[i] = (double)play_buf[i] /

уже можно выводить в звуковое устройство. Ну и еще немножко об этих числах

(double)((ALushort)(-1)) * 2.0;

. Если

fftw_execute(p_forward);

вспомнить пример с маятником выше, то как раз и получится, что каждое число примера. То есть: •

аргумент

модуль

задает

начальную

ветствующего колебания; число

– это

фазу

for (unsigned int i = 0; i < play_buf_size; ++i)

из

{ cbuf[i][0] *= inv_n; cbuf[i][1] *= inv_n;

соот-

– амплитуду гармоники;

отвечает гармонике с частотой

} // ну, поехали

,

где F – частота дискретизации.

transform(); // делаем обратный FFT и возвращаем обработанный сигнал

Практически всегда обработка звука затрагивает

// в буфер

только амплитуды гармоник, а фазы остаются неизменными.

Еще

надо

заметить,

fftw_execute(p_backward);

что

for (unsigned int i = 0; i < play_buf_size; ++i)

преобразование Фурье обычно применяется к

{

короткому отрезку сигнала, например 512...4096

double c = dbuf[i];

семплов. То есть входной сигнал каждые N семплов

подвергается

if ( c > 1.0 ) c = 1.0;

преобразованию,

if ( c < -1.0 ) c = -1.0;

обработке, затем обратному преобразованию. И так происходит с каждыми N семплами. Причем на каждом таком отрезке

будут, естественно,

разными*. Кстати, набор чисел

– называется

play_buf[i] = (c * (double)((ALushort)(-1)))/2; } }

* Комментарий автора.

FFTW принимает буфер только из double'ов,

http://ru.wikipedia.org/wiki/Дискретное_преобразование_Фурье.

преобразовать в double, причем значения в этом

Если кто хочет разобраться глубже, отсылаю читателя к википедии

ЛАБОРАТОРИЯ

поэтому

приходится

наш

буфер

play_buf

СОДЕРЖАНИЕ

27


ПРО

НЕМНОГО О DSP. ЧАСТЬ 2

№12 (март) 2011

граммист

массиве должны быть в пределах от -1.0 до 1.0 (в

гармоника с частотой phi, то в преобразованном

После выполнения fftw_execute() в массиве cbuf

частотой pi / 2 с той же амплитудой. То есть все

play_buf они лежат в пределах -32768...+ 32768). будут лежать комплексные числа

, cbuf[i][0] –

это вещественная часть i-го числа, cbuf[i][1] – мнимая. Я делю результат преобразования на размер буфера, так как FFTW масштабирует выход в n раз. Поэтому модули всех

будут в

пределах 0.0 - 1.0. Итак,

когда

сделаны,

все

необходимые

вызываем

trasform().

сигнале она будет соотвествовать гармонике с частоты в сигнале будут понижены в два раза. Это приводит к снижению тона сигнала на

октаву. Обратного эффекта можно добиться с помощью растяжения спектра в два раза: void transform() {

приготовления Эта

функция

ниже).

Затем

for (unsigned int i = play_buf_size; i > 1; --i) {

каким-либо образом меняет амплитуды гармоник (примеры

рассмотрим

cbuf[i][0] = cbuf[i/2][0]; cbuf[i][1] = cbuf[i/2][1];

производится обратное преобразование Фурье,

}

результат которого – это уже измененный сигнал в dbuf. Но его значения имеют тип double и

лежат в пределах -1.0...+1.0, а если не лежат, то по ним надо ограничить. Поэтому необходимо снова

нормализовать

их

в

пределы

-32768...+32767. В таком виде они и отправятся в аудиоустройство.

}

В итоге, тон сигнала будет повышен на октаву. А вот старый телефон: void transform() { for (unsigned int i = 0; i < play_buf_size; ++i)

transform()

{

Вот здесь и происходит самое интересное. От

if ( ((float)i / (float)play_buf_size * 44100.0f) > 3000) {

того, каким образом мы преобразуем спектр cbuf,

cbuf[i][0] = 0.0;

будет зависеть какой эффект мы получим:

cbuf[i][1] = 0.0; }

void transform()

}

{

}

for (unsigned int i = 0; i < play_buf_size; ++i) { if (i > play_buf_size / 2) { cbuf[i][0] = 0.0; cbuf[i][1] = 0.0; } else

Суть его проста – обрезаем все частоты выше

трех (3) килогерц. Получаем что-то похожее по звучанию на аналоговую телефонную сеть. Фильтр низких частот void transform()

{

{ cbuf[i][0] = cbuf[i*2][0];

for (unsigned int i = 0; i < play_buf_size; ++i)

cbuf[i][1] = cbuf[i*2][1];

{

}

if ( ((float)i /(float)play_buf_size * 44100.0f) < 3000)

}

{

}

cbuf[i][0] = 0.0; cbuf[i][1] = 0.0;

Мы просто «сжимаем» спектр в два раза, а верхнюю половину его обнуляем. Как видим, обе

части комплексного числа надо обрабатывать

вместе, чтобы не изменилась фаза гармоники. Если

в

исходном

ЛАБОРАТОРИЯ

сигнале

присутствовала

} } }

Вырезает все частоты ниже трех килогерц, что

СОДЕРЖАНИЕ

28


ПРО

граммист

глушит

НЕМНОГО О DSP. ЧАСТЬ 2

№12 (март) 2011

практически

весь

диапазон

звуков,

наиболее хорошо слышимых (и воспроизводимых) человеком.

Исходники тестового проекта, рассмотренного в данной

статье,

приложены

в

виде

непосредственно в архиве с журналом.

ресурсов

Заключение

Ресурсы

Если пропустить музыку через такой фильтр, то

Ресурс Википедии http://ru.wikipedia.org/wiki/

ударной

Ресурс Википедии http://ru.wikipedia.org/wiki/

хорошо будут слышны разве что тарелки на установке,

потому

что

они

наиболее широкий шум по всему спектру.

издают

Ну вот, собственно и все. Это самое простое, что можно Фурье.

сделать

ЛАБОРАТОРИЯ

с

помощью

преобразования

• •

Дискретное_преобразование_Фурье Формула_Эйлера

Исходники проекта http://mathdev.org/sites/ default/files/dsp2.cpp_.zip

Персональный блог автора http://mathdev.org/ mirat

СОДЕРЖАНИЕ

29


ПРО

граммист

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5

№12 (март) 2011

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

Виталий Белик

// Выбирает из стека в EDX. Соответственно восстанавливать после

by Stilet http://www.programmersforum.ru

// операций целочисленного деления function PopEDX;

begin Result:=#$5A

end;

Shards of mirror

// Обнулятор для EDX. Опять-таки, пригодится при операциях

Я разделил опкоды на группы, чтобы проще было

// на какой-то мусор, ведь при умножении 32

понять,

с

чем

они

работают.

Операции

с

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

// деления умножения, чтоб не дай бог процессор не умножил число // битных чисел один из множителей находится в EDX:EAX. function XOR_EDX_EDX;

begin Result:=#$33#$D2

end;

// Обменивает регистры EAX EDX содержимым. После того как прошла // операция деления. В ЕАХ остается частное, а в EDX остаток. И

// Сервисные опкоды *******************************************

// если нам нужен остаток, то либо проводить

// Поднимает планку стека. Фактически избавляет стек от мусора,

// выбор из EDX, либо поменять их местами. Второе проще, если

//«затирая» ненужные данные. «Затирая» -

// принять во внимание что все операции будем проводить только в

слово не совсем

// удачное, ибо на самом деле ничего не затирается.

// ЕАХ. В общем эта операция пригодится. function XCHG_EAX_EDX; begin Result:=#$92

end;

// Просто указатель стека перемещается так, что эти, уже // ненужные, данные оказываются

// Помещает в регистр ЕАХ то, что сохранено в вершине стека, не

// вне игры. Они потом наверняка затрутся другими данными

// смещая при этом верхушку стека. Если нужно оставить вершину

Function AddESP (i:byte):String;

// на своем месте, но позарез выбрать из

begin

Result:=#$83#$C4+chr(i);

end;

// него значение

- эта операция пригодится.

function MOV_EAX_ESP;

begin Result:=#$8B#$04#$24

end;

// Помещает значение регистра ЕАХ в стек. Будет использоваться // если в выражении появится подвыражение, и уже рассчитанное

// Передает в переменную значение другой переменной. Поскольку

// целое число необходимо будет сохранить

// мы договорились, что наши переменные будут вариантны нужно

// Так же может применяться при приведении целого типа к

// пересылать все 10 байт, чем и занимается инструкция

// вещественному. В этом случае рассчитанный результат

// MOVS BYTE. Остается ей передать адреса источника и получателя

// переносится в стек, а уж из него выбирается в сопроцессор.

// и дело в шляпе. Это пригодится при присваивании одной

function PushEAX;

// переменной другой, поскольку данные просто копируются

begin Result:=#$50

end;

REP

function XCHG_MEM_MEM2; // Выбирает из стека в регистр ЕАХ значение на его вершине.

begin

// Может использоваться после расчета. Честно говоря, AddESP

Result:=

// больше будет использоваться, но и эта команда может

Result:=Result+#$BE+mFrom.Addr;

// MOV

ESI, mFrom

// вполне пригодится. Она весит всего один байт в отличии от

Result:=Result+#$BF+mTo.Addr;

// MOV

ESI, mTo

// трех байтов AddESP, так что, если позволит случай, присобачим

// REP MOVS BYTE PTR ES:[EDI], BYTE PTR DS:[ESI]

// ее в нужное место

Result:=Result+#$F3;

function PopEAX;

begin Result:=#$58

end;

// Помещает в стек значение регистра EDX. Пригодится при // операции деления и умножения. Помните операции DIV и MOD // работают и с EDX регистром в случае с 32 // битными числами. Может быть,

его понадобится сохранить.

// На всякий случай. function PushEDX;

ЛАБОРАТОРИЯ

begin Result:=#$52

end;

#$B9+DWordToStr(10); // MOV

ECX, 0A

end;

На всякий случай поясню, что #$ - говорит о том, что это символ с указанным кодом. Откуда я их взял? Да очень просто: написав команду в ассемблере, подсмотрел ее опкод в отладчике.

Вот, например опкод для AddESP (см. рисунок 1):

СОДЕРЖАНИЕ

30


ПРО

граммист

№12 (март) 2011

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5 // может пригодится в будущем function PopMem;

Рис. 1. Опкод Где: 04 – операнд машинной команды 83C4h, указывающий,

вершину стека.

на

сколько

байт

сместить*

begin Result:=#$8F#05+m.Addr

// Передача из переменной целого 32-битного числа в регистр ЕАХ // Его можно использовать в начале выражения, когда первый // операнд переменная, и ее нужно внести в регистр для начала // операции function MovEAXMem;

Идем дальше. Рассмотрим операции с ЦП:

end;

begin Result:=#$A1+m.Addr

end;

// Обратное действие - из регистра целое переносится в переменку

// CPU опкоды ************************************************

// Применять после выражения, когда нужно присвоить вычисленное

// Разница между значением в вершине стека и ЕАХ. Применяется

// переменной

// при операции вычитания, после отработки вложенного выражения,

function MovMemEAX;

begin Result:=#$A3+m.Addr

end;

// которое свой результат помещает в ЕАХ, а результат до начала // выражения должен быть помещен в стек. Именно их разность и

// Особые случаи, когда переменную инициализируют числом. При

// будет вычислять данная комбинация

// хорошем оптимизаторе можно проверять, обрабатывалась ли эта

function Sub_ESP_EAX;

begin Result:=#$29#$04#$24

end;

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

// Та же ерунда, но для операций сложения. Суммирует результат

// данных. Но если переменная уже участвовала в присвоении,

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

// придется этот опкод применить. В нашем случае оптимизатора

// подвыражения, сохраненный в стеке

// нет, посему применять его будем везде где не лень.

function ADD_EAX_ESP;

begin Result:=#$03#$04#$24

end;

function MovMemConst;

begin

Result:=#$C7#$05+m.Addr+DWordToStr(i);

end;

// То же для операции умножения. Напоминаю, что эти действия // проводятся над 32 битными целыми. function MUL_EAX_ESP;

begin Result:=#$F7#$24#$24

// Используется для вычитания операнда-переменной из значения в end;

// ЕАХ, просчитанного в ходе обработки выражения. Эта операция // не должна применяться к первому операнду. Только к

// Помещает в ЕАХ число. Будет использоваться, если в выражении // встретится целочисленная константа. В основном если она будет // первой в выражении function MovEAXConst;

// последующим, а первый должен помещаться в регистр вычисления // как есть. function SubEAXMem;

begin Result:=#$B8+DWordToStr(i)

end;

begin

Result:=#$2B#$05+m.Addr;

end;

// Вычитает из ЕАХ целую константу. Этот оператор не будет

// Тоже самое касается и сложения. Что бы не подхватить мусор

// работать с первыми операндами. Только со следующими, чтоб не

// при вычислении, первый операнд должен помещаться в регистр, а

// получить неправильный результат.

// уж к последующим применять этот опкод.

function SubEAXConst;

begin Result:=#$2D+DWordToStr(i)

end;

function ADDEAXMem;

begin

Result:=#$03#$05+m.Addr;

end;

// Оператор суммирования ЕАХ и целой константы function ADDEAXConst;

begin Result:=#$05+DWordToStr(i)

end;

// Эта функция проводит опкод умножения переменной на значение в // регистре - значение уже рассчитанное в результате вычисления

// Заносит в стек целую константу. Например, может пригодится

// выражения. Не забыть при этом что для 32-битные числа при

// для передачи целого сопроцессору через стек

// умножении и делении используют регистр EDX, потому его нужно

function PushConst;

begin Result:=#$68+dwordToStr(i)

end;

// обнулять, при необходимости сохраняя в стек function MUL_EAX_Mem;

// Заносит в стек целое из переменной. Стоит проследить за тем,

begin

Result:=#$F7#$25+m.Addr;

end;

// чтобы эта операция использовалась только в том случае если // тип переменной целочисленный. Опять-таки можно использовать

// Инкремент. Аналог сишного ++, но для регистра. Инкременты и

// для передачи сопроцессору значения из переменной

// декременты наш язык унаследует от Си - очень уж там это

function PushMem;

begin Result:=#$FF#$35+m.Addr

end;

// удобно сделано. Кстати, забегая наперед, скажу, что и // самоприсваиваемые операторы типа += мы позаимствуем.

// Выборка из вершины стека целого числа в переменную. Тоже

ЛАБОРАТОРИЯ

function _inc;

begin

СОДЕРЖАНИЕ

31


ПРО

граммист

№12 (март) 2011

Result:=#$40;

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5 end;

function DIV_EAX_ESP; begin

// Инкремент для переменной целого типа. function incMem;

Result:=PushEAX+XOR_EDX_EDX+DIV_EAX_ESP_(4);

begin

Result:=#$FF#$05+m.Addr;

if not ItNotMod then Result:=Result+XCHG_EAX_EDX; end;

Result:=Result+AddESP(4); {внимание EDX не бережется. Осторожнее в будущем}

// Декременты. Действуют по тем же правилам. function _dec;

end;

begin

Result:=#$48;

end;

// Их вариация. Работает с переменной. Тоже скомбинированна, для // того, чтоб в классах обработчиках писать поменьше. Принцип

function decMem;

begin

Result:=#$FF#$0D+m.Addr;

// деления тот же. end;

function DIV_EAX_Mem; begin

// Операция деления значения из ЕАХ, того которое рассчитывается

Result:=PushMem(m)+XOR_EDX_EDX+DIV_EAX_ESP_(4);

// при вычислении выражения, на значение, сохраненное по каким

if not ItNotMod then Result:=Result+XCHG_EAX_EDX;

// то соображениям в стеке. Предполагается применяться при // операции деления, после расчета вложенного выражения. В стек // помещается уже рассчитанное целое, рассчитывается вложенное // выражение, после чего сохраненное делится на результат // подвыражения. Грубо говоря, если есть 2*8/(5+3) то 16 пойдет // в стек, а после вычисления число 8 будет поделено на 16. // Здесь нужно будет указать какой элемент стека рассчитывать, // если это верхушка, то 0 если он лежит чуть глубже (вдруг в // стек еще пришлось что-то сохранить) то указать номер позиции, // который должен быть кратен 4-м. function DIV_EAX_ESP_;

begin

Result:=#$F7#$74#$24; if(i>0) then Result:=Result+chr(i); end; // Достаточно непростая процедура деления. Делится целое число // на значение в ЕАХ. Поскольку операция деления затрагивает // регистр EDX, приходится обнулять его, сохраняя если // необходимо значение регистра в стеке программы, вдруг // понадобится. Здесь же учитывается, что нужно получить на // выходе - остаток от деления или частное. Если остаток - после // деления необходимо добавить операцию смены содержимого ЕАХ и // EDX, поскольку остаток помещается в EDX, а работаем то мы с // ЕАХ, ввиду отсутствия какой-либо оптимизации. Был бы // оптимизатор, он бы не менял значения, а просто переключал на // выборку из EDX.

В общем это дело будущего - избавиться от

// "ничего" не весящей инструкции обмена. function DIV_EAX_CONST; begin Result:=PushEDX+PushConst(i)+XOR_EDX_EDX+DIV_EAX_ESP; if not ItNotMod then Result:=Result+XCHG_EAX_EDX; Result:=Result+PopEDX+AddESP(4); end; // Тоже скомбинированная функция, чтоб в классах много не // писать. Делит ЕАХ и значение, сохраненное в стеке. Работает с // Целыми числами

ЛАБОРАТОРИЯ

Result:=Result+AddESP(4); end;

Это только для начала. Особо много их не

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

Теперь давайте посмотрим, как обстоит дело с

функционалом для расчета вещественных чисел. Все знают как работает сопроцессор? В нем есть

регистры ST0-7. Семь штук всего. Они работают

скорее как стек, чем как обычные регистры ЦП. Когда из памяти число передается сопроцессору, значения,

уже

содержащиеся

в

регистрах

сдвигаются, так что верхушка этого стека – регистр ST0 становится свободным, его значение

перемещается в ST1, смещая его значение в ST2

и так далее. Значение из ST7, если не ошибаюсь, выпадает из сопроцессора в никуда. Давайте

посмотрим, как это выглядит в натуре. Если

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

посмотреть,

как

сопроцессор

«питается» (см. рисунок 3). Число 45.256 ушло в * Комментарий автора.

Вообще, кстати, это смещение не обязательно должно быть кратно размеру элемента стека. В нашем случае кратно 32-битам или 4-м

байтам. Никто не помешает написать там другое число, указатель

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

сторона – никто не мешает нам освобождать элементы стека программы,

в

обратном

порядке

соблюдая

лишь

размерность

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

СОДЕРЖАНИЕ

32


ПРО

граммист

№12 (март) 2011

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5 вершину стека сопроцессора. Если продолжить

трассировку, то это число сместится по стеку ниже. Или выше? Какая разница – сместится в следующий регистр и все тут (см. рисунок 4).

Видите: 45 с копейками уступило месту новому

значению. И так далее, помещая значение в стек сопроцессора, программа сдвигает предыдущий

«мусор», который в принципе то может по праву

считаться мусором. Думаю что ситуаций, когда

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

вычисление

единственное

трансцедентных

синуса-косинуса Рис. 2. Работа сопроцессора

регистра

функций

типа

Соответственно

когда

потребует

сопроцессора.

вычисление

задействовать

3-4

число «выгребается» из сопроцессора, по идее

Рис. 3. Работа сопроцессора (2)

ЛАБОРАТОРИЯ

СОДЕРЖАНИЕ

33


ПРО

граммист

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5

№12 (март) 2011

Рис. 4. Работа сопроцессора (3) оно «выгребается» из верхушки стека из ST0. Идущие

после

него

значения

пододвигаются

поближе к вершине. 3.0 уйдет в память, а 45 с копейками займет его место в ST0. То, что было в

ST2 переселится в ST1, на место 45-ти, и так далее за одним маленьким исключением – стек прокрутится,

вершина

его

не

исчезнет,

«самые помидорки», дело может спасти команда FINIT,

которая

изначальное

инициализирует умолчанию.

сбрасывает

состояние. его

регистры

сопроцессор

в

значением

по

Если

точнее

а

Выглядит это так: все правильные (valid-ные)

стека ST7, и продолжит вращаться, двигаясь к

вершине стека в порядке их внесения в стек (это

переместится по кругу в последний регистр своему прежнему месту. Можете попробовать поиграться примером типа (см. рисунок 5).

Заодно заметьте, что если стек переполнен, а мы

продолжаем в него посылать данные, процессор

не помещает в него данные, а ставит NAN значение. Это тоже стоит учитывать. И стараться

не посылать более чем 7 значений без выборки. Впрочем, если уж пришлось нагрузить проц по

ЛАБОРАТОРИЯ

значения я

с

собираются,

позиции

и

перестраиваются

наблюдателя

говорю),

к

таким

образом, чтобы между ними не было инвалидных

значений. Инвалидные же значения падают на

дно стека. Сбрасываются флаги состояний. Эта информация для интереса, не более. Врядли нам понадобятся такие «выкрутасы».

Итак. Операции, которые нам понадобятся для работы с вещественными

СОДЕРЖАНИЕ

34


ПРО

граммист

№12 (март) 2011

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5

function FCOS;

begin

Result:=#$D9#$FF;

end;

// Кстати. в функционале сопроцессора есть опкод FSINCOS, // который рассчитывает сразу и синус и косинус, помещая в // первые два регистра результаты. В принципе можно использовать // и ее, но... Религия Пятен На Солнце мешает :D Кстати бытует // мнение, что эта операция медленная. Не могу ручаться за его // правдивость. Вносит в вершину стека число ПИ. Оно настолько // магическое, что его специально вставили отдельной командой. // Круто! function FLDPI;

begin

Result:=#$D9#$EB;

end;

// Эта команда вычитает из вершины стека целое число, помещенное // в стек программы. Вообще при общении с сопроцессором я // предполагаю активно использовать программный стек (ту часть // памяти, на которую указывает ESP), и через него передавать // данные сопроцессору. function FISUB_ESP;

FISUB

DWORD PTR SS:[ESP]

begin

Result:=#$DA#$24#$24;

end;

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

Рис. 5. Пример «поиграться» // FPU опкоды *********************************************

// когда результат вычитания нужно поместить в ST0 для взятия // его оттуда function FXCH;

begin

Result:=#$D9#$C9;

end;

// Инициализирует сопроцессор. Пригодится если встретится // вещественное, для включения сопроцессора к действу над

// Операции вычитания. Вычитаются вершины стека сопроцессора. В

// выражением. Кстати подробнее об этом можно почитать в хелпе

// зависимости от ситуации могут понадобится разные вариации

// или в [1]

// вычитания либо ST0 из ST1 либо наоборот

function FINIT:String; begin Result:=#$DB#$E3;

function FSUB_ST1_ST; end;

// Помещает 10 байтовое значение из переменной в верхушку стека // сопроцессора FSTP function FPopDMem;

TBYTE PTR DS:[mem]

begin

Result:=#$DC#$E9+FXCH; function FSUB_ST_ST1;

end;

begin

Result:=#$D8#$E1;

end;

begin

Result:=#$DB#$3D+m.Addr;

end;

// Помещает в стек сопроцессора 32-битное целое значение из // вершины стека программы. Таким образом, происходит обмен

// Помещает в вершину стека единицу. Будем использовать для

// между программой и сопроцессором - через программный стек

// инкремента-декремента. Почему бы не применить его для

// FILD

// вещественных.

function FILD_ESP;

function FLD1;

begin

Result:=#$D9#$E8;

DWORD PTR SS:[ESP] begin

Result:=#$DB#$04#$24;

end;

end; // Обратная операция. Помещение в стек 10 байтного значения из

// Вычисляет Синус и косинус. Может быть, и не пригодится на

// регистра сопроцессора. Нужно ли говорить что в стеке

// первых порах, но показательно будет ее запрограммировать.

// программы должно быть заранее зарезервировано необходимое

function FSIN;

// кол-во элементов? Думаю нет. Впрочем, потом я покажу это на

Result:=#$D9#$FE;

ЛАБОРАТОРИЯ

begin end;

// примере. Фактический аналог:

СОДЕРЖАНИЕ

35


ПРО

граммист

// PUSH

№12 (март) 2011

EAX(3раза);

FSTP

function FSTP_ESP;

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5

TBYTE PTR SS:[ESP]

function FPushDMem;

begin

begin

Result:=#$DB#$2D+m.Addr;

Result:=#$50#$50#$50#$DB#$3C#$24;

end;

end; function FPushIMem;

// То же самое, но в стек помещается 32-битное целое

begin

Result:=#$DB#$05+m.Addr;

end;

// (округленное) из ST0 сопроцессора. В стеке программы должно // быть зарезервировано 4 байта - стандартный элемент стека.

// Помещает целое число в вершину сопроцессора. Использует

// FISTP

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

DWORD PTR SS:[ESP]

function FISTP_ESP;

begin

// Естественно после вложения в сопроцессор стек нужно очищать,

Result:=#$DB#$1C#$24;

end;

// что и проделывает операция

ADD

ESP, 4

function FPushIConst; // Помещает в вершину стека сопроцессора, размещенные в стеке

begin

// программы 10 байт, представляющие вещественное число. На

Result:=#$68+DWordToStr(i);

// PUSH

Const

// самом деле ради выравнивания стека программы резервироваться

Result:=Result+FILD_ESP;

// FILD

DWORD PTR SS:[ESP]

// под вещественное будет 12 байт, хотя конечно никто не мешает

Result:=Result+AddESP(4);

// ADD

ESP, 4

// резервировать чисто 10 байт, главное потом не запутаться с

end;

// указателем программного стека (ESP) и не выйти из программы в // какую-нибудь неизвестность. function FLD_ESP;

FLD

TBYTE PTR SS:[ESP]

begin

// Помещает в сопроцессор в ST0 вещественное. Константу. Само // число занимает 80бит (10 байт), функция разбивает их на 5

Result:=#$DB#$2C#$24;

end;

// двойных слов, чтоб достигалось выравнивание относительно // стека программы, кратное 4-м. Каждое слово помещается в стек,

// суммирует вершину стека сопроцессора (ST0) и другой регистр

// начиная со старшего слова заканчивая младшим. После чего

// сопроцессора. В параметрах указывается номер регистра.

// выбирается из стека (не все, а только 10 байт, не забывайте

// Результат помещается в ST0

// помещаем мы 12 байт) наше значение. Естественно после

function FADD_ST0_ST;

// помещения нужно очищать 12 байт стека, чем и занимается

begin

Result:=#$D8+chr(i+$C0);

end;

//

AddESP(sizeof(TDoubleConst))

function FPushDConst; // Умножение ST0 и другого регистра, по указанному номеру.

var dc:TDoubleConst;i:integer;

// Результат так же помещается в верхушку стека сопроцессора.

begin Result:='';

function FMUL_ST0_ST;

begin

FillChar(dc,sizeof(TDoubleConst),0);

Result:=#$D8+chr(i+$C8);

end;

move(d,dc,sizeof(d)); i:=high(dc);while i>=low(dc) do begin

// Деление ST0 и другого регистра, по указанному номеру.

Result:=Result+PushConst(dc[i]);

// Результат так же помещается в верхушку стека сопроцессора. function FDIV_ST0_ST;

dec(i);

begin

end;

Result:=#$D8+chr(i+$F0);

end;

Result:=Result+FLD_ESP+AddESP(sizeof(TDoubleConst)); end;

// Может пригодиться вариация деления. Когда делится ST1 на ST0 function FDIV_ST1_ST;

begin

// Помещает округленное значение из вершины сопроцессора в

Result:=#$DC#$F9+FXCH;

end;

// регистр ЕАХ для последующей целочисленной обработки. // Например, может применяться в качестве функции округления.

// Комбинация (для удобства). Помещает в сопроцессор целое из

// Скажем так предусмотренна на будущее.

// ЕАХ. Может часть применяться, поэтому удобнее сформировать

function FPop_EAX_ST0;

// этот набор операций заранее, и в классах-обработчиках

begin

// применять уже его, дабы не писать много одно и тоже function FPushEAX;

begin

end;

Result:=PushEAX+FILD_ESP+AddESP(4);

end;

// Помещают в сопроцессор из переменной вещественное или целое, // минуя пересылку через стек программы. Аналоги: // FLD

TBYTE PTR DS:[mem] и

ЛАБОРАТОРИЯ

Result:=PushEAX+FISTP_ESP+MOV_EAX_ESP+AddESP(4);

FILD

DWORD PTR DS:[mem]

Фух… Сколько писанины… Нелегкая это работа компилятор тащить за… гм… кхм… Road on Selva

СОДЕРЖАНИЕ

36


ПРО

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5

№12 (март) 2011

граммист

Ну, что ж… Можно сказать, что контент, то, чем

любят не все. Все любят удобство, этого в Си

приготовлен, и ждет своего применения. Теперь

классическом

будем

наполнять

наш

исполняемый

файл,

задача чуть упрощается. Описать обработчики

операторов проще, если конечно понимаешь, как они должны действовать, вот давайте и научимся это

делать,

тем

более,

что

в

большинстве

случаев, они, скорее всего, будут не очень сильно отличаться друг от друга, по крайней мере, по структуре

методов

обработки,

а

уж

какие

команды туда добавить, мы, думаю, разберемся. Еще

раз

операторов

повторюсь, будут

что

классы-обработчики

унаследованы

от

класса-

шаблона, с едиными для всех методами. С какого

предостаточно.

в

в

написании

когда программисты пишут загуголину вроде: int i=1;

int k=k+++i+++k++;

Прям BrainFuck… Видимо, именно от этого и хотел избавить Никлаус Вирт свой язык, не дав ни

малейшего

шанса

воспаленной

фантазии

мозга программиста написать какую-нибудь**

«отсебятину».

предусмотрел список операций, которые стоит

операцию

слова. Заранее давайте договоримся, что каждая

удобств

мере,

хотя иногда это доходит до абсурдного маразма,

Но,

сейчас запрограммировать – зарезервированные

Си

крайней

выражений больше, чем в классическом паскале,

оператора стоит начать? Полагаю, что проще всего с операции сложения. Выше, в коде, я

По

оставим

описать

один

лирику из

в

стороне.

классов,

сложения,

с

Попробуем

отвечающий

использованием

за

того

модуля, в котором лежат функции с опкодами.

операция будет являться функцией, т.е. что-то

Надеюсь, никто не будет против того, чтобы

учитывать,

случае, я это сделаю. Модуль назову – Plus. Вот

возвращать.

Результат

но

операций

на

примере

В

Паскале

можно

инкремента

не

Си,

сравнивая его с паскалем, можно просмотреть выгоду

такого.

предусмотрена увеличивает

для

процедура

значение

инкремента

inc(),

переменной

которая

на

некое

число. Но, это процедура. Я не могу использовать ее в выражении. В Си же немного по-другому. Я

могу использовать инкремент в выражении без особого стеснения. В этом случае увеличится значение

в

переменной,

увеличенное в выражение: int n=1;

и

вернется

уже

его начало: interface

uses UnitCompiler, types; type //** Сложение ************************************ TAlisaPlus=class(TAlisaReserved) private Function OnDouble(d:Extended):String;override; Function OnInt(i:integer):String;

override;

Function OnMember(m:TAlisaMem):String;override;

int k=++n+1;

Function OnOperator(Oper:string):String;override;

В результате, мы одной строкой увеличили n, и

это повлияло на результат в k. В языках Вирта пришлось бы писать в две строки:

end; //**************************************

Не сильно замысловатый класс, наследующий необходимые методы от шаблона. OnInt для обработки

Var n=1;

целого

операнда,

OnDouble

для

вещественного и т.д. Теперь давайте реализуем

Begin

по порядку. OnDouble будет выглядеть так:

Inc(n); K:=n+1; End.

Только потому, что Вирт не захотел (его, кстати, можно

понять)

возможность

предусмотреть

«самоинкремента»

такую

как

функцию.

Но

строгость

Строгость его конек, и зачастую эта строгость играет

поместить его в отдельный модуль? В любом

на

руку

ЛАБОРАТОРИЯ

программисту.

** Комментарий автора. Такие

выражения

напоминают

мне

язык

«падонкаф».

Вроде

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

СОДЕРЖАНИЕ

37


ПРО

граммист

№12 (март) 2011

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5

// Обработка операнда, если он является вещественным числом

begin

function TAlisaPlus.OnDouble(d: Extended): String;

(*Не будем забывать инициализировать переменную-результирующую функции, дабы не выдать мусор, оставшийся в стеке.*)

//****************************************

Result:='';

(*Если результат операции до этого операнда был целочисленный

(*Если тип операции еще не известен, но первый операнд

Придется принимать меры к приведению целого к вещественному.

вещественное, то вся операция подразумевает работу с

То-есть нужно передать сопроцессору рассчитанное целое число

вещественными - ее тип вещественное число*)

Как я уже говорил сделать это можно поместив результат в стек из

if TypeOfLastOperand=_Unknown then TypeOfLastOperand:=_Double;

регистра ЕАХ, и выбрать из стека в сопроцессор. После не забыть скормить сопроцессору наше вещественное и провести операцию

(*Проверим, какой тип операции был последним. Для каждого типа

сложения*)

придется писать свой обработчик, потому как универсальных

procedure IfInt;

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

begin

два типа - целые и вещественные числа*)

Result:=Result+FPushEAX+FPushDConst(d)+FADD_ST0_ST(1); end;

case TypeOfLastOperand of _Int:IfInt; _Double:IfDouble;

//****************************************

end;

(*Если же результат операции уже известен как вещественное, он должен находится на вершине стека сопроцессора в ST0, наша

(*На последок в каждом обработчике нужно не забыть указать к

задача сводится к простому внесению в сопроцессор очередного

какому типу привести результат выражения. Причем приоритет

операнда и сложения его с подвинувшимся

больше у вещественного, потому как целое является часным случаем

в ST1 результата. Полученное при сложении число помещается в

его.*)

ST0, заменяя операнд, помещенный в сопроцессор.

if TypeOfLastOperand=_Int then TypeOfLastOperand:=_Double; end;

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

Кода не так уж и много. Больше комментария, который

поясняет

(надеюсь)

основную

обработчика. Далее обработчик целых:

сложения, а просто поместить в сопроцессор операнд

(*Если же у нас операнд целое*)

2:Не стоит забывать про переполнение процессора. Если

function TAlisaPlus.OnInt(i: integer): String;

идею

понапомещать в него операндов штук 10, то как я писал выше стек сопроцессора переполнится, и все следующие после 7-го элемента в

//****************************************

сопроцессор пойдут NaN. В этом случае придется прибегнуть к

(*Будем работать с ним в рамках ЦП. Незачем загружать

услугам инструкции FINIT. Чтоб сбросить сопроцессор в

сопроцессор такой мелочью, тем паче с его округлением можно

изначальное состояние. В принципе никто не мешает нам заранее

нарваться на неприятность. Все целые, пока они не будут

проинициализировать

приведены к вещественному типу будет обрабатывать центральным

сопроцессор*)

камушком*) //****************************************

procedure IfInt;

procedure IfDouble;

begin

begin

(*Если тип выражения еще целый, то либо просто поместить в

// Если это первый операнд, и он вещественный инициализируем

регистр ЕАХ первый операнд, если он первый, либо провести

// сопроцессор

операцию сложения, если операнд не первый, точнее сложить то,

if OperandNumber=1 then Result:=FINIT;

что было в ЕАХ, с константой, которую ядро передаст в

//и вкладываем операнд

обработчик*)

Result:=Result+FPushDConst(d);

if OperandNumber=1 then

// Если он не первый то проведем суммирование

Result:=Result+MovEAXConst(i)

if OperandNumber>1 then Result:=Result+FADD_ST0_ST(1); end;

else Result:=Result+ADDEAXConst(i);

//****************************************

ЛАБОРАТОРИЯ

end;

СОДЕРЖАНИЕ

38


ПРО

граммист

№12 (март) 2011

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5

//****************************************

значение сопроцессору, и проведем операцию, если она не первая.

(*Если же выражение уже приведено к типу вещественного, и работа

Единственный затык в типе переменной. Если она целая, то и

ведется с сопроцессором, нам придется скармливать сопроцессору

помещать ее в сопроцессор нужно операцией помещения целого

наше целое, и при условии, что это не первый операнд проводить

FPushIMem. Если же вещественная, то соответственно другой

операцию сложения. *)

операцией FPushDMem*)

procedure IfDouble;

procedure IfDouble;

begin

begin

if OperandNumber=1 then Result:=FINIT;

if OperandNumber=1 then Result:=FINIT;

Result:=Result+FPushIConst(i);

if m.TypeMem=_Double then

If OperandNumber>1 then

Result:=Result+FADD_ST0_ST(1);

Result:=Result+FPushDMem(m)

(*Естественно не забудем предупредить компилятор о том, что

else

наше выражение уже стало вещественным.*)

Result:=Result+FPushIMem(m);

if TypeOfLastOperand in [_Int,_Unknown] then

(*После чего следуют стандартные шаги суммирования, если

TypeOfLastOperand:=_Double;

конечно это не первый операнд.*)

end;

if OperandNumber>1 then

Result:=Result+FADD_ST0_ST(1);

(*Аналогично нужно напомнить компилятору, что начиная отсюда, и //****************************************

заканчивая выражением теперь он имеет дело с вещественными*)

(*Тут стандартный набор - приведение к целому, если тип

if TypeOfLastOperand in [_Int,_Unknown] then

выражения еще не установлен и в зависимости от типа выражения выполнение работы в том или ином направлении*)

TypeOfLastOperand:=_Double; end;

begin Result:=''; if TypeOfLastOperand=_Unknown then TypeOfLastOperand:=_Int;

//****************************************

case TypeOfLastOperand of

begin

_Int:IfInt;

(*Тут важно Результ инициализировать. Не забудьте этого*)

_Double:IfDouble;

Result:='';

end;

if TypeOfLastOperand=_Unknown then

end;

TypeOfLastOperand:=m.TypeMem; case TypeOfLastOperand of

Далее идет работа с переменными:

_Int:IfInt; _Double:IfDouble;

(*Если же операндом является переменная, действия будут почти аналогичные учитывая конечно, что в операции будет ссылка по адресу на ячейку переменной*) function TAlisaPlus.OnMember(m: TAlisaMem): String; //****************************************

end; end;

И,

наконец,

подвыражения:

обработчик

оператора-

(*Если наша переменная целого типа, и сама операция еще не

(*В выражениях могут участвовать другие выражения - вложенные.

получила статус вещественного то провести операции аналогично

Этот случай тоже нужно предусмотреть отдельно. Посмотрите, как

операциям с константой, но по адресу в памяти, а не просто по

реализована обработка вложенных выражений в шаблоне - вызов и

числу, оформленному в опкоде как операнд. Или если по-русски -

компиляция подвыражения, с возвратом скомпилированной строки

операндом опкода будет указатель на переменную*)

опкодов. Здесь и в дальнейшем мы переопределим обработчик, не

procedure IfInt;

забыв вызвать метод родителя - нам же все равно нужно как-то

begin

скомпилировать подвыражение. Однако его результат нужно

if OperandNumber=1 then Result:=Result+MovEAXMem(m) else Result:=Result+ADDEAXMem(m); end;

обработать, и тело такого обработчика будет разным в зависимости от оператора.*) function TAlisaPlus.OnOperator(Oper: string): String; (*Для начала нам нужно где-то запомнить какой тип операции был до выполнения вложенного выражения.*) var TypeBeforeOperation:TypeOper;

/**************************************** (*Аналогично если переменная вещественных кровей, скормим ее

ЛАБОРАТОРИЯ

//****************************************

СОДЕРЖАНИЕ

39


ПРО

граммист

№12 (март) 2011

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5

(*После выполнения подвыражения вполне возможна ситуация, когда

(*Если вещественное кроме этого еще и перевести тип выражения

тип выражения не совпадает с типом, возвращенным подвыражением,

к вещественному*)

и в этом случае придется типы приводить. Всего мы имеем на руках

_Double: begin

два результата: один тот, что был до выполнения подвыражения, он

Result:=Result+FLD_ESP+FADD_ST0_ST(1)+

помещен в стек программы, а другой тот что вернуло подвыражение,

AddESP(sizeof(TDoubleConst));

и он находится в регистрах ЦП или сопроцессора. Поэтому придется

TypeOfLastOperand:=_Double;

и здесь приводить к единому типу по приоритетности типов*)

end;

procedure IfOperInt;

end;

begin

end;

case TypeBeforeOperation of (*Если это целое, то результат выражения хранится в стеке

//****************************************

программы в 4 байтах на вершине стека (о том нужно сурово

begin

позаботится, чтоб подвыражение не оставляло после себя в стеке

Result:='';

программы мусор). Если подвыражение целое просто просуммируем

(*Итак, перед компиляцией подвыражения запоминаем тип

значение в ЕАХ и верхушку стека, не забыв из стека выкинуть

выражения*)

уже не нужно после суммирования значение.*)

TypeBeforeOperation:=TypeOfLastOperand;

_Int:

(*В зависимости от типа выражения либо сохраняем в стек

Result:=Result+ADD_EAX_ESP+AddESP(4);

программы из ЕАХ центрального процессора, либо из ST0 (*В случае если подвыражение вещественное, телодвижений будет

сопроцессора*)

поболее. Учитывая что здесь обрабатывается случай, когда

if TypeBeforeOperation=_Double Then

выражение до начала подвыражения имело целый тип результата,

Result:=Result+FSTP_ESP;

нам придется привести сохраненное в стеке, передав его

if TypeBeforeOperation=_Int

сопроцессору, так же не забыв очистить стек программы. Будем

Then

Result:=Result+PushEAX;

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

(*После чего не побоимся скомпилировать подвыражение*)

не был затронут, я предлагаю все равно перед вычислением

Result:=Result+ inherited OnOperator(Oper);

подвыражения сохранять в стек результат, дабы не потерять.

(*И в зависимости от типа подвыражения выполнить описанные выше

Хотя конечно при наличии оптимизатора, тот бы понял, что стек

функции по сочетанию результатов и приведению к единому типу*)

трогать не надо, но у нас его нет, так что будем сохранять. А

case TypeOfLastOperand of

поскольку он лежит в стеке программы, его нужно вернуть

_Int:IfOperInt;

обратно сопроцессору. И после уж передать ему же результат уже

_Double:IfOperDouble;

вычисленной части выражения просуммировав их*)

// Если оператор выдал целое // Если оператор выдал вещественное

end;

_Double:begin Result:=Result+PushEAX+FILD_ESP+PopEAX+FLD_ESP+ AddESP(sizeof(TDoubleConst))+FADD_ST0_ST(1); (*Естественно тип выражения будет приведен к вещественному, ибо у него приоритет выше.*) TypeOfLastOperand:=_Double; end;

Надеюсь, я ничего не забыл описать… Работу

ядра и вызовы объектов-обработчиков, пример объекта

обработчика

для

оператора

суммирования, и модуль сборник функций с опкодами, чтоб не писать в телах обработчиков

end;

много одинакового.

end; //**************************************** (*Если до вычисления подвыражения тип был вещественный, то наша задача передать сохраненное в стеке программы в сопроцессор. Естественно не забыв просуммировать инструкцией FADD ST0,ST1*) procedure IfOperDouble; begin case TypeBeforeOperation of (*Если это целое - выбрать его, не забыв почистить стек*) _Int:

end;

Result:=Result+FILD_ESP+FADD_ST0_ST(1)+AddESP(4);

ЛАБОРАТОРИЯ

Если

все

правильно

сделано,

теперь

можно

попробовать построить «выраженьеце суммы», и

посмотреть в отладчике как оно себя поведет. Давайте для примера возьмем что-нибудь типа 2+2+(28.3+45)+5. Понятное дело, что скобочки тут не имеют особого смысла, но для проверки

обработчика оператора пригодятся как тестовые. В

ЛИСП-синтаксисе

это

выражение

будет

выглядеть как (+ 2 2 (+ 28,3 45) 5). Так и

СОДЕРЖАНИЕ

40


ПРО

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5

№12 (март) 2011

граммист

напишем в редакторе программы (см. рисунок 6):

забыл. Еще раз условимся – разделителем в вещественном числе будет точка и только точка.

Итак, посмотрим на «кишки» скомпилированного файла (см. рисунок 7). Что тут происходит? •

в регистр ЦП помещается первый операнд. Компилятор

распознал

его

как

целое,

и

решил что аферку с ним стоит провернуть в •

Рис. 6. Пробуем выражение Вы

же

не

вышеприведенному

забыли коду

прикрутить

красивый

к

редактор?

Или забыли? Если забыли – прикрутите. А если

прикрутили – компилируем. На выходе в папке с

компилятором должен появиться файл File.exe. Откроем его в «Оле» (Olly Debuger). О! пока не

ЦП без сопроцессора; к

помещенному

добавляется

в

константа.

регистр Она

значению

тоже

целая,

компилятор ее скармливает ЦП без тени •

смущения. Это первая часть выражения 2+2; что

же

встречает

происходит

дальше?

подвыражение.

Он

Компилятор знает

что,

встретив подвыражение, нужно запомнить в стеке

программы,

на

который

указывает

регистр ESP, уже просчитанный результат.

Рис. 7. Скомпилированный файл

ЛАБОРАТОРИЯ

СОДЕРЖАНИЕ

41


ПРО

граммист

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5

№12 (март) 2011

Поскольку до этого момента он имел дело с

не хочет в рамках этого выражения трогать

командой PUSH EAX живность 32-битного

пойдет только с сопроцессором, поэтому ему

целыми, вполне резонно, что в стек попадает регистра, •

равное

кстати

4-м.

центральный

Пройдите

пошаговку (F8) – убедитесь сами;

скармливается

а

ядро

определило

что

это

это

вещественное

число

равное

операнд 28.3.

компилятор принял решение задействовать

тяжелую артиллерию в лице сопроцессора. тремя

операторами

PUSH,

почистить

от

мусора,

чем

и

команда ADD ESP, 0C, передвигая указатель стека

на

12

байт

вниз.

Можно

было

он

первый,

теперь

нужно

подвыражения поскольку работа

уже

будет

поместить

второй

операнд

за

в

пазуху

понятно, с

что

сопроцессора, дальнейшая

вещественными.

Этим

займется набор команд PUSH 2D и FILD DWORD PTR SS:[ESP]. Где: 2D – собственно число

45.

Не

забыв

почистить

стек

программы, компилятор дописал ADD ESP, 4, •

программы,

после

чего

чутким

руководством)

добавил

одну позицию (на 4 байта) ADD ESP, 4; стек

очищен,

последний

но

у

операнд.

5-ти.

Его

нас

Он

есть

еще

один,

целочисленный,

так

же

передаем

добавит

командой FADD компилятор

его

к

общей

сумме

ST, ST(1), а перед ней

любезно

вставит

команду

очистки стека программы от этой пятерки. И

в

результате

сопроцессора

мы

получаем

результат

(см.

на

верхушке

рисунок

8).

Проверим на калькуляторе: 2+2+28.3+45+5 =

82.3. Что есть неоспоримым доказательством работоспособности, работоспособности

самого компилятора.

и

главное

программы,

а

правильной значит

и

чтоб убрать из стека этот операнд, уже не

Welcome home

сопроцессор

Уф… Как вы думаете, сможет ли такая работенка

нужный там;

получил

два

операнда

подвыражения. Теперь он может провести с ними

операцию,

операция оператор

задуманную

сложения, FADD

за

ST,

нами.

которую ST(1).

Это

отвечает

Результат

поместится в вершину стека сопроцессора в •

стека

сопроцессор

компилятор, зная это, не прикрутил еще операцию сложения;

из

сопроцессору через стек программы, так же

таким образом, сопроцессор получил свой поскольку

третий

в ST1, дабы уступить место числу 4 (это 2+2

равный

вдруг еще значение в ЕАХ необходимо; операнд,

ибо

операцию смещения указателя стека вниз на

EAX, но это некрасиво смотрится, и мало ли первый

вычисления,

результат подвыражения любезно подвинется

нашим

выполнить для очистки реестра три раза POP •

сохранили

посему компилятор предусмотрительно (под

необходимо занимается

благоразумно

не чистит стек от уже ненужного значения,

из стека эти, м-м-м, 10 байт, два байта не стек

PTR

вычисления

будем забывать, что ни одна из этих команд

TBYTE PTR SS:[ESP] выбирает чего

DWORD

операндов, и результат поместит в ST0. Не

программы то у нас 32-битный), после чего после

до

команда FADD ST, ST(1) проведет сложение

слова (слова, а не байта, потому что стек

трогает,

FILD

мы

и готов к соитию с суммой первых двух; дало)

естественно начиная со старшего двойного команда FLD

материнского

операнд – результат подвыражения вычислен

Для этого число 28.3 помещается в стек программы

операция

Помните,

продолжения

с

вещественными работать не умеет, посему

работа

результат в стек. Теперь он пригодится для

его

ЦП

результат

подвыражения

выражение, и занялся вовсю встреченным Первый

ответит

SS:[ESP].

подвыражение – теперь компилятор отложил подвыражением.

Теперь

выражения, поскольку он целочисленный, за

встретились скобки, парсер взял его как строку,

процессор.

ST0;

подвыражение

закончено.

Компилятор

присвоил ему статус вещественного и больше

ЛАБОРАТОРИЯ

потянуть на диплом? Думаю да. Если продолжать

«наворачивать» прочими

интересно

компилятор

вкусностями, поиграться

попробовать его в деле. На

фоне

зачастую

с

операциями

комиссии новым

банальных

и

и

будет

языком, скучных

дипломок, сто раз передранных у предыдущего

СОДЕРЖАНИЕ

42


ПРО

КОМПИЛЯТОР ДОМАШНЕГО ПРИГОТОВЛЕНИЯ. ЧАСТЬ 5

№12 (март) 2011

граммист

Рис. 8. Результат работы программы поколения

однозначно

студентов станет

что-то

новое

предметом

и

свежее

всеобщего

внимания. Я, конечно, не пробовал нагрузить компилятор

сложными

выражениями,

предоставляю эту работенку вам, но, думаю,

The Чтиво •

FLAT ASSEMBLER 1.64. Мануал программера http://flatassembler.narod.ru/fasm.htm

основные принципы компиляции соблюдены в достаточном количестве условностей. Итак,

на

сегодня

все.

Пока

хватит,

все

последующие арифметические операции будут как братья похожи на этот обработчик, я опишу их

позже.

В

следующей

статье.

Работу

с

синусами, косинусами, логарифмами, оператор

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

А на сегодня, Auf Wiedersehen...

ЛАБОРАТОРИЯ

СОДЕРЖАНИЕ

43


ПРО

№12 (март) 2011

граммист

ПОДКЛЮЧЕНИЕ МИКРОКОНТРОЛЛЕРА К GPS ПРИЕМНИКУ

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

представляют собой символы из ASCII таблицы

by VoJak http://avrall.com.ua

символов.

2. Практическая часть

Введение На

днях

удалось

взять

GPS-приемник

для

экспериментов (см. рисунок 1) у друга aka Kalina. GPS-приемник просто дель

(далее

приемник)

MARS-200AS

Для того чтобы та каша в голове, которая

образовалась при прочтении теории о протоколе, упорядочилась я решил подключить приемник к

мо-

тора

с

проводил

PS/2. Документация на [1].

данного редает цию: • • • • • • •

прибор*

Итак,

здесь

приемника

0.4.263

пе-

[2],

протокол

SkyTraq

ле экспорта данных из

нее и подредактировав, я анализировал код в

WinHex [3]. Для удобст-

GGA – информация о

ва анализа пользовался

ре-

шении;

помощью

сдирал утилиткой. Пос-

информа-

фиксированном

содрать

с

программы

протокол

такую

портов

протокол обмена. Связь

ТТЛ выходом и разъемом данный

ПК и с помощью мони-

таблицей ASCII [4].

GLL – данные широты

Дамп обмена (частично)

GSA – общая инфор-

данные

и долготы;

в таблице 1**. Числовые

мация о спутниках;

кодами каждой цифры

GSV – детальная информация

о

никах;

представлены

из таблицы ASCII соот-

спут-

ветственно.

RMC – рекомендован-

Итак, все сообщения с

набор GPS данных;

со

ный

VTG

минимальный

вектор

приемника начинаются

дви-

затем

жения и скорости; обмена

ставляет

собой

секунду,

данные,

ция в данном информа-

пересы-

ционном пакете заключена: RMC, VTG, GGA,

кото-

последова-

данных

ЛАБОРАТОРИЯ

(0×47,0×50)

ющие, какая информа-

пред-

GSA, GSV, GLL. Различ-

рые имеют строго опретельность

(0×24),

идет три байта означа-

лаемые, с периодом в 1 деленную

GP

$

после этих трех байтов

ZDA – дата и время.

Протокол

значка

и

ные Рис. 1. Устройство в работе

данные

в

пакете

разделены между собой запятой «,» (0×2C). В

СОДЕРЖАНИЕ

44


ПРО

граммист

№12 (март) 2011

ПОДКЛЮЧЕНИЕ МИКРОКОНТРОЛЛЕРА К GPS ПРИЕМНИКУ

Таблица 1. Дамп обмена

конце

после

будет

два

информационных

данных

стоит

02,34,054,42,05,21,102,35*71

суммы,

затем

2447504753562C322C312C30362C31322C33382C3132392C32362C33312C3432

звездочка «*» (0×2A). Она означает, что сейчас байта

контрольной

символы CR LF (0×0D, 0×0A). Это команда – возврат каретки, по сути конец пакета.

Ниже выкладываю дамп той части, где приемник видит

спутники

и

выдает

местоположении и так далее:

информацию

$GPRMC,185536.00,A,5051.12898,

о

2C3238362C33312C30322C33342C3035342C3432 2C30352C32312C3130322C33352A37310D0A $GPGSV,2,2,06,10,21,067,37,30,71,148,*70 2447504753562C322C322C30362C31302C32312C3036372C33 372C33302C37312C3134382C2A37300D0A

N,02873.41497,E,0.000,,170610,,,A*77 $GPGLL,5051.12898,N,02873.41497,E,185536.00,A,A*6F 244750524D43 2C 3138353533362E30302C41 2C 353035312E31323839382C4E 2C30323837332E

244750474C4C2C353035312E31323839382C4E2C30323837332E34313439

3431343937 2C 452C302E303030 2C2C

372C452C3138353533362E30302C412C412A36460D0A

313730363130 2C2C2C 41 2A37370D0A $GPVTG,,T,,M,0.000,N,0.000,K,A*23 2447505654472C2C542C2C4D2C302E3030302C 4E2C302E3030302C4B2C412A32330D0A $GPGGA,185536.00,5051.12898,N,02873.41497, E,1,04,9.82,21.5,M,28.7,M,,*66

Поскольку реализовывать весь данный протокол я пока не собираюсь, так как цель – вывод текущих

координат

координат на

ЖКИ

и

вывод

(индикатор

требуемых

на

жидких

кристаллах). Для этой цели подойдет пакет с заголовком RMC. Он включает в себя: $GPRMC,123519,A,4807.038,N,01131.000, E,022.4,084.4,230394,003.1,W*6A

2447504747412C3138353533362E30302C353035312E31323839382C4E 2C30323837332E34313439372C452C312C30342C392E38322C32312E352C4D 2C32382E372C4D2C2C2A36360D0A

$GPGSA,A,2,31,02,05,10,,,,,,,,,9.87,9.82,1.00*03 2447504753412C412C322C33312C30322C303 52C31302C2C2C2C2C2C2C2C2C392E3837 2C392E38322C312E30302A30330D0A $GPGSV,2,1,06,12,38,129,26,31,42,286,31,

ЛАБОРАТОРИЯ

где: RMC – заголовок, 123419 – UTC время (12:34:59), А – статус

(А – активный, V –

игнорировать), 4807.038,N – широта (48 градусов

07.038 минут северной широты), 01131.000,Е – долгота (11 градусов 31.000 минута восточной

долготы), 022.4 – скорость (в узлах), 084.4 – * Комментарий автора.

Стоит отметить, что данный прибор имеет и USB исполнение, но

изначально приобретался для возможности подключения к МК. Поэтому при работе приемника с ПК необходимо подключать его через RS232 или USB конверторы. Данный приемник поддерживает протокол

NMEA-0183,

битрейт

9600

(указано

на

корпусе

приемника), напряжение питания 5В. Теория по стандарту NMEA0183 была взята из http://www.gps-profi.ru/nmea.php.

СОДЕРЖАНИЕ

45


ПРО

ПОДКЛЮЧЕНИЕ МИКРОКОНТРОЛЛЕРА К GPS ПРИЕМНИКУ

№12 (март) 2011

граммист

направление движения (в градусах),

230394 –

дата (23 марта 1994 года), 003.1,W – магнитные вариации.

за

компьютером,

то

естественно скорость была нулевая, но после нее идет направление движения в градусах, у меня

МК фирмы Атмел ATmega8L и ЖКИ NOKIA 3310.

данные

электрической

будет выполнять. Реализовать я его собираюсь на

этого три запятых и буква А. Таким образом, если для

бы

что из себя будет представлять прибор и что он

Задача*** прибора:

нее идти должны магнитные вариации, вместо вычисленны

схеме

вроде

которые шлет приемник. Осталось определиться,

сразу идет запятая, затем дата 170610, и после какие-либо

к

этим

NMEA -0183 и разобрались с пакетами данных,

Замечу, что после долготы (Е) идет скорость в сидел

перейдем

С

В предыдущей части мы исследовали протокол

N,02873.41497,E,0.000,,170610,,,A*77

я

сумме).

принципиальной и разработке алгоритма

$GPRMC,185536.00,A,5051.12898,

т.к.

контрольной

разобрались... Теперь

Сравним с посылкой, которую я «задампил»:

узлах,

по

неуместны

данного

или

момента,

не

то

либо

с компьютера залить контрольные точки, а затем,

при

подключении

GPS-приемника,

выборочную точку;

ставятся нули, либо просто ставятся запятые по

Итак,

Памяти МК с лихвой хватит для ASCII таблицы,

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

сообщение

нас

полностью

устраивает, так как содержит: координаты, дату и

время.

направление,

Кроме

использовать.

того,

но

пока

Таким

еще

мы

их

образом,

скорость не

и

будем

обработка

принятых данных будет сводиться к следующим этапам: •

определение начала $ пакета и выделения

выделение данных о времени 185536.00;

• • •

команды GPRMC; выделение

широты

5051.12898,N,02873.41497,E;

и

долготы

выделение даты 170610;

контрольная сумма и конец сообщения.

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

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

пропустить

(но

в

этом

случае

достоверность данных нельзя будет определить ** Комментарий автора.

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

Учтите,

что

данные

в

таблице

после

<CR><LF> указаны в HEX (16-тиричном формате).

ЛАБОРАТОРИЯ

символов

вывод на ЖКИ текущего местоположения (координаты) и координаты требуемой точки.

программной

реализации

обмена

с

ЖКИ

и

приема и обработки данных. Кодить буду на ассемблере.

Поскольку

все

равно

придеться

реализовывать выбор пакетов по содержимому, а

также определять начало пакета, блок данных, контрольную

сумму

и

конец

данных,

то

по

такому же принципу реализую обмен с ПК. Для

этого начало пакета данных будет $GPPCD (GP Personal Comuter Data), окончание такое же как и в приемнике *CS CR LF (CS- checksum).

Электрическая принципиальная схема показана на рисунке 2. На данной схеме частота кварца ZQ1 выбрана равной 7.3728 MHz. Данная частота выбрана из даташита на ATmega 8, так как при данной

частоте

работа

с

последовательным

портом USART происходит с нулевым процентом

ошибок. Питание устройства я решил сделать от 9-ти

вольтовой

защищает

«Кроны».

схему

от

Диод

Шоттки

VD1

переполюсовки,

стабилизатор питания 78L05 обеспечивает 5В питание,

последовательно

включенный

по

питанию диод VD2 понижает 5 В до 3.8 В для питания

ЖКИ.

Выводы

разъем XS4 (female).

USART

выведены

на

SW1 – тумблер питания,

SA1-SA3 – кнопки управления прибором. Данные

кнопки думаю реализовать ввиде джойстика от мобильника.

Конденсаторы

С3…С5

для

антидребезга при нажатии кнопок, чтобы не

СОДЕРЖАНИЕ

46


ПРО

граммист

№12 (март) 2011

ПОДКЛЮЧЕНИЕ МИКРОКОНТРОЛЛЕРА К GPS ПРИЕМНИКУ

Рис. 2. Схема электрическая принципиальная подключения МК к GPS приемнику «заморачиваться» предназначен

для

программно.

Разъем

подключения

ЖКИ

мобильного телефона Nokia 3310.

Печатная плата показана на рисунке 3:

XS3

от

Чтобы

не

усложнять

универсальности),

буду

программу

(во

анализировать

вред

только

необходимые байты – остальные же пропускать. То есть, при анализе протокола, я анализирую символ

«$»

начала

пакета

и

три

символа

Рис. 3. Печатная плата

ЛАБОРАТОРИЯ

СОДЕРЖАНИЕ

47


ПРО

граммист

ПОДКЛЮЧЕНИЕ МИКРОКОНТРОЛЛЕРА К GPS ПРИЕМНИКУ

№12 (март) 2011

команды – «GPR», остальные три пропускаю

отображения

их я анализаирую и после долготы жду только

Анализ

вместе с запятой. Далее идут данные о времени, условие возврата каретки 0×0A).

<CR><LF> (0×0D

обсуждения

с

человеком,

которому оно требовалось – получилось такое:

происходит

по

принятию

времени.

заголовка

данных конкретно для моего приемника. А судя могут

выдавать

в

одних

никакого ввода данных трекерных точек;

никакого ручного ввода координат (позже я

никакой связи с ПК;

и

заголовках разное количество символов.

тех

же

Посылка с нумерацией байтов отображена в таблице

2

(выделил

только

который будем анализировать):

• •

и

$GPRMC. Я адаптировал и упростил обработку фирм

Итак. После кропотливой работы над данным и

координат

по описаниям протокола, приемники различных

Предварительные итоги… устройством

текущих

Клавиатура не задействованна, подсветка тоже.

часть

заголовка,

Таблица 2. Посылка с нумерацией байтов

подумал о вводе координат вручную).

Поскольку

я

информации,

пытался

то

упростить

координаты

обработку

первой

точки

занимали бы 19 байт. А с объемом ЕЕПРОМ в 512

Для

точек.

флаги состояния StRx, StHd, StDt, StStrt, StStop.

кБ сильно много не запишешь – максимум 26 В

итоге

получился

прибор

для

анализа

протокола

назначим

регистр

состояния StReg. Где биты в нем использовал как

Рис. 4. Алгоритм обработки принятого байта

ЛАБОРАТОРИЯ

СОДЕРЖАНИЕ

48


ПРО

граммист

ПОДКЛЮЧЕНИЕ МИКРОКОНТРОЛЛЕРА К GPS ПРИЕМНИКУ

№12 (март) 2011

Где соответствующие биты:

Sbr StReg,StRx_m

StRx – флаг приема байта (устанавливался

StHd

• •

при получении байта по USART); –

флаг

(устанавливался

приема

тогда,

когда

заголовок $GPRMC);

заголовка получен

StStrt – флаг приема байта начала сообщения (устанавливался при приеме символа $; StStop

флаг

(устанавливался

окончания после

приема

получения

пакета

байта, конец «полезных» байт пакета).

45-го

Алгоритм обработки принятого байта показан на рисунке 4. Анализ начала сообщения и заголовка

я сделал в обработчике прерывания по USART. Рассмотрим подпрограмму обработки:

R_st: Cpi UBR_R,’$'

; условие начала сообщения было?

Breq StartSet

; да прыжок на метку

Reti R_Header

; проверка на прием заголовка (байты 2-4)

Cpi Count,1 Breq

; проверка на литеру «G»

G_Lit

Cpi Count,2

; проверка на литеру «P»

Breq P_Lit Cpi Count,3

; проверка на литеру «R»

Breq R_Lit Return:

;************* Обработка прерывания по приему байта

; устанавливаем флаг приема байта

reti

; выход

Clr Count

; очистка счетчика

Cbr StReg,StStrt_m

; очистка бита начала сообщения

ReceiveByte:

Cbr StReg,StRx_m

; очистка бита приема байта

In UBR_R,UDR

Cbr StReg,StHd

; очистка бита приема заголовка

Sbrs StReg,StStrt ; пропустить, если уст.флаг начала сообщения

reti

Rjmp R_st

; прыжок на проверку байта начала сообщения

Sbrs StReg,StHd

; пропустить если установлен флаг

StartSet:

; заголовка сообщения Rjmp R_Header ; прыжок на проверку байта заголовка

; Установка флага условия СТАРТ ($)

Sbr StReg,StStrt_m Inc Count

ldi Count,1 reti

Cpi Count,20 breq zpt

; данная проверка счетчика сделана для того чтобы ; определить когда появятся координаты

G_Lit:

; изначально пока приемник не настроился и не

Cpi UBR_R,’G’

; является принятый байт символом «G»

; определил координаты – он гонит в этих строках

Brne Return

; если нет, прыжок на метку выхода

; запятые

Inc Count

; да, увеличиваем счетчик принятых байтов

RB_1:

reti

Cpi Count,45 ; проверка счетчика равенство посл. байту 45 P_Lit: Brge End_GP

; если равен – прыжок на метку выхода обработки

Cpi UBR_R,’P’

; является принятый байт символом «P»

; пакета

Brne Return

; если нет, прыжок на метку выхода

Inc Count

; да, увеличиваем счетчик принятых байтов

Sbr StReg,StRx_m

; установка флага

; приема байта (устанавливается только после

reti

; принятия заголовка) ;***** Проверка на команду RMC****** Reti

; выход из прерыванияzpt:

cpi UBR_R,’,' ; проверка 20 байта на равенство знаку «,»

R_Lit:

brne RB_1

; если равен – тогда конец обрабатываемого пакета

Cpi UBR_R,’R’

; является принятый байт символом «R»

Brne Return

; если нет, прыжок на метку выхода

End_GP:

; Конец прин-го сообщ-я (для нужной части пакета)

Sbr StReg,StHd_m

; устанавливаем флаг принятия заголовка

Clr StReg

; очищаем регистр состояния

Clr Count

; очищаем счетчик байтов пакета

; StHd_m в регистре статуса Inc Count

Sbr StReg,StStop_M

; устанавливаем флаг конца сообщения

ЛАБОРАТОРИЯ

; увеличиваем счетчик принятых байтов

reti

СОДЕРЖАНИЕ

49


ПРО

граммист

ПОДКЛЮЧЕНИЕ МИКРОКОНТРОЛЛЕРА К GPS ПРИЕМНИКУ

№12 (март) 2011

Рис. 5. Внешний вид устройства (отображен режим отладки, на ЖКИ выведена часть протокола) Где: UBR_R – регистр принятого байта, StReg –

или

принятых байтов.

ячейку ОЗУ, в зависимости от номера байта). С

регистр

статуса,

Count

регистр

счетчик

Для анализа были использованы только первые 4 байта. В основном теле программы определяем *** Комментарий автора.

Усложнять себе задачу – указывать в какую сторону идти и так далее, я пока не буду. Если и захочется реализовать данную

«фичу», тогда допишу и приложу в отдельной статье.

ЛАБОРАТОРИЯ

устанавливаем

флаг

StRx

(если

да,

то

сохраняем принятый байт в соответствующую

данного ОЗУ ведем обновление значений на экране ЖКИ.

А также в конце проверяем или устанавливаем флаг StStop (если установлен, тогда обновляем данные на экране ЖКИ).

Внешний вид устройства показан на рисунке 5.

СОДЕРЖАНИЕ

50


ПРО

ПОДКЛЮЧЕНИЕ МИКРОКОНТРОЛЛЕРА К GPS ПРИЕМНИКУ

№12 (март) 2011

граммист

Заключение

КАРТОЧКА АВТОРА

Собранное устройство – всего лишь один из

Яковлев

простейших вариантов чтения данных с GPS чипа и

обработки

координат.

Я

Владимир

использовал

Витальевич

программный вывод данных на ЖКИ. Используя аппаратный

SPI

для

ЖКИ

освободить

ядро

МК

для

можно

в

разы

Родился 11 декаб-

уменьшить время вывода данных на экран и данных. На

основе

данных

множество

обработки

модулей

интересных

других

можно

устройств.

карту

на

экран,

просто

периодически сохранять точки на SD-card и затем

уже

дома

на

ПК

просмотреть

свой

маршрут. Можно встраивать в модели машин,

роботов, либо просто для контроля местоположения

или

мар-шрута

для

четко

и

его

комбинировании

с

запрограммированного

корректировки.

GSM

модемами

При

есть

возможность следить за объектом, на котором

установлен модуль. И их применение ограничивается только Вашей фантазией и функциональностью конкретного типа модуля.

Сейчас широко распространен этот метод для слежения

передаются

за

по

грузоперевозками: GSM

или

GPRS

координаты

каналам

сервер, где строится маршрут объекта.

на

года

в

Житомире.

В 2001 году закончил

Например,

можно

1979

городе

делать

велокомпьютер с GPS, причем не обязательно выводить

ря

Житомирский

инженерно-техно-

логический институт (ЖИТИ) (сейчас это Житомирский логический

Государственный

Университет

ЖГТУ)

Технопо

специальности – радиотехника, с 2001 по 2008 работал на кафедре Радиотехники при

ЖГТУ инженером, системным администратором, ассистентом. Увлекаюсь

разработкой

электронных

устройств на микроконтроллерах (МК), пишу программы на ассемблере к МК, в среде AVR

Studio для МК фирмы Atmel. Есть множество наработок и задумок, которые впоследствии будут

выложены

на

воплотятся в железе.

сайте,

как

только

Ресурсы • • • • • •

Data

Sheet

GPS_Mars200AS_SPECv1.0

http://avrall.com.ua/wp-content/uploads/2010/

06/GPS_Mars200AS_SPEC%5B1%5D_v1.0.pdf

Утилита SkyTraq 0.4.263 http://avrall.com.ua/ wp-content/uploads/2010/06/GPS-Viewer.exe Утилита

WinHex

http://avrall.com.ua/wp-

Таблица

ASCII

http://avrall.com.ua/wp-

content/uploads/2010/06/winhex.zip content/uploads/2010/06/ascii.pdf Ресурс

организации

Electronic

Foundation https://www.eff.org

Frontier

Персональный сайт автора http://avrall.com.ua

ЛАБОРАТОРИЯ

СОДЕРЖАНИЕ

51


ПРО

КРОССПЛАТФОРМЕННЫЙ ОСЦИЛЛОГРАФ НА GTK+/CAIRO

№12 (март) 2011

граммист

Моей основной операционной системой является Linux, а основным окружением рабочего стола – Gnome. Предыдущий мой урок «Анимированный осциллограф на WinAPI в С++» [1] вызвал интерес наших читателей, поэтому я решил сделать кроссплатформенную версию этого импровизированного осциллографа и полностью переписал программу на GTK+. Данная статья может быть полезна всем, кто только начал или собирается изучать этот «тулкит». Параллельно в уроке рассматривается использование некоторых функций мощной библиотеки двухмерной графики Cairo... Олег Кутков

by Oleg Kutkov

виджетом. Само окно так же является виджетом.

elenbert@gmail.com

Еще одно очень важное понятие GTK+ – сигналы. Их

В примере (см. ресурсы к статье) показана компиляция программы в Linux, пользователи Windows

например,

могут с

скомпилировать

помощью

MinGW,

программу, так

же

скомпилировать программу могут и пользователи

Mac OS X. Вопросы компиляции на платформах, отличных от Linux выходят за рамки данной статьи

желает

и

рассматриваться

всегда

не

будут.

сможет

Те

кто

разобраться

самостоятельно, используя поиск. Но, в начале немного теории GTK+

это

библиотека,

предназначенная для создания приложений с интерфейсом

пользователя,

предоставляет богатый набор функций на все случаи

жизни.

Изначально

разрабатывалась

для

библиотека

программы

Gimp.

Библиотека написана на языке С, но, тем не менее,

сохраняет

ориентированного использования

принципы

объектно-

программирования.

возможностей

Для

библиотеки,

в

Вашей системе должна быть установлена как

сама библиотека, так и заголовочные файлы (dev, devel пакеты) к ней. Все это Вы можете сделать с помощью

менеджера

дистрибутива.

пакетов

Вашего

После установки всех недостающих компонентов можно

подключать

заголовочные

библиотеки в свою программу:

файлы

Основными объектами библиотеки, с которыми придется

называемые

иметь

виджеты.

дело,

являются

Каждый

так

элемент

управления (кнопка, метка, поле ввода) является

АРХИВ

сообщений

в

различные

своего

Windows.

элементы

рода,

Сигналы

аналогами

управления

посылают

(нажата

кнопка, передвинуто окно), а так же функции, при наступлении некоторого события (сработал

таймер). Для обработки сигналов необходимо создавать

обработчик

функции-обработчики. не

будет

Но

функция-

взаимодействовать

с

сигналом сама по себе, для этого их необходимо связать. Это делается с помощью специальных

методов GTK+. Обратите также внимание на явное приведение типов аргументов функций.

В

самом

начале

программы

подключаем

необходимые заголовочные файлы, объявляем необходимые

переменные

и

Комментарии, думаю, все поясняют:

функции.

#include <gtk/gtk.h> #include <stdio.h> #include <math.h> // объявляем функцию, обработчик сигнала закрытия окна static void destroy(GtkWidget*, gpointer); // объявляем функцию, обработчки сигнала перерисовки окна static void draw(GtkWidget*, GdkEventExpose*, gpointer); // объявляем функцию, объявляющую обл.перерисовки недейств-ной static gboolean time_handler(GtkWidget*); // две переменные, смещ-е синусоиды и тек.полож-е точки графика gint count = 0, t = 0; // угол радиан, определяет расстояние между гребнями волн double radianPerx = 2 * 3.14 / 90; const double uAmplit = 900; // амплитуда синусоиды

После объявлений – главная функция main, в

#include <gtk/gtk.h>

нам

считать,

Настало время рассмотрения программы

кроссплатформенная

графическим

можно

самом

объявлении

функции

нет

ничего

необычного. Рассмотрим что твориться в ней. Первым делом мы должны объявить два виджета: наше будущее окно и область, в которой мы будем рисовать

СОДЕРЖАНИЕ

52


ПРО

КРОССПЛАТФОРМЕННЫЙ ОСЦИЛЛОГРАФ НА GTK+/CAIRO

№12 (март) 2011

граммист

Далее мы создаем новую область для рисования и помещаем ее в контейнер окна:

GtkWidget *window, *drawing;

Пока что это просто виджеты, объекты, без

// создаем новую область для рисования

инициализация GTK:

// помещаем ее в контейнер окна

указания

конкретного

типа.

Далее

идет

drawing = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER(window), drawing);

gtk_init(&argc, &argv);

Функции передаются два аргумента – аргументы

функции main. Теперь создаем новое окно и задаем ему необходимые параметры:

И

самое

интересное,

в

главной

функции

помощью функции g_signal_connect:

// связываем сигнал уничтожения окна и функцию-обработчик

// создаем новое, обычное окно

g_signal_connect(GTK_OBJECT(window), "destroy",

window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

G_CALLBACK(destroy), NULL);

// задаем текст заголовка окна

// связываем сигнал перерисовки обл.рис-я и функцию-обработчик

gtk_window_set_title(GTK_WINDOW(window), "Осциллограф");

g_signal_connect(G_OBJECT(drawing), "expose_event",

// запрещаем изменение размеров окна

G_CALLBACK(draw), NULL);

gtk_window_set_resizable(GTK_WINDOW(window), FALSE);

Первым параметром функции является объект,

// задаем размер окна

который мониторится, следующий параметр –

gtk_widget_set_size_request(window, 600, 400); // задаем стартовую позицию - центр экрана gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); // задаем отступы контейнера окна

название сигнала, третий параметр – функцияобработчик

сигнала.

Последний

Тут стоит сказать, что в GTK элемент управления

который мы не используем, в данном случае – передан в функцию-обработчик.

не может существовать произвольно в любом

Оставшиеся

невизуальный

основного цикла программы:

Его

может

приложения

содержать

в

себе

объект-контейнер.

может

выполнять

некий

Окно

функцию

контейнера, но может содержать только один

объект, для расположения в окне множества элементов

контейнера

в

окне

окна

следует

другой

расположить

контейнер.

в

Сам

контейнер может содержать сколь угодно много элементов. Функция

gtk_container_set_border_width

параметр,

дополнительный параметр, который может быть

gtk_container_set_border_width(GTK_CONTAINER(window), 10);

месте.

связывание сигналов с обработчиками делается с

задает

«поля» контейнера окна. Если не вызывать эту функцию – контейнер, содержащий в себе, в

данном случае, поле рисования, будет равен размеру самого окна, без заголовка.

Следующей функцией мы создаем новый таймер,

две

функции

выполняют

отображение всех виджетов на экране и запуск

gtk_widget_show_all(window); gtk_main();

Теперь

рассмотрим

собственно

функции-

обработчики сигналов. Пойдем от простого к сложному.

закрытия окна:

Функция-обработчик

сигнала

static void destroy(GtkWidget* window, gpointer data) { // завершаем главный цикл приложения gtk_main_quit(); }

с интервалом срабатывания – 100 миллисекунд и

Здесь все просто: при вызове этой функции

time_handler:

работы программы. Стоит заметить, что если не

привязываем к таким срабатываниями функцию

g_timeout_add(100, (GSourceFunc) time_handler, (gpointer) window);

АРХИВ

происходит вызов gtk_main_quit() и завершение

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

СОДЕРЖАНИЕ

53


ПРО

граммист

КРОССПЛАТФОРМЕННЫЙ ОСЦИЛЛОГРАФ НА GTK+/CAIRO

№12 (март) 2011

Функция-обработчик сигнала таймера:

точку, из которой будем рисовать;

cairo_line_to (cr, x, y) – задаем точку, куда

static gboolean time_handler(GtkWidget* widget)

следует нарисовать линию;

cairo_stroke (cr) – собственно выполнение

{ // если передан нулевой параметр - возвращаем ложь

рисования в контексте;

cairo_show_text(cr, buf*) – рисование текста в

if (widget->window == NULL) return FALSE; // увеличиваем значение переменной смещения графика

заданном контексте, в точке, предварительно заданной cairo_move_to.

count++; // обнуляем точку положения на графике

Собственно,

t = 0;

Рисование

gtk_widget_queue_draw(widget);

методом,

return TRUE;

Здес так же нет ничего замудренного. Сначала корректность

если

он

переданного

нулевой

функция

завершается, вернув FALSE. Если функция не завершилась – происходит увеление значения переменной,

задающей

смещение

рисуемого

графика. Так же обнуляется вспомогательная

переменная, указывающая текущую точку на графике.

Следующий

вызов

gtk_widget_queue_draw(widget) переданный

самой

знакомым

синусоиды по

ему

объявляет,

виджет,

визуально

{ int ya, xa; // перем-е для хранения X и Y коорд. рис-я char *buf;

// текстовый буфер для хранения текста метки

// создаем область для рисования Cairo cairo_t *cr = gdk_cairo_create(drawarea->window); // рисуем рамку, размером с область рисования cairo_rectangle (cr, event->area.x, event->area.y, event->area.width, event->area.height);

// задачем черный цвет

самое

интересное

функция,

выполняющая отрисовку координатной сетки и Для

рисования

используется

библиотека Cairo, встроенная в Gtk. Мы будем использовать следующие методы:

• • •

gdk_cairo_create(GtkWidget*)

данная

функция создает, на основе переданного ей виджета, новый контекст рисования Cairo;

cairo_rectangle (cr, x1, y1, x2, y2) – данная

функция рисует, в контексте рисования Cairo прямоугольник по заданным координатам;

cairo_set_line_width (cr, 3) – данная функция задает ширину линии, которой производится рисования в указанном контексте; cairo_set_source_rgb(cr, функция

задает

производится

r,

цвет

рисования

g,

b)

линии, в

данная

которой

указанном

контексте, параметры r, g, b определяют, соответственно, •

интенсивность

зеленого и синего цветов;

красного,

cairo_move_to(cr, x, y) – с помощью этой функции мы переходим в новую исходную

АРХИВ

уроку.

GdkEventExpose* event, gpointer data)

И

производится

предыдущему

static void draw(GtkWidget* drawarea,

// задаем толщину линии

синусоиды.

которые

комментариями:

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

функции,

Ниже приведен полный исходный код функции, с

}

параметра,

все

выполняют всю работу в данном обработчике.

// заставляем окно перерисоваться

выполняется

это

cairo_set_line_width (cr, 3); cairo_set_source_rgb (cr, 0, 0, 0); // рисуем линию оси Y cairo_move_to(cr, 40, 0); cairo_line_to (cr, 40, 350); cairo_stroke (cr); // рисуем линию оси X cairo_move_to (cr, 30, 340); cairo_line_to (cr, 600, 340); cairo_stroke (cr); // задаем толщину линии cairo_set_line_width (cr, 0.7); // рисуем риски на оси X, линии сетки и текстовые метки xa = 40; while(xa < 600) { // задаем черный цвет cairo_set_source_rgb (cr, 0, 0, 0); // рисуем риски cairo_move_to (cr, xa, 333); cairo_line_to (cr, xa, 347); cairo_stroke (cr);

СОДЕРЖАНИЕ

54


ПРО

№12 (март) 2011

граммист

КРОССПЛАТФОРМЕННЫЙ ОСЦИЛЛОГРАФ НА GTK+/CAIRO

// преобр-м значение коорд-ы риски в строку

cairo_stroke (cr);

sprintf(buf, "%i", xa-40); // рисуем текст метки в нужной точке cairo_move_to (cr, xa-2, 360); cairo_show_text(cr, buf); // задаем зеленый цвет

}

Компиляция исходного текста в файле <osc.c> выполняется командой:

cairo_set_source_rgb (cr, 0, 0.5, 0);

gcc -Wall -g osc.c -o osc `pkg-config --cflags gtk+-2.0` `pkg-

// рисуем линии сетки

config --libs gtk+-2.0`

cairo_move_to (cr, xa+40, 333); cairo_line_to (cr, xa+40, 0); cairo_stroke (cr); xa += 40; } // рисуем риски на оси Y, линии сетки и текстовые метки ya = 340; while(ya > 0)

Здесь просходит подключение всех необходимых

библиотек, с помощью pkg-config (у вас должен

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

{ // задаем черный цвет cairo_set_source_rgb (cr, 0, 0, 0); cairo_move_to (cr, 33, ya); cairo_line_to (cr, 47, ya); cairo_stroke (cr); // преобр-м значение координаты риски в строку sprintf(buf, "%i", 340-ya); // рисуем текст метки в нужной точке cairo_move_to (cr, 12, ya+2); cairo_show_text(cr, buf); // задаем зеленый цвет cairo_set_source_rgb (cr, 0, 0.5, 0); // рисуем линии сетки cairo_move_to (cr, 47, ya-40); cairo_line_to (cr, 600, ya-40); cairo_stroke (cr); ya -= 40;

осциллографа

Заключение

}

Все

// рисуем синусоиду

архиве с журналом.

// задаем красный цвет и толщину линии cairo_set_source_rgb (cr, 1, 0, 0); cairo_set_line_width (cr, 4); // устанавливаем начальную точку синусоиды cairo_move_to(cr, t+40, ((uAmplit * sin(t * radianPerx + count)) * 0.1 + 160)); // рисуем синусоиду while(t < 600) { cairo_line_to(cr, t+40, ((uAmplit * sin(t * radianPerx + count)) * 0.1 + 160)); t++; }

АРХИВ

Рисунок. Скриншот окна кроссплатформенного

упомянутые

в

статье

исходные

коды

приведены в виде ресурсов непосредственно в Ресурсы • • • •

Тема

и

обсуждение

на

форуме

http://www.programmersforum.ru/showthread.p hp?t=59858 от 10.08.2009 Библиотека

для

создания

графических

интерфейсов GTK+ http://www.gtk.org Практические

советы

по

GTK+

http://subscribe.ru/catalog/comp.soft.prog.gtk Кроссплатформенный

осциллограф

PureBasic от *PB* (см. ресурсы в журнале)

на

СОДЕРЖАНИЕ

55


ПРО

граммист

ЧЕГО ТОЛЬКО НЕ БЫВАЕТ

№12 (март) 2011

Классификация емкостей с водкой в стиле IT •

0.1л – demo version;

0.5л – personal edition;

• • • • • • •

– Какой у тебя IР?

– Да уж не меньше твоего!

0.25л – trial version;

Мальчик спрашивает у отца-програмиста...

0.7л – professional edition;

заходит на западе?

Сын: Пап, а почему солнце всходит на востоке а

ведро водки – extreme edition;

Отец: Сынок, пока работает, не трогай!

море водки – global edition;

Бутыль самогона – home edition;

Меня пугает наш программер, который по 1С:

Рассол с утра – Recovery tool.

тихонько ржЕт...

На посошок – Service pack;

Зарплата... Он что-то там кодит и при этом

mov ПРОграммист, Авторы

xxx: Смотрю фильм про оракула.

mov ПРОграммист, Успех

дельфе писал или на оракле?

mov ПРОграммист, Читатели

xxx: Малой подходит: а дельфийский Оракул на

«Такого вируса у меня еще не было!», – подумал программист, увидев за своим компьютером тещу. Учительница

сынку программера: ты чего в

Сейчас

в

буфете от программеров услышал

прекрасное: «Бэкап – акт проявления трусости…». Три человека подавились кофе.

словосочетании «Дубовая роща», слово «роща»

admin:

правильно напиши, чтобы на всю жизнь запомнил!

шеф: почему?

через «я» написал? А ну-ка на доске 20 раз Через

минуту

@Repeat(роща; 20)

ЮМОР

поворачивается

и

видит:

...говорят,

что

не

корпоратив – плохая примета.

звать

админа

на

admin: интернета весь год не будет...

СОДЕРЖАНИЕ

56


ПРО

граммист

ЧЕГО ТОЛЬКО НЕ БЫВАЕТ

№12 (март) 2011

Корпоративная речевка...

Прогер сидит с девушкой за завтраком...

Люблю я Работу, зарплату люблю!

Она делает, он съедает.

Все больше себя я наэтом ловлю.

Люблю я и Босса, он лучше других! И боссова Босса, и всех остальных.

Люблю я мой Офис, его размещение,

Он: Сделай, пожалуйста, бутерброд! Он: А можно еще?

Она: Не могу. Это была демо-версия заботливой женщины... Лицензию ты сможешь приобрести только после регистрации брака.

А к отпуску чувствую я отвращение.

Какого пола твой компьютер?

Бумажки, в которых, как в Бога, я верую!

Люблю мою мебель, разбитую, серую,

Люблю мое кресло в ячейке без света, И в мире предмета любимее нету!

Люблю я и равных мне по положению,

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

• •

откройте обычный блокнот

наберите сами или скопируйте следующий текст CreateObject("SAPI.SpVoice").Speak"I love you"

сохраните как xyz.vbs запустите,

если

мужской

голос,

мальчик, если женский – девочка

то

у

вас

Украдкой целую, хоть им безразлично...

Столкнулись в нашей базе с наименованием

Я время от времени силюсь понять.

что это человек, который пишет DLL.

И каждую схему опять и опять

ЮМОР

должности «библиотекарь-программист». Решили,

СОДЕРЖАНИЕ

57


ПРО

ЧЕГО ТОЛЬКО НЕ БЫВАЕТ

№12 (март) 2011

граммист

Реклама

компьютерной

фирмы

в

Армавире:

«При покупке у нас 1000 мегабайт оперативной памяти – 24 мегабайта в подарок!»

– Загугли...

чтобы понять значение года, поговорите со

чтобы понять значение месяца, поговорите с

• • •

студентом, не сдавшим сессию;

женщиной, родившей недоношенного ребенка;

чтобы понять значение недели, поговорите с редактором еженедельного обозрения;

чтобы понять значение минуты, поговорите с тем, кто опоздал на поезд;

чтобы понять значение секунды, поговорите с попавшим в автокатастрофу; чтобы

понять

Вас получится программист.

– Слышь? А что такое ДРИФТ???

О значении времени

Если Вам все еще непонятно значение времени, из

значение

миллисекунды,

– А это кто такие? Настоящие програмист пишут мессаги так: "copy con com1".

Приходит

программист к окулисту. Тот его

усаживает напротив таблицы, берет указку: – Читайте!

– БНОПНЯ... Доктор, у вас что-то не то с кодировкой!

поговорите с бегуном, пришедшим вторым в

– X: Ростов, ищецца программер на Яве

о

– X: зп – несколько тыс. уе по собеседованию

олимпийском забеге; наносекунде

хардвейра.

ЮМОР

спросите

проектировщика

– Y: круто...

– Y: вау, а почему на мотоцикле?

СОДЕРЖАНИЕ

58

PROgrammist, №12  

Official editorial layout

Advertisement