Arsen Gonian
Решение задач по программированию на языке Форт (Forth). Версия 3

Введение

С этой книгой вы изучите язык программирования Форт с нуля на конкретных примерах, минимум теории максимум практики. Почему Форт? Философия Форта – минимализм, простота, быстрота выполнения кода. Интерпретатор Форта компилирует все новые, определяемые слова, в словарь, немедленно после ввода, а это значит, что он сохраняет:

интерактивность REPL-среды и вы сразу видите результат своих действий и

быстроту компилятора

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

Синтаксис этого ЯП (Языка Программирования) настолько прост, что его практически нет. Есть только одно главное правило – все Слова (именно так называются операторы языка и определяемые пользователем слова-функции) и числа должны быть разделены между собой хотя бы одним символом пробела и/или табуляции и/или переноса строки (то есть «любой символ пустоты, разделитель»).

Для кого эта книга?

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

Среда программирования SP-Forth – замечательно подойдет для наших целей. Программировать в этом консольном приложении можно начать сразу после быстрого скачивания и установки с официального сайта последней его версии. Он распространяется бесплатно. Работает он в режиме интерпретатора. Это значит, что после запуска системы программирования SP-Forth можно вводить команды или целую программу, после, нажав <Enter>, приложение обработает код и выдаст, предусмотренные результаты. Скачивание и установка SP-Forth не должен вызвать никаких сложностей, а потому этот процесс мы здесь опустим (в любом поисковике наберите «скачать SP-Forth», исполняемый файл запускается двойным кликом левой мыши по нему). После запуска Форта вы увидите:

Рис 1

Исторически сложилось, что основы программирования начинают изучать с первой программы, которая выводит на экран «Hello, World!», что по-русски означает «Здравствуй, Мир!».

Код для ЯП Форт будет следующий:

.( Hello world!)

или

S" Hello, World!" TYPE

После чего на экран будет выведено вышеупомянутое сообщение. Операторы ЯП (язык программирования) Форт <.(> и <S">: первый берет текст, следующий за ним до закрывающей кавычки – " (признак конца выводимой на экран строки) и печатает его на экран, второй создаёт строку адрес которой с ее длиной отправляется на стек. Затем оператор TYPE используя эти числа, адрес и его размер, как параметры также печатает сообщение. Главное «TYPE» должен идти сразу, во избежание ошибок при исполнении, это особенность работы системы. Обращайте внимание на пробелы – в форте они разделяют слова языка. Зачем нужен второй вариант, если первый проще и короче? Затем что он универсальный, так определяются в Форте строки, и они будут полезны в будущем.

Вот как должен выглядеть результат работы:

Рис 2

Третий вариант этой же программы будет выглядеть так:

: Hello_World ." Hello, World!" ;

Hello_World

Рис 3

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

Так мы написали первую полноценную программку. Вместо «Hello_World», вы можете назвать «Start» или как захочется, главное придерживаться простого правила – названия должны быть информативными. Стиль программирования на ЯП Форт ничем не отличается от других языков, будет полезно в дальнейшем почитать об этом.

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

Начало уже положено. Первая программа на Форте для начинающих выглядит довольно просто.

Добавим пару штрихов – комментарии:

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

: Hello_World ( -> ) ." Hello, World!" ; Hello_World

Так как Hello_World оставляет стек неизменным (не трогает его), то до и после стрелки пусто. Комментарий – это содержимое скобок.

Также существует второй способ комментирования кода до конца строки. Это символ – «\».

: Hello_World ( -> ) ." Hello, World!" ; Hello_World \ это программа, выводящая сообщение «Hello, World!»

Программирование без посторонней помощи не составит особой трудности даже, если вы только начинаете. В отличие от языков программирования С или С++, с которых также можно стартовать с нуля, все довольно наглядно и просто. На Форте можно практиковать программирование как онлайн, так и офлайн. Для первого варианта существует скрипт интерпретатор языка Форт на JavaScript (с его помощью можно создавать Форт программы прямо в браузере). Мы же будем ориентироваться на конкретный диалект Форта – SP-Forth. Он существует для всех популярных систем (Windows, Linux).

Обычно вторая задача при обучении программированию – это написание калькулятора. В Форте калькулятор писать не нужно, так как он поддерживает основные операции встроенными средствами, правда несколько в необычной форме, которая называется обратная польская запись или постфиксная. В математике вы привыкли писать формулы в инфиксной форме типа «(1 + 2) * 5 * (4 – 5)», где знак бинарной операции пишется между числами, например, в Лиспе сначала идёт операция, а затем операнд или операнды, а в Форте наоборот, сперва мы отправляем на стек операнды, затем операция выполняет действия над ними, оставляя результат там же. Стек это просто место в памяти, поддерживаемое на аппаратном уровне, следовательно, все операции над ними выполняются очень быстро, где будет хранится промежуточные данные. Так будет выглядеть работа с нашим калькулятором:

Операнд1 Операнд2 Операция. То есть вместо «1 + 2» на Форте мы должны написать «1 2 +».

1 2 +

Ok ( 3 ) \ 1+2=3 в скобках – это содержимое стека

1 2 *

Ok ( 3 2 ) \ 1*2=2 очередной результат на вершине стека

1 2 -

Ok ( 3 2 4294967295(-1) ) \ 1-2=-1 4294967295 – это без знаковый вариант числа -1

1 2 /

Ok ( 3 2 4294967295(-1) 0 ) \ 1/2=0 – это целочисленное деление, потому результат нуль

1 2 MOD

Ok ( 3 2 4294967295(-1) 0 1 ) \ остаток от деления 1/2

1 2 /MOD

Ok ( [7].. 4294967295(-1) 0 1 1 0 ) \ остаток от деления 1/2 и целая часть ½

Рис 4

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

Приведем конкретные примеры на SP-Forth.

: ^2 ( A -> A^2 ) DUP * . ; \ возведение числа в квадрат

: ^3 ( A -> A^3 ) DUP DUP * * . ; \ возведение числа в куб

: ^4 ( A -> A^4 ) DUP * DUP * . ; \ возведение числа в четвертую степень

DUP – это слово которое просто дублирует число на вершине стека.

Возведём 5 в квадрат, для этого наберём на клавиатуре:

5 ^2

25 Ok

Получаем правильный ответ:

рис 5

Копировать код нужно аккуратно, при возведении в квадрат, получаем сообщение об ошибке. При «Копи пасте» куда-то делся символ табуляции между «;» и комментарием, вследствие видим «^ -2003 WORD OR FILE NOT FOUND», из-за нарушения синтаксиса. Наверное, это особенность системы. Почему-то «сглатывается» символ табуляции, а потому вставлен дополнительный символ пробела в коде выше, ниже приведен пример с ошибкой интерпретации:

: ^2 ( A -> A^2 ) DUP * . ;      \ возведение числа в квадрат

рис 6

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

Также, вместо ^2, можно написать **2, в стиле python:

: **2 ( A -> A^2 ) DUP * . ; \ возведение числа в квадрат

5 **2

25 Ok

В итоге получаем тот же ответ, но оформление, стиль изменились. Не забывайте «**2» – это слово, а значит он отделяется пробелом от операнда «5» (до и после, если за ним будут другие инструкции). Это одно из самых главных отличий Форта от других языков.

Для начала этого достаточно. Далее при решении конкретных задач, процесс станет более понятным и осознанным. Приступим.

BEGIN 1-10

Практиковаться программированию на Форте будем с задач из книги М. Э. Абрамян "1000 задач по программированию Часть I Скалярные типы данных, управляющие операторы, процедуры и функции" 2004. Автор пишет, что получить задачник можно по e-mail: mabr@math.rsu.ru или за подробностями обращайтесь к веб ресурсу ptaskbook.com. Текст задач я приводить не буду, дабы исключить плагиат. Думаю, пояснения к коду с описанием слов (функций-программ) должно быть достаточно, в противном случае обращайтесь к первоисточнику за текстом задач.

Пример 1. Итак, начнем, (для простоты вначале мы будем рассматривать все входные параметры как целые числа, далее мы перепишем код для вещественных аргументов, где об этом указано в условии задачи). Вот и решение первой задачи:

: B1 ( A -> P ) 4 * ; \ P=4*A

B – это сокращение от BEGIN, что обозначает первую группу заданий (мы и далее будем использовать такой вид названий в последующих группах заданий), затем слитно пишется номер примера, как часть названия слова. Сразу после имени в скобках пишется комментарий стековой нотации. Так принято в Форте. В данном случае Слово-функция B1 берет один параметр A (четырехбайтовое целое число и оставляет другое того же типа). A – сторона квадрата – вход функции, P – его периметр – возвращаемое функцией значение. Тело – очень короткое, просто умножает число на вершине стека на 4 (не забываем, что в Форте обратная польская запись, сначала идут операнды, затем операция).

Теперь чтобы пользоваться нашим словом, например, чтобы посчитать периметр квадрата со стороной 3, наберем следующий код:

3 B1 .

12 Ok

Рис 7

Точка «.» – это стандартное форт слово, которое печатает число на вершине стека на экран.

Напомним, общий вид определения нового слова в Форте:

: Название-Слова ( стек до выполнение –> после ) Код-Тела-Функции ; \ комментарий

Пример 2. Посчитаем площадь квадрата:

: B2 ( A -> S ) DUP * ; \ S=A^2

Мы просто дублируем число на вершине стека (для чего используем оператор языка Форт – DUP, от английского слова дублировать) и умножаем его на себя. Данный пример можно оформить более красиво для использования в ваших будущих программах и включать как библиотеку или как расширение стандартного словаря с последующим сохранением как новой версии Форта.

: SQR ( A -> A^2 ) DUP * ;      \ A^2 – вычисление квадрата числа, или

: ^2 ( A -> A^2 ) DUP * ;            \ или

: **2 ( A -> A^2 ) DUP * ;      \ отличие только в названии

Рис 8

Результат тот же, так как слова делают одно и тоже. Имя слову можно давать любое – area, square (не только заглавными буквами, так просто исторически сложилось на Форте, что все операторы языка пишутся не строчными буквами), в зависимости от контекста. Какой вариант нравится, тот и можете использовать. Или все сразу, так тоже можно.

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

: B3 ( A B -> S P ) \ ( S=A*B P=2*(A+B) )

2DUP ( A B -> A B A B ) \ Слово 2DUP, дублирует сразу два числа

* ( A B A B -> A B A*B=S ) \ Площадь вычислен – это просто произведение сторон

ROT ROT ( A*B=S A B ) \ оператор ROT вытаскивает 3-ий от вершины параметр на вершину

\ применив его два раза на вершине мы получаем A B и вычисленный под ним Площадь

+ 2* ; \ складываем A и B, и умножив на 2, оператором 2*, получаем периметр

Слово «2*» делает тоже самое что и два слова «2 *», только короче и проще, и главное одно слово выполняется быстрее, чем два.

В итоге на стеке мы получаем Площадь и Периметр. Чтобы напечатать результаты на экран из примеров нужно просто ввести точку с клавиатуры «.» и затем нажать «Enter». Сначала напечатается вершина, т. е. периметр, в данном примере, затем повторив действия площадь. Чтобы изменить порядок печати, наберите слово SWAP, который меняет местами 2 числа на вершине стека ( A B -> B A), т.е., например чтобы напечатать площадь и периметр прямоугольника со сторонами 1 и 2 введём следующее:

1 2 B3 SWAP . .

2 6 Ok

Площадь равна «1*2=2», а периметр равен «2*(1+2) = 6». Слово работает корректно и вычисляются площадь и периметр соответственно стековой нотации, а выводятся по условию задачи.

Перепишем слово «B3» более красиво:

: B3 ( A B -> S P ) \ ( S=A*B P=2*(A+B) )

2DUP       \ A B -> A B A B

*             \ A B A B -> A B A*B=S

ROT ROT       \ A B S -> S A B

+ 2*             \ S A B -> S (A+B)*2=P

;

Удалены лишние комментарии, оставлена только стековая нотация.

Слово «B3» считает площадь и периметр и ничего не выводит на экран. Его можно использовать, как и первый вариант, результат будет тот же:

1 2 B3 SWAP . .

Проверьте этот факт самостоятельно.

рис 9

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

Пример 4. Нужно вычислить длину круга зная его диаметр:

: B4 ( D -> L ) 314 * ; \ L=Pi*D*100

Ответ буде в 100 раз больше для целочисленных данных («Пи» вместо 3,14 взяли 314), таким образом избавились от дробной части. Так себе решение. Напишем слово:

: .2 ( A -> {[A/100],[A%100]} )

100 /MOD . 8 EMIT 44 EMIT .

;

Слово «.2» печатает результат, который в 100 раз больше, как два разных числа «склеенных» вместе (целая часть от «A/100» и его дробная часть – 2 знака десятичной дроби) с запятой. Теперь вычислим длину окружности диаметром 1 – результат точный для «Пи»=3,14:

1 B4 .2

3,14 Ok

Рис 10

Подробно рассматривать код слова «.2» не будем, а перепишем так, чтобы можно было работать с вещественными числами. Для этого в SP-Forth нужно подключить соответствующие библиотеки. Скопируйте и вставьте следующие две строчки:

S" lib\include\float.f" INCLUDED

S" lib\include\float2.f" INCLUDED

Можно только вторую строчку (вызов второй, после первой выводит много сообщений типа «isn't unique (lib\include\float2.f)» – это не ошибки, означает, что слова с таким именем ранее были введены в систему).

Теперь чтобы ввести вещественное число, скажем 0,5, нужно набрать на клавиатуре следующее:

5E-1 или 5e-1

До E – это мантисса (число), после экспонента (степень). Мантисса и экспонента могут быть как положительными (знак не требуется), так и отрицательными (в данном случае степень -1, что значит 10 в минус первой степени).

После ввода, вещественное число размещается на соответствующем ей вещественном стеке. Поэтому мы не видим его после вывода слова Ok в скобках, так как это другой стек для целых чисел. Чтобы его увидеть нужно ввести «F.». Итак, чтобы проверить, что всё работает как надо, введём код:

5E-1 F.

В ответ увидим:

0.5000000 Ok

Слово «F.», аналогично, как и «.» выводит число на экран, только не с целочисленного стека, а с вещественного.

Теперь мы можем переписать пример 4 для вещественных аргументов:

: B4 ( D -> L ) \ L=Pi*D

314E-2 F* ;

Вместо 314E-2 можно набрать 3.14E, результат тот же. Посчитаем длину окружности диаметром 0,5, набрав следующее:

5E-1 B4 F. \ вызываем слово, которое считает длину окружности и «F.» печатает его результат

1.5700000 Ok

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

Пример 1:

: B1 ( A -> P ) 4E F* ; \ P=4*A

Знак «*» заменяется на «F*», четверка вводится как вещественное число (операция «F*», в отличие от «*» производит операцию над вещественными числами на вещественном стеке). Теперь проверим, посчитаем периметр квадрата со стороной 0,5:

5E-1 B1 F.

2.0000000 Ok

Ответ 2 (0,5*4=2) что является правдой.

Данный пример, так же можно преобразовать, написав в стиле:

: B1 ( A -> P ) \ P=4*A

4E F*

;

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

Пример 2:

: B2 ( A -> S ) FDUP F* ; \ S=A^2

Опять DUP превращается в FDUP, умножение как в первом случае. Проверим работу слова. Посчитаем площадь квадрата со стороной 0,5:

5E-1 B2 F.

0.2500000 Ok \ 0,5*0,5 = 0,25

Иллюстрация примеров 1,2 и 4 для вещественных аргументов:

рис 11

Пример 3: Расчет площади и периметра прямоугольника.

: B3 ( A B -> S P ) \ ( S=A*B P=2*(A+B) )

FOVER FOVER \ A B -> A B A B

F*             \ A B A B -> A B A*B=S

FROT FROT       \ A B S -> S A B

F+ 2E F*       \ S A B -> S (A+B)*2=P

;

Первая строка стандартное, уже знакомое начало слова на Форте. Далее:

Вторая строка «FOVER FOVER ( A B -> A B A B )». Слова 2FDUP – нет в словаре, «FOVER» копирует элемент под вершиной вещественного стека на вершину, двойное повторение аналогично целочисленному «2DUP».

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

Четвертая, как и для целочисленного варианта слова (ROT заменяется на FROT).

Пятая сложение двух чисел на вершине вещественного стека и удвоение результата.

Шестая завершение слова, в итоге на вершине стека получаем 2 числа – площадь и периметр.

Проверим работу слова B3 на сторонах прямоугольника 0,2 и 0,3:

2E-1 3E-1 B3 FSWAP F. F.

0.0600000 1.0000000 Ok

Как можете увидеть ниже всё работает верно:

S = 0,2*0,3=0,06

P=2*(0,2+0,3)=2*0,5=1

0,2 и 0,3 можно вводить и в следующем виде: 0.2E и 0.3E. Самостоятельно можете убедиться, что слово «F.» выведет на экран тоже самое значение.

Код «2E-1 3E-1 B3 FSWAP F. F.» можно собрать в отдельное слово, если не приятно работать с таким количеством команд:

: B3. ( S P -> S. P. ) B3 FSWAP F. F. ;

Ok

2E-1 3E-1 B3.

0.0600000 1.0000000 Ok

Согласно логике Форта «B3.» аналогично «.» означает печать результатов слова «B3».

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

Вы можете заметить, что, в примере 3, каждая строка кода сопровождается комментарием, которая поясняет изменения на стеке. Так как все операции и манипуляции, в данном примере, производятся только с вещественным стеком, мы это никак не обозначаем, когда слово будет работать с обоими стеками сразу, вещественный стек будем выделять так: «F: A B A*B=S -> S A B», изменения целочисленного стека дополнительно выделять не будем. Существует еще и стек возвратов. Будьте осторожны, любые неосторожные манипуляции приведут к некорректным результатам, в лучшем случае, в худшем вызовут сообщения об ошибке.

Так же обратите внимание, что комментарии выравниваются так чтобы они выстроились в вертикальную линию, даже те строчки, в которых нет кода, исключительно для удобства чтения (транслятор Форта «проглотил» знаки табуляции, поэтому между кодом и комментарием остался 1 пробел).

рис 12

Пример 5. Здесь вычисляется объем куба и площадь его боковой поверхности. Вначале приведем работу с целочисленным аргументом.

: B5 ( A -> V=A^3 S=6*A^2 )

DUP 2DUP      \ A -> A A A A

* *            \ A A A A -> A A^3

SWAP            \ A A^3 -> A^3 A

DUP * 6 *      \ A^3 A -> A^3 A^2*6 – V=A^3 S=6*A^2

;

Поясним код:

Во второй строчке кода 2DUP, в отличие от DUP дублирует сразу 2 верхних элемента, совместное использование (DUP и 2DUP) оставляют четыре числа A.

В третьей, два умножения «* *» приводят к вычислению куба ( A A A A -> A A*A*A=A^3 ).

В четвертой, SWAP меняет куб числа «A» с ним самим местами ( A A^3 -> A^3 A ).

В пятой, «DUP *» (A^3 A -> A^3 A*A ), возводит в квадрат число на вершине стека, а «6 *» умножает его на шесть. В результате получаем площадь боковой поверхности.

Вызовем написанное слово с параметром 15 (сторона куба):

15 B5

Ok ( 3375 1350 )

3375=15*15*15 и 1350=6*15*15, все верно, слово работает корректно.

То же самое с вещественными числами:

: B5 ( A -> V S )      \ V=A^3 S=6*A^2

FDUP FDUP FDUP      \ A -> A A A A – 2FDUP SP-Forth не понимает

F* F*                  \ A A A A -> A A*A*A=A^3

FSWAP                  \ A A^3 -> A^3 A

FDUP F*            \ A^3 A -> A^3 A*A

6E F*                  \ A^3 A*A -> A^3 6*A^2

;

Проверим написанный код, возьмем куб со стороной 1,5:

15E-1 B5 F. F.

13.500000 3.3750000 Ok      \ 6*1.5^2 = 13.5 1.5^3 = 3.375

Помните, что оператор «F.» печатает то, что лежит на вершине стека. Если вам нужен другой порядок можно применить FSWAP, так при необходимости вывести сперва объем, как в стековой нотации, можно набрать следующее:

15E-1 B5 FSWAP F. F.

3.3750000 13.500000 Ok

рис 13

Самостоятельно напишите слово «B5.» для вещественного варианта и корректный вывод результатов с целочисленного стека в первом случае.

Пример 6. Здесь необходимо вычислить объем и площадь поверхности прямоугольного параллелепипеда, через его ребра.

: B6 ( A B C -> S V )      \ S=2*(A*B+B*C+A*C) V=A*B*C )

DUP 2OVER      \ A B C -> A B C C A B

DUP 2OVER      \ A B C C A B -> A B C C A B B C A

ROT *            \ A B C C A B B C A -> A B C C A B C A*B

ROT ROT * +      \ A B C C A B C A*B -> A B C C A (A*B+B*C)

ROT ROT * +      \ A B C C A A*B+B*C -> A B C (A*B+B*C+C*A)

2*            \ A B C (A*B+B*C+C*A) -> A B C (A*B+B*C+C*A)*2

SWAP 2SWAP      \ A B C (A*B+B*C+C*A)*2 -> (A*B+B*C+C*A)*2 C A B

* * ;            \ (A*B+B*C+C*A)*2 (C*A*B)

Где (A*B+B*C+C*A)*2 – это площадь поверхности, а (C*A*B) – объем.

В данном примере появляется 3 параметра, что не слишком усложняет задачу, и по-прежнему мы не будем использовать переменные в явном виде, манипулируя только с данными на стеке.

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

FDEPTH            \ Это слово возвращает количество элементов в вещественном стеке

Ok ( 0 )            \ 0 элементов

5E-1 FDEPTH      \ введем 1-ое число

Ok ( 0 1 )      \ 1 элемент на вещественном стеке

5E-1 FDEPTH      \ введем 2-ое число

Ok ( 0 1 2 )      \ 2 элемента

5E-1 FDEPTH      \ введем 3-е число

Ok ( 0 1 2 3 )      \ 3

5E-1 FDEPTH      \ введем 4-ое число

Ok ( 0 1 2 3 4 )      \ 4

5E-1 FDEPTH      \ введем 5-ое число

Ok ( [6].. 1 2 3 4 5 )

5E-1 FDEPTH      \ введем 6-ое число

Ok ( [7].. 2 3 4 5 6 )

5E-1 FDEPTH      \ введем 7-ое число

Ok ( [8].. 3 4 5 6 7 )

5E-1 FDEPTH      \ ошибка !!!

Если после ошибки ввести «F.» получим:

infinity Ok

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

рис 14

Или можете набрать «FINIT» для переинициализации.

рис 15

Перепишем Пример 6 для вещественных чисел.

: B6 ( A B C -> S V )      \ S=2*(A*B+B*C+A*C) V=A*B*C )

FOVER FOVER F+      \ A B C -> A B C (B+C)

FROT FROT F*            \ A B C (B+C) -> A (B+C) B*C

FROT                  \ A (B+C) B*C -> (B+C) B*C A

FOVER FOVER F*      \ (B+C) B*C A -> (B+C) B*C A B*C*A

F.                  \ 1-ый результат – объем

FROT F* F+ 2.E F*      \ (B+C) B*C A -> {B*C+A*(B+C)}*2

F.                  \ 2-ой результат S=2*(A*B+B*C+A*C)

;

Теперь можно проверить как работает написанное слово:

рис 16

1E-1 2E-1 3E-1 B6

0.0060000 0.2200000 Ok

Объем прямоугольного параллелепипеда 0,006=0,1*0,2*0,3 и площадь его поверхности 0,22=2*(0,1*0,2+0,2*0,3+0,1*0,3).

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

Пример 7. Зная радиус окружности, посчитаем его длину и площадь.

: B7 ( R -> L S)             \ L=2*Pi*R и S=Pi*R^2

DUP 2* 314 *             \ R -> R R*2*314=L

SWAP             \ R L -> L R

DUP 314 * *             \ L R -> L R*R*314=S

;

Целочисленный вариант принимает целое значение радиуса и выдает результат в 100 раз больше. Надеюсь по комментариям стековой нотации работа слова понятна (она довольно тривиальна).

Результаты теста с ранее написанным словом «.2»:

рис 17

Для печати с тем же порядком как в стековой нотации наберите «1 B7 SWAP .2 .2»

Код для вещественного аргумента:

: B7 ( R -> L S) \ L=2*Pi*R и S=Pi*R^2

FDUP 2E F* 314E-2 F* \ R -> R 2*Pi*R=L

FSWAP             \ R L -> L R

FDUP 314E-2 F* F*       \ L R -> L R*R*3.14=S

;

Вычислим длину окружности и площадь круга радиусом 0,1:

1E-1 B7 F. F.

0.0314000 0.6280000 Ok

0.0314000=0,1*0,1*3,14 и 0.6280000= 2*3,14*0,1. Результаты теста корректны.

рис 18

Пример 8. Простая задачка на вычисление среднего арифметического двух целых чисел:

: B8 ( A B -> [A+B]/2 ) + 2/ ;

1 3 B8

Ok ( 2 )

Мини-код работает правильно (1+3)/2=2. Ниже приведем код для вещественного аргумента:

: B8 ( A B -> [A+B]/2 ) F+ 2E F/ ;

рис 19

1E-1 2E-1 B8 F.

0.1500000 Ok

0.15 = (0.1+0.2)/2 – ИСТИНА

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

: B9 ( A B -> SQRT[A*B] ) F* FSQRT ;

Очень короткий и понятный код, который тестируем ниже:

3E-1 75E-1 B9 F.

1.5000000 Ok \ 1,5 = Корень_Квадратный_из(0,3*7,5) – ИСТИНА

рис 20

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

: MIDDLE_ARITHMETIC ( A B -> [A+B]/2 ) F+ 2E F/ ;

: MIDDLE_GEOMETRIC ( A B -> SQRT[A*B] ) F* FSQRT ;

За грамотные английские названия не ручаюсь.

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

: B10 ( A B -> A^2+B^2 A^2-B^2 A^2*B^2 A^2/B^2 )

SWAP DUP * SWAP DUP *       \ A B ->A^2 B^2

2DUP +                   \ A^2 B^2 -> A^2 B^2 (A^2+B^2)=(+)

ROT ROT 2DUP –             \ A^2 B^2 (+) -> (+) A^2 B^2 (A^2-B^2)=(-)

ROT ROT 2DUP *             \ (+) A^2 B^2 (-) -> (+) (-) A^2 B^2 (A^2*B^2)=(*)

ROT ROT /                   \ (+) (-) A^2 B^2 (*) -> (+) (-) (*) (A^2/B^2 )=(/)

;

Протестируем на числах 4 и 2.

4 2 B10

Ok ( 20 12 64 4 )

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

: B10 ( A B -> A^2+B^2 A^2-B^2 A^2*B^2 A^2/B^2 )

FSWAP FDUP F*             \ A B -> B A^2

FSWAP FDUP F*             \ B A^2 -> A^2 B^2

FOVER FOVER F+             \ A^2 B^2 -> A^2 B^2 (A^2+B^2)=(+)

FROT FROT FOVER FOVER F-       \ A^2 B^2 (+) -> (+) A^2 B^2 (A^2-B^2)=(-)

FROT FROT FOVER FOVER F*       \ (+) A^2 B^2 (-) -> (+) (-) A^2 B^2 (A^2*B^2)=(*)

FROT FROT F/             \ (+) (-) A^2 B^2 (*) -> (+) (-) (*) (A^2/B^2)=(/)

;

Тест примера 10:

1E-1 2E-1 B10 F. F. F. F.

0.2500000 0.0004000 -0.0300000 0.0500000 Ok

Не забываем, что оператор F. Печатает число с вершины стека, поэтому сначала печатается частное, затем произведение, после чего разность и в конце сумма.

0,25 = 0,01/0,04; 0,0004 = 0,01*0,04; -0,03 = 0,01-0,04; 0,05 = 0,01+0,04.

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

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

: SquarePerimeter       ( a -> P=4*a ) 4E F* ;

: SquareArea             ( a -> S=a*a ) FDUP F* ;

: RectanglePerimeter       ( a b -> P=2*[a+b] ) F+ 2E F* ;

: RectangleArea             ( a b -> S=a*b ) F* ;

: CircleLength             ( r -> l=2*Pi*r ) 2E FPI F* F* ;

: CircleArea             ( r -> S=Pi*r*r ) FDUP F* FPI F* ;

: CubeVolume                   ( a -> V=a*a*a ) FDUP FDUP F* F* ;

: CubeLateralSurfaceArea       ( a -> S=6*a*a ) FDUP F* 6E F* ;

: RectangularPrismVolume             ( a b c -> V=a*b*c ) F* F* ;

: RectangularPrismLateralSurfaceArea       ( a b c -> S=2*[a*b+b*c+a*c] ) FOVER FOVER F+ FROT FROT F* FSWAP FROT F* F+ 2E F* ;

: ArithmeticMean       ( a b -> [A+B]/2 ) F+ 2E F/ ;

: GeometricMean       ( a b -> SQRT[A*B] ) F* FSQRT ;

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

рис 21 Результат работы целочисленного и вещественного вариантов примера B10

BEGIN 11-20

Пример 11. Отличается от 10-ого примера незначительными поправками. Просто заменяем квадрат на модуль: код «DUP *» на «ABS» (и «FDUP F*» на «FABS» для вещественных аргументов).

: B11 ( A B -> {|A|+|B|} {|A|-|B|} {|A|*|B|} {|A|/|B|} )

SWAP ABS SWAP ABS \ A B ->|A| |B|

2DUP + \ |A| |B|-> |A| |B| (|A|+|B|)

ROT ROT 2DUP – \ |A| |B| (|A|+|B|) -> (|A|+|B|) |A| |B| (|A|-|B|)

ROT ROT 2DUP * \ (+) |A| |B| (-) -> (+) (-) |A| |B| (|A|*|B|)

ROT ROT / \ (+) (-) |A| |B| (*)-> (+) (-) (*) (|A|/|B|)

;

В случае для вещественных аргументов:

: B11 ( A B -> {|A|+|B|} {|A|-|B|} {|A|*|B|} {|A|/|B|} )

FSWAP FABS                   \ A B -> B |A|

FSWAP FABS                   \ B |A| -> |A| |B|

FOVER FOVER F+             \ |A| |B|-> |A| |B| (|A|+|B|)=(+)

FROT FROT FOVER FOVER F-       \ |A| |B| (+) -> (+) |A| |B| (|A|-|B|)=(-)

FROT FROT FOVER FOVER F*       \ (+) |A| |B| (-) -> (+) (-) |A| |B| (|A|*|B|)=(*)

FROT FROT F/             \ (+) (-) |A| |B| (*)-> (+) (-) (*) (|A|/|B|)=(*)

;

Рис 22

В комментариях (скобках) соответствующие сумма, разность, произведение и частное взяты в фигурные скобки для визуального выделения элементов на стеке. Обычные скобки в данном случае применять нельзя, так как они обозначают комментарий и являются зарезервированными словами Форта и системы программирования SP-Forth в частности.

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

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

: B12 ( A B -> C P ) \ C=Квадратный_Корень(A^2+B^2) P=A+B+C

FOVER FDUP F*       \ A B -> A B A^2

FOVER FDUP F*       \ A B A^2 -> A B A^2 B^2

F+ FSQRT             \ A B A^2 B^2 -> A B Квадратный_Корень(A^2+B^2)=C

FROT FROT F+       \ A B C -> C A+B

FOVER F+             \ C A+B -> C A+B+C=P

;

Проверим на прямоугольном треугольнике с катетами 3 и 5:

3E 4E B12 F. F. \ вызываем нашу подпрограмму и печатаем результат

12.000000 5.0000000 Ok

3^2+4^2=25. Квадратный корень из 25=5. 5+3+4=12– что является истиной. В данном случае специально подобрана Пифагорова тройка, для простоты проверки. Проверим общий случай:

3E 5E B12 F. F.

13.830952 5.8309519 Ok

Можете самостоятельно проверить истинность теста.

Рис 23

Пример 13. Найти площади двух кругов (с общим центром) и кольца между ними. Даны радиусы R1 и R2, причем R1 > R2. Как и ранее сперва напишем слово для целочисленных чисел. Если не совсем понятно почему я не пишу сразу универсальный вариант для вещественных данных, то поясняю: отладка в этом случае наиболее проста для сложных слов и для начинающих, так как все данные на стеке видны сразу после их ввода, то получается легко проверить и понять работу кода пошагово вводя команду за командой. Этого преимущества лишены операторы для работы с вещественными числами. После написания слова с целыми аргументами не сложно перевести его код для работы с вещественными и получить результат того же типа.

: B13 ( R1 R2 -> S1 S2 S3) \ S1=Pi*R1^2 S2= Pi*R2^2 S3=S1-S2

SWAP DUP * 314 *       \ R1 R2 -> R2 (Pi*R1^2)=S1

SWAP DUP * 314 *       \ R2 S1 -> S1 (Pi*R2^2)=S2

2DUP –             \ S1 S2 -> S1 S2 (S1-S2)=S3

;

Запустим наше слово на примере двух кругов с радиусами 25 и 15 соответственно.

25 15 B13

Ok ( 196250 70650 125600 )

Выше приведен вариант кода с целочисленными аргументами, причем все 3 площади больше в 100 раз из-за того, что мы приняли Пи равным 314. При необходимости используйте написанное ранее слово «.2». Далее перепишем пример для случая вещественных аргументов.

: B13 ( R1 R2 -> S1 S2 S3) \ S1=Pi*R1^2 S2= Pi*R2^2 S3=S1-S2

FSWAP FDUP F* 314E-2 F*       \ R1 R2 -> R2 (Pi*R1^2)=S1

FSWAP FDUP F* 314E-2 F*       \ R2 (Pi*R1^2)=S1 -> (Pi*R1^2)=S1 (Pi*R2^2)=S2

FOVER FOVER F-             \ S1 S2 -> S1 S2 (S1-S2)=S3

;

Тестирование примера 13:

25E-1 15E-1 B13 F. F. F.

12.560000 7.0650000 19.625000 Ok

S1 = 19,625 = 3.14*2.5^2; S2 = 7,065 = 3.14*1.5^2; S3=S1-S2=12,56=19,625-7,065.

Тестирование прошло успешно. Не забываем про обратный порядок печати со стека. Написанное слово работает правильно, соответственно стековой нотации. Если вам необходим другой порядок вывода, то можете самостоятельно скорректировать слово, добавив код после вызова «B13» и до вывода «F. F. F.». Ранее аналогичные манипуляции со стеком уже совершались. Также в зависимости от того в каком виде вам нужны результаты вывода слова «B13» в первом случае, проведите соответствующую обработку по выводу на экран или напишите слово «B13.». Чтобы отличать обе версии, добавьте к именам «I и F», чтобы получились имена «B13I» и «B13F» и соответствующие им слова для печати результатов «B13I.» и «B13F.»

рис 24

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

: B14 ( L -> R S ) \ R=L/(2*Pi) S=Pi*R^2

628e-2 F/                   \ L -> R=L/6.28 где 6,28=2*Pi=D

FDUP FDUP F* 314e-2 F*       \ R -> R Pi*R^2

;

рис 25

Посчитаем R и S для L=25,37

2537E-2 B14 F. F.

51.244976 4.0398089 Ok

R=25.37/6.28= 4,0398 и S=3,14* 4,0398^2= 51,244. Тест прошел успешно.

Перепишем код с учетом слова «FPI» (значение «Пи» получаемое системой). Наберите «FPI F.», чтобы узнать точное значение получаемое Форт-словом.

: B14 ( L -> R S )                         \ R=L/(2*Pi) S=Pi*R^2

FPI 2E F* F/                   \ L -> R=L/6.28 где 6,28=2*Pi=D

FDUP FDUP F* FPI F*             \ R -> R Pi*R^2

;

2537E-2 B14 F. F.

51.218997 4.0377609 Ok

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

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

: B15 ( S -> D L )             \ D=Квадратный_Корень(4*S/Pi) L=Pi*D

4E F*             \ S -> 4*S

314E-2 F/             \ 4*S -> 4*S/Pi

FSQRT             \ 4*S/Pi -> Квадратный_Корень(4*S/Pi)=D

FDUP 314E-2 F*       \ D -> D D*Pi=L

;

Посчитаем диаметр и длину круга площадью равной 12,345.

12345E-3 B15 F. F.

12.452036 3.9656166 Ok

Квадратный корень из (12,345*4/3.14) равно 3,965616, а 3,965616*3,14=12,4520, то ест ь ИСТИНА. Пример довольно простой и нет других причин писать код для целочисленного варианта аргументов. В случае необходимости несложно самостоятельно решить эту задачу.

рис 26

Пример 16. Вычислим расстояние между двумя точками на числовой оси, зная координаты.

: B16 ( X1 X2 -> |X1-X2| )

– ABS       \ X1 X2 -> |X1-X2|

;

Для вещественных аргументов.

: B16 ( X1 X2 -> |X1-X2| )

F- FABS       \ X1 X2 -> |X1-X2|

;

рис 27

31E-1 -12E1 B16 F.

123.10000 Ok \ |3.1-(-120)|=123.1

Пример 17. По трем координатам на числовой оси (X1, X2, X3) вычислить следующие расстояния: |x1-x3|, |x2-x3| и их сумму. Сперва для целых чисел.

: B17 ( X1 X2 X3 -> |x1-x3| |x2-x3| {|x1-x3|+|x2-x3|} )

SWAP OVER             \ X1 X2 X3 -> X1 X3 X2 X3

– ABS                   \ X1 X3 X2 X3 -> X1 X3 |X2-X3|

ROT ROT – ABS SWAP \ X1 X3 |X2-X3| -> | X1-X3| |X2-X3|

2DUP +             \ | X1-X3| |X2-X3|-> | X1-X3| |X2-X3| (| X1-X3|+|X2-X3|)

;

Для вещественных.

: B17 ( X1 X2 X3 -> |x1-x3| |x2-x3| {|x1-x3|+|x2-x3|} )

FSWAP FOVER \ X1 X2 X3 -> X1 X3 X2 X3

F- FABS \ X1 X3 X2 X3 -> X1 X3 |X2-X3|

FROT FROT F– FABS FSWAP \ X1 X3 |X2-X3| -> | X1-X3| |X2-X3|

FOVER FOVER F+ \ | X1-X3| |X2-X3|-> | X1-X3| |X2-X3| (| X1-X3|+|X2-X3|)

;

Тест на координатах

–1E1 1E-1 3E2 B17 F. F. F.

609.90000 299.90000 310.00000 Ok

|X1-X3|=|-10-300|=310; |X2-X3|=|0.1-300|=299.9; (|X1-X3|+|X2-X3|)=310+299.9=609.9.

рис 28

Пример 18. Схож с предыдущей задачей. Сумма заменяется произведением.

: B18 ( X1 X2 X3 -> {|x1-x3|*|x2-x3|} )

SWAP OVER             \ X1 X2 X3 -> X1 X3 X2 X3

– ABS                   \ X1 X3 X2 X3 -> X1 X3 |X2-X3|

ROT ROT – ABS *       \ X1 X3 |X2-X3| -> {|x1-x3|*|x2-x3|}

;

–5 2 7 B18

Ok ( 60 )

|-5-7|*|2-7|= 12*5=60

Для вещественных чисел.

: B18 ( X1 X2 X3 -> {|x1-x3|*|x2-x3|} )

FSWAP FOVER             \ X1 X2 X3 -> X1 X3 X2 X3

F- FABS                   \ X1 X3 X2 X3 -> X1 X3 |X2-X3|

FROT FROT F– FABS F*       \ X1 X3 |X2-X3| -> {|x1-x3|*|x2-x3|}

;

–1E1 2E-1 23E1 B18 F.

55152.000 Ok

|-10-230|*|0.2-230|=240*229.8=55152

рис 29

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

: B19 ( X1 Y1 X2 Y2 -> P S ) \ P=2*[A+B] S=A*B

ROT – ABS             \ X1 Y1 X2 Y2 -> X1 X2 |Y2-Y1|

SWAP ROT – ABS       \ X1 X2 |Y2-Y1| -> |Y2-Y1|=A |X2-X1|=B

2DUP + 2*             \ A B -> A B 2*(A+B)=P

ROT ROT *             \ A B P -> P A*B=S

;

1 3 7 8 B19 . .

30 22 Ok

A=|1-7|=6 B=|3-8|=5. P=2*(A+B)=2*(6+5)=22. S=A*B=6*5=30.

Вариант с вещественными аргументами не сильно отличается от целочисленного.

: B19 ( X1 Y1 X2 Y2 -> P S ) \ P=2*[A+B] S=A*B

FROT F- FABS \ X1 Y1 X2 Y2 -> X1 X2 |Y2-Y1|

FSWAP FROT F– FABS \ X1 X2 |Y2-Y1| -> |Y2-Y1|=A |X2-X1|=B

FOVER FOVER F+ 2E F* \ A B -> A B 2*(A+B)=P

FROT FROT F* \ A B P -> P A*B=S

;

11E-1 15E-1 73E-1 62E-1 B19 F. F.

29.140000 21.800000 Ok

A=|1.5-6.2|=4.7; B=|1.1-7.3|=6.2; P=2*(4.7+6.2)= 21,8; S=A*B=4.7*6.2= 29,14.

рис 30

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

: B20 ( X1 Y1 X2 Y2-> R )             \ R= Квадратный_Корень((X2-X1)^2+(Y2-Y1)^2)

FROT F- FDUP F*             \ X1 Y1 X2 Y2-> X1 X2 (Y2-Y1)^2

FSWAP FROT F- FDUP F*       \ X1 X2 (Y2-Y1)^2 -> (Y2-Y1)^2 (X2-X1)^2

F+ FSQRT                   \ (Y2-Y1)^2 (X2-X1)^2 -> R

;

11E-1 15E-1 73E-1 62E-1 B20 F.

7.7801028 Ok

A=|1.5-6.2|=4.7; B=|1.1-7.3|=6.2; R= Квадратный_Корень(A^2+B^2)= Квадратный_Корень(22.09+ 38,44)= 7,7801.

рис 31

Полезные слова из предыдущих десяти примеров:

: Hypotenuse ( A B -> C P )             \ C=Квадратный_Корень(A^2+B^2) P=A+B+C

FDUP F* FSWAP FDUP F* F+ FSQRT ;       \ A B -> SQRT(A^2+B^2)

: RightTriangleP ( A B C -> P )             \ P=A+B+C

F+ F+ ;

: TriangleP ( A B C -> P )                   \ P=A+B+C

F+ F+ ;

Если даны только 2 катета, то можем вычислить периметр так:

3E 4E FOVER FOVER Hypotenuse TriangleP F.

12.000000 Ok

: R1 ( X1 X2 -> R1=|X1-X2| )             \ R1 – расстояние между двумя точками на прямой

F- FABS       \ X1 X2 -> |X1-X2|

;

: R2 ( X1 Y1 X2 Y2-> R )             \ R= SQRT{[X2-X1]^2+[Y2-Y1]^2}

\ R2 – расстояние между двумя точками на плоскости

FROT F- FDUP F*             \ X1 Y1 X2 Y2-> X1 X2 (Y2-Y1)^2

FSWAP FROT F- FDUP F*       \ X1 X2 (Y2-Y1)^2 -> (Y2-Y1)^2 (X2-X1)^2

F+ FSQRT                   \ (Y2-Y1)^2 (X2-X1)^2 -> R

;

BEGIN 21-30

Перед решением очередного примера рассмотрим, как объявляются и используются переменные в SP-Forth. Так как операции с большим количеством данных на стеке становится крайне затруднительным, нам они пригодятся. Для этого воспользуемся зарезервированными словами VARIABLE и FVARIABLE. Первое для целых чисел, второе для вещественных. Если кто-то не знает, что такое переменная, то это просто участок памяти, в которое записывается значение (число для целых переменных или вещественных, текст для строковых или их комбинация для структур), считывается и изменяется. На самом деле любые данные, неважно что: простые переменные, структуры или даже файлы, все они кодируется исключительно числами, причем в двоичном формате (нулями и единицами).

Создадим две переменные

FVARIABLE FVAR      \ FVAR переменная вещественного типа

VARIABLE VAR            \ VAR переменная целого типа

Теперь инициализируем эти переменные, то есть присвоим начальное значение.

1234E-2 FVAR F!

Ok

4552249 VAR !

Ok

Код «1234E-2» нам уже знаком, он просто переносит число «1234E-2» на вершину вещественного стека, FVAR оставляет адрес вещественной переменной с этим именем, и в итоге «F!» – записывает значение по этому адресу. Целочисленное присвоение выглядит по проще, но суть та же. Сначала также число идет в стек. Слово VAR, аналогично своему собрату, оставляет адрес целочисленной переменной на стеке. А записывает «!» – восклицательный знак. Считывает значения переменных соответственно слова – «F@» и «@». Теперь считаем и выведем на экран значения созданных и инициализированных выше переменных.

VAR @ .

4552249 Ok

Знакомый оператор «.» – точка, печатает на экран целочисленное число, а «F.» – вещественное. Вы можете заметить логику Форта по названию операторов, добавив большую букву «F», многие операции становятся применимы к вещественным операндам. Покажем вышесказанное на примере вывода значения вещественной переменной.

FVAR F@ F.

12.340000 Ok

VAR и FVAR – это просто названия, они могут быть любыми другими – это просто удобное обозначение в стиле Форта. Теперь можем приступить к очередной задачке.

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

FVARIABLE FX1

FVARIABLE FY1

FVARIABLE FX2

FVARIABLE FY2

FVARIABLE FX3

FVARIABLE FY3

FVARIABLE FA

FVARIABLE FB

FVARIABLE FC

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

FVARIABLE FX1 FVARIABLE FY1 FVARIABLE FX2 FVARIABLE FY2 FVARIABLE FX3 FVARIABLE FY3

FVARIABLE FA FVARIABLE FB FVARIABLE FC

: B21 ( FX1 FY1 FX2 FY2 FX3 FY3 -> P S )       \ P=(A+B+C)/2 S=SQRT{P*(P-A) *(P-B) *(P-B)}

FY3 F! FX3 F! FY2 F! FX2 F! FY1 F! FX1 F!                   \ FX1 FY1 FX2 FY2 FX3 FY3 ->

FX1 F@ FY1 F@ FX2 F@ FY2 F@ B20 FDUP FA F!       \ A

FX2 F@ FY2 F@ FX3 F@ FY3 F@ B20 FDUP FB F!       \ A B

FX1 F@ FY1 F@ FX3 F@ FY3 F@ B20 FDUP FC F!       \ A B C

F+ F+ FDUP                                     \ A+B+C=P P

2E F/                                           \ P (A+B+C)/2=p

FDUP FA F@ F-                                     \ P p p-A

FOVER FDUP FB F@ F-                               \ P p p-A p p-B

FSWAP FC F@ F-                               \ P p p-A p-B p-C

F* F* F* FSQRT                                     \ P SQRT{p*(p-A)*(p-B)*(p-C)}=S

;

Строка №1 название слова с комментарием что она вычисляет.

Вторая – сохранение координат в соответствующих переменных.

С третьей по пятую – вычисление сторон треугольника с сохранением в переменных A, B, C. Здесь мы не высчитаем расстояния между точками (стороны треугольника), а пользуемся предыдущей задачей, в которой эта проблема решена, просто вызвав ее с параметрами задачи №21. В стековом комментарии написано, то что остается после работы строки (из-за нехватки места), присвоение переменных FA, FB, FC никак не обозначено.

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

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

С восьмой по десятую – вычисление сомножителей в формуле площади.

Одиннадцатая – вычисление самой площади.

Проверим работу слова на координатах: (1,1; 1,1) (6,1; 1,1) (6,1; 4,1). Это прямоугольный треугольник с катетами 3 и 5.

11E-1 11E-1 61E-1 11E-1 61E-1 41E-1 B21 F. F.

7.5000000 13.830952 Ok

рис 32

S=3*5/2=15/2=7.5. Гипотенуза равна sqrt(3^2+5^2)= sqrt(9+25)= sqrt(34)= 5,83095. P= 5,83095+3+5= 13,83095.

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

Если у вас будут проблемы и ошибки введите код в нижеприведенной последовательности.

S" lib\include\float2.f" INCLUDED

: B20 ( X1 Y1 X2 Y2-> R )

FROT F- FDUP F*

FSWAP FROT F- FDUP F*

F+ FSQRT ;

FVARIABLE FX1 FVARIABLE FY1 FVARIABLE FX2 FVARIABLE FY2 FVARIABLE FX3 FVARIABLE FY3

FVARIABLE FA FVARIABLE FB FVARIABLE FC

: B21 ( X1 Y1 X2 Y2 X3 Y3 -> P S ) FY3 F! FX3 F! FY2 F! FX2 F! FY1 F! FX1 F! FX1 F@ FY1 F@ FX2 F@ FY2 F@ B20 FDUP FA F! FX2 F@ FY2 F@ FX3 F@ FY3 F@ B20 FDUP FB F! FX1 F@ FY1 F@ FX3 F@ FY3 F@ B20 FDUP FC F! F+ F+ FDUP S" 2E" >FLOAT DROP F/ FDUP FA F@ F- FOVER FDUP FB F@ F- FSWAP FC F@ F- F* F* F* FSQRT ;

11E-1 11E-1 61E-1 11E-1 61E-1 41E-1 B21 F. F.

Здесь нет комментариев в коде, чтобы избежать ошибок при копировании. Первая строка подключение библиотеки для работы с вещественными числами (тип float2). Далее идет код предыдущего примера, который мы используем (в других языках это называется вызвать функцию). Затем объявляются переменные где мы сохраняем координаты и вычисляемые длины сторон. И, наконец сжатый код слова, производящий вычисления периметра и площади треугольника, и его вызов с заранее подготовленными параметрами.

Пример 22. Довольно классическая задача по обмену содержимым между двумя переменными. В Форте создание переменных в данном случае даже не обязательна.

: B22 ( A B -> B A) SWAP ;

5 4 B22

Ok ( 4 5 )

Для вещественных аргументов.

: B22 ( A B -> B A) FSWAP ;

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

Теперь напишем с переменными:

VARIABLE A

VARIABLE B

: B22 ( -> )       \ обмен содержимым двух переменных A и B

A @ B @ A ! B ! ;

Как проверить работу?

15 A ! 50 B !                   \ Сначала инициализируем переменные. A=15, B=50

Ok

B22                         \ Вызов функции обмена переменных

Ok

A @ . B @ .                   \ Печатаем содержимое переменных после обмена

50 15 Ok

рис 34 с целочисленными переменными

Обмен вещественных переменных.

FVARIABLE FA

FVARIABLE FB

: B22 ( -> )                   \ обмен содержимым для двух переменных A и B

FA F@ FB F@ FA F! FB F! ;

Проверим. Инициализируем переменные, вызовем слово и распечатаем результат. Все по стандартной схеме.

35E-1 FA F!                   \ A=3.5

Ok

77E-1 FB F!                    \ B=7.7

Ok

B22

Ok

FA F@ F. FB F@ F.             \ Печать содержимого переменных после обмена

7.7000000 3.5000000 Ok       \ A=7.7 B=3.5

рис 35 с вещественными переменными

У нас получилось четыре слова с одинаковыми именами, и каждое заменяет предыдущее, о чем нас предупреждает Форт система – SP-Forth сообщением «B22 isn't unique ()». Если вы собираетесь использовать несколько вариантов этих слов одновременно, то надо их называть разными именами. Например, как уже говорилось ранее, добавив «F» в начало слова, которые предназначены для работы с вещественными аргументами становится другим. Разные имена решают проблему и у вас становится больше инструментов в работе. Разумеется, в этом примере, первые два варианта (без использования переменных), в которых тело состоит всего лишь из одного оператора, в отдельные Форт-слова оформлять не обязательно. Просто помните о возможности таких манипуляций на стеке – это одна из сильных сторон Форта.

Пример 23. Обменять значения трех переменных A, B, C по следующей схеме: A -> B -> C -> A.

VARIABLE A VARIABLE B VARIABLE C

Если у вас все примеры в одном файле, то каждый раз объявлять переменные не надо. Добавьте только новую, в данном случае – это «VARIABLE C». Иначе будут сообщения от Форт системы

A isn't unique ()

B isn't unique ()

Это не критично, SP-Forth будет работать дальше, но, во-первых, будут созданы новые переменные с такими же именами, что просто будет бесполезно увеличивать расходы памяти, во-вторых, если вам нужно было предыдущее значение, то они уже не будут доступны по имени.

: B23 ( -> )             \ стековая нотация по прежнему пуста

A @ B @ C @       \ -> A B C

A ! C ! B !       \ A=C C=B B=A

;

1 A ! 2 B ! 3 C ! B23 A @ . B @ . C @ .

3 1 2 Ok

A=3, B=1, C=2.

рис 36

Для вещественного аргумента нужны соответствующие переменные.

FVARIABLE FA FVARIABLE FB FVARIABLE FC

: B23 ( -> )                   \ стековая нотация по прежнему пуста

FA F@ FB F@ FC F@       \ -> A B C

FA F! FC F! FB F!       \ C=A B=C A=B

;

1E FA F! 2E FB F! 3E FC F! B23 FA F@ F. FB F@ F. FC F@ F.

3.0000000 1.0000000 2.0000000 Ok      \ FA=3 FB=1 FC=2

Инициализация и вывод слова «1E FA F! 2E FB F! 3E FC F! B23 FA F@ F. FB F@ F. FC F@ F.» получились не короче самого слова. Оформите в отдельное слово для обоих вариантов самостоятельно.

рис 37

Пример 24. Аналогичен предыдущему с перемещением содержимого по схеме: A -> C-> B -> A.

Переменные используем с предыдущего примера.

: B24 ( -> )             \ стековая нотация пуста

A @ B @ C @       \ -> A B C

B ! A ! C !       \ B=C A=B C=A

;

1 A ! 2 B ! 3 C !             \ инициализация A=1 B=2 C=3

Ok

B24                   \ вызов нашей функции

Ok

A @ . B @ . C @ .       \ A=2 B=3 C=1

2 3 1 Ok

Для вещественного аргумента.

: B24 ( -> )                   \ стековая нотация пуста

FA F@ FB F@ FC F@       \ -> A B C

FB F! FA F! FC F!       \ B=C A=B C=A

;

Проверим на аналогичных данных.

1E-1 FA F! 2E-1 FB F! 3E-1 FC F! B24       \ FA=0.1 FB=0.2 FC=0.3

FA F@ F. FB F@ F. FC F@ F.             \ FA=0.2 FB=0.3 FC=0.1

0.2000000 0.3000000 0.1000000 Ok

рис 38

рис 39

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

: B25 ( X -> F[X] )       \ F(X)=3*X^6-6*x^2-7

F**2             \ X -> X^2

FDUP -6E F*       \ X^2 -> X^2 -6*X^2

–7E F+             \ X^2 {-6*X^2} -> X^2 {-6*X^2-7}

FSWAP             \ X^2 {-6*X^2-7} -> {-6*X^2-7} X^2

3E F**             \ {-6*X^2-7} X^2 -> {-6*X^2-7} {X^2}^3=X^6

3E F* F+       \ {-6*X^2-7}+{3*X^6}

;

1E B25 F.

–10.000000 Ok                  \ F(1)=-10

1E-3 B25 F.

–7.0000060 Ok                  \ F(0.001)=– 7.0000060

рис 40

А вот при аргументе равным нулю выдает ошибку (возведение нуля в степень):

0E B25 F.

EXCEPTION! CODE:C0000090 ADDRESS:0055384E WORD:F**

USER DATA: 007005BC THREAD ID: 00002120 HANDLER: 0019EF98

STACK: (0) 5BF752DB 00328000 76F066DD 0019FFDC 74C30400 00328000 [74C30419]

RETURN STACK:

0019EF84 : 0056DC53 B25

…………………………………………………………

0019EFB4 : 0056BC66 (INIT)

END OF EXCEPTION REPORT

S" 0E >FLOAT DROP B25 F.

^ 0xC0000090L FLOAT_INVALID_OPERATION

рис 41

Но во второй раз у меня выдал другой ответ (разумеется уже некорректный):

0E B25 F.

infinity Ok

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

1E B25 F.

1E B25 F.

^ -2003 WORD OR FILE NOT FOUND

0E B25 F.

0E B25 F.

^ -2003 WORD OR FILE NOT FOUND

1E-3 B25 F.

1E-3 B25 F.

^ -2003 WORD OR FILE NOT FOUND

После замены кода «F**2» на «FDUP F*» и «3E F**» на «FDUP FDUP F* F*» проблема исчезает.

: B25 ( X -> F[X] )             \ F(X)=3*X^6-6*x^2-7

FDUP F*             \ X -> X^2

FDUP -6E F*             \ X^2 -> X^2 -6*X^2

–7E F+                   \ X^2 {-6*X^2} -> X^2 {-6*X^2-7}

FSWAP                   \ X^2 {-6*X^2-7} -> {-6*X^2-7} X^2

FDUP FDUP F* F*       \ {-6*X^2-7} X^2 -> {-6*X^2-7} {X^2}^3=X^6

3E F* F+             \ {-6*X^2-7}+{3*X^6}

;

0E B25 F.

–7.0000000 Ok

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

Пример 26. Брат близнец предыдущего. Разница в формуле. Так же рассмотрим только вещественный аргумент.

: B26 ( X -> F[X] )       \ F[X]=4*{X-3}^6-7*{X-3}^3+2

3E F-             \ X -> X-3

3E F**             \ X-3 -> (X-3)^3

FDUP -7E F*       \ (X-3)^3 -> (X-3)^3 {-7*(X-3)^3}

FSWAP F**2       \ (X-3)^3 {-7*(X-3)^3} -> {-7*(X-3)^3} [(X-3)^3]^2

4E F*             \ {-7*(X-3)^3} (X-3)^6 -> {-7*(X-3)^3} 4*(X-3)^6

2E F+ F+       \ {-7*(X-3)^3} 4*(X-3)^6 -> {-7*(X-3)^3}+4*(X-3)^6+2

;

4E B26 F.

–1.0000000 Ok

3E B26 F.

2.0000000 Ok

Здесь ошибки пи возведении нуля в степень не выдал, но при вводе кода «0E 3E F** F.» по-прежнему выдает ошибку. Будьте осторожны с возведением в степень, это опасная операция, вызывающая много ошибок.

Не привожу скана с тестом примера 26, разберите самостоятельно.

Пример 27. В Форте дополнительная переменная здесь и не понадобится. Для тех, кто любит все выполнять строго по инструкции – задание самостоятельно переписать код.

: B27 ( A -> A^2 A^4 A^8)

DUP *             \ A -> A^2

DUP DUP *       \ A^2 -> A^2 A^4

DUP DUP *       \ A^2 A^4 -> A^2 A^4 A^8

;

Примеры работы слова:

2 B27

Ok ( 4 16 256 )

3 B27

Ok ( 9 81 6561 )

Без комментариев.

рис 42

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

: B27 ( A -> A^2 A^4 A^8 )

FDUP F*       \ A -> A^2

FDUP FDUP F*       \ A^2 -> A^2 A^4

FDUP FDUP F*       \ A^2 A^4 -> A^2 A^4 A^8

;

2E B27 F. F. F.

256.00000 16.000000 4.0000000 Ok      \ Порядок печати обратный

3E B27 F. F. F.

6561.0000 81.000000 9.0000000 Ok

рис 43

Пример 28. Похож на предыдущую задачу и чуть посложнее.

: B28 ( A -> A^2 A^3 A^5 A^10 A^15 )

DUP DUP *       \ A -> A A^2

SWAP OVER *       \ A A^2 -> A^2 A^3

OVER OVER *       \ A^2 A^3 -> A^2 A^3 A^5

DUP DUP *       \ A^2 A^3 A^5 -> A^2 A^3 A^5 A^10

OVER OVER *       \ A^2 A^3 A^5 A^10 -> A^2 A^3 A^5 A^10 A^15

;

2 B28

Ok ( 4 8 32 1024 32768 )

3 B28

Ok ( 9 27 243 59049 14348907 )

Не забывайте, что степенная функция растет очень быстро и при большом основании быстро произойдет переполнение, в результате ответ будет некорректным. Так, например, при A=10, уже 10-ая степень вычисляется неправильно.

10 B28

Ok ( 100 1000 100000 1410065408 2764472320(-1530494976) )

Для вещественного аргумента.

: B28 ( A -> A^2 A^3 A^5 A^10 A^15)

FDUP FDUP F*             \ A -> A A^2

FSWAP FOVER F*       \ A A^2 -> A^2 A^3

FOVER FOVER F*       \ A^2 A^3 -> A^2 A^3 A^5

FDUP FDUP F*             \ A^2 A^3 A^5 -> A^2 A^3 A^5 A^10

FOVER FOVER F*       \ A^2 A^3 A^5 A^10 -> A^2 A^3 A^5 A^10 A^15

;

рис 44

рис 45

Тесты с вещественным параметром.

2E B28 F. F. F. F. F.

32768.000 1024.0000 32.000000 8.0000000 4.0000000 Ok      \ опять обратный порядок при печати

3E B28 F. F. F. F. F.

14348907. 59049.000 243.00000 27.000000 9.0000000 Ok

Очевидно слова работает корректно.

Пример 29. Перевести градусы в радианы.

Целочисленный вариант будет в 100 раз больше, поэтому его код не приводится. При необходимости можно его получить из кода для вещественного аргумента. Также можете воспользоваться словом «.2»

: B29 ( A{DEG} -> X{RAD} )       \ X=A*Pi/180

314E-2 F*             \ A -> 3.14*A

180E F/                   \ 3.14*A -> 3.14*A/180

;

Нижеприведенные тесты корректны.

90E B29 F.

1.5700000 Ok

360E B29 F.

6.2800000 Ok

Для повышения точности, самостоятельно перепишите слово «B29» используя слово «FPI».

рис 46

Пример 30. Обратная к предыдущему задача перевода из радиан в градусы.

: B30 ( A{RAD} -> X{DEG} )       \ 180*A/Pi

180E F*                   \ A -> 180*A

314E-2 F/             \ 180*A -> 180*A/Pi

;

Тесты слова B30.

314E-2 B30 F.

180.00000 Ok

628E-2 B30 F.

360.00000 Ok

рис 47

Как и в предыдущем случае, перепишите для увеличения точности.

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

Например, RAD>DEG и DEG>RAD (знак «>» означает перевести, перенести в зависимости от контекста использования).

Пример содержания такого файла:

\ Подключение библиотеки для работы с вещественными числами

S" lib\include\float2.f" INCLUDED

VARIABLE A VARIABLE B VARIABLE C      \ Часто используемые переменные

FVARIABLE FA FVARIABLE FB FVARIABLE FC

: DEG>RAD ( A{DEG} -> X{RAD} )            \ X=A*Pi/180 – градусы в радианы

314E-2 F*      \ A -> 3.14*A

180E F/            \ 3.14*A -> 3.14*A/180

;

: RAD>DEG ( A{RAD} -> X{DEG} )            \ 180*A/Pi – радианы в градусы

180E F*            \ A -> 180*A

314E-2 F/      \ 180*A -> 180*A/Pi

;

Назовите файл как вам нравится, но обязательно с расширением «.F», тогда его можно будет запустить и он откроется в SP-Forth, при условии, что он у вас установлен. Тогда вам не придется каждый раз выполнять рутинные задачи, которые вы включите в этот файл.

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

Рассмотрим пример №28.

2E B28 F. F. F. F. F.

Здесь «B28 F. F. F. F. F.» заменяем на слово

: B28. ( A^2 A^3 A^5 A^10 A^15 -> ) B28 F. F. F. F. F. ;

Такой вариант предпочтительный не только из-за упрощения, но и исходя из того, что вы со временем забудете, как работает слово, а разбираться в работе слове каждый раз не разумно, да и зачем перегружать мозг бесполезными мелочами? Оператор «.» – точка в Форте всегда связана с печатью на экран, поэтому «Name.» – логично означает печать результатов слова «Name». Только не забудьте написать соответствующее слово перед ее выполнением, автоматически слова в Форте не пишутся. Так же можно включать словесное описание результатов, например,

: B28. ( A^2 A^3 A^5 A^10 A^15 -> ) B28

.” A^15= “ F. CR             \ CR – это оператор перевода на новую строку

.” A^10= “ F. CR

.” A^5= “ F. CR

.” A^3= “ F. CR

.” A^2= “ F. CR

;

Окончательно вызов слова B28 будет выглядеть, с учетом упрощения, так:

2E B28.

Так мы разделяем алгоритм работы слова с его примитивным интерфейсом вывода результатов на экран. В таком случае при необходимости что-то доработать, одно не помешает другому и, разумеется, код становится более лаконичным и понятным. Оригинал «B28» можно использовать в других словах, библиотеках, и это никак не помешает по-прежнему пользоваться примером 28. Так же при решении сложной задачи, можно писать аналогичные промежуточные слова «Name.» для отладки и контроля промежуточного состояния сложной системы слов, когда не понятно на каком этапе возникает ошибка.

рис 48

BEGIN 31-40

Пример 31. Перевести температуру в Фаренгейтах в градусы Цельсия. Сперва для целых значений температуры.

: B31 ( TF-> TC )             \ TC=(TF-32)*5/9

32 – 5 * 9 /

;

32 B31             \ 32 град Фаренгейта = 0 град Цельсия, 32-32=0, 0*5/9=0

Ok ( 0 )

35 B31             \ 35 град Фаренгейта = 1 град Цельсия (35-32)=3, 3*5=15, 15/9=1 – целая часть

Ok ( 0 1 )

40 B31             \ 40 град Фаренгейта = 4 град Цельсия (40-32)=8, 8*5=40, 40/9=4 – целая часть

Ok ( 0 1 4 )

рис 49

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

: B31 ( TF-> TC )            \ TC=(TF-32)*5/9

32E F-

5E F* 9E F/

;

32E B31 F.       \ как и в первом варианте, только результат в вещественном формате

0.0000000 Ok

321E-1 B31 F.

0.0555555 Ok

рис 50

Пример 32. Обратная к предыдущему примеру задача. Перевести температуру из Цельсия в Фаренгейты.

: B32 ( TC -> TF )       \ TF= TC*9/5+32

9 * 5 / 32 + ;

0 B32                   \ 0 град Цельсия = 32 град Фаренгейта

Ok ( 32 )

–18 B32

Ok ( 32 0 )             \ -18 град Цельсия = 0 град Фаренгейта

рис 51

Для вещественного аргумента.

: B32 ( TC -> TF )       \ TF= TC*9/5+32

9E F* 5E F/ 32E F+ ;

0E B32 F.              \ 0 град Цельсия = 32 град Фаренгейта

32.000000 Ok

–18E B32 F.             \ -18 град Цельсия = -0,4 град Фаренгейта

–0.4000000 Ok

рис 52

Ноль градусов по Цельсию по-прежнему 32 градусов Фаренгейта, единственное отличие – результат вещественное число, а вот при T=-18 остаток уже не отбрасывается, как в первом случае, и ответ получается точный.

Пример 33. Детская задача. Цена 1 кг конфет обозначим через C1, а Y кг соответственно CY, тогда легко вычислить C1=A/X и CY=C1*Y, а дано X кг за A рублей и количество Y кг, цену которого нужно вычислить.

: B33 ( X A Y -> C1 CY )             \ C1=A/X CY=C1*Y

SWAP ROT /             \ X A Y -> Y A/X=C1

SWAP OVER *             \ Y C1 -> C1 Y*C1= CY

;

3 9 10 B33                   \ 3 кг стоит 9 р, т. е. 3р за 1 кг, а 10 кг будут стоить 3*10=30 р

Ok ( 3 30 )

Вы можете самостоятельно потренироваться на современных ценах различных товаров.

А теперь для дробных цен.

: B33 ( X A Y -> C1 CY )             \ C1=A/X CY=C1*Y

FSWAP FROT F/             \ X A Y -> Y A/X=C1

FSWAP FOVER F*       \ Y C1 -> C1 Y*C1= CY

;

3E 9E 10E B33 F. F.

30.000000 3.0000000 Ok

рис 53

Проверили на тех же данных. И снова не забываем об обратном порядке при печати со стека, не важно с какого (целочисленного или вещественного). Чтобы в начале вывести цену за 1 кг, можно использовать слово FSWAP перед «F.» или переписать строчку

FSWAP FOVER F*      \ Y C1 -> C1 Y*C1= CY

таким образом

FDUP F. F*            \ Y C1 -> Y*C1= CY

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

Пример 34. Продолжение детских задачек.

: B34 ( X A Y B -> CX CY CX/CY )             \ CX=A/X CY=B/Y CX/CY

SWAP /                         \ X A Y B -> X A B/Y=CY

SWAP ROT /                   \ X A CY -> CY A/X=CX

SWAP 2DUP /                   \ CY CX -> CX CY CX/CY

;

5 30 5 15 B34

Ok ( 6 3 2 )

Цена шоколадных конфет равна 30/5= 6 р/кг, ирисок 15/5=3 р/кг, и соответственно шоколадные конфеты дороже ирисок в 6/3=2 раза. Перепишем для вещественных аргументов, чтобы не терять точность для «неудобных данных».

: B34 ( X A Y B -> CX CY CX/CY )             \ CX=A/X CY=B/Y CX/CY

FSWAP F/                   \ X A Y B -> X A B/Y=CY

FSWAP FROT F/                   \ X A CY -> CY A/X=CX

FSWAP FOVER FOVER F/       \ CY CX -> CX CY CX/CY

;

5E 30E 5E 15E B34 F. F. F.

2.0000000 3.0000000 6.0000000 Ok

рис 54

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

Пример 35. Детская задачка на движение лодки в стоячей и движущейся воде.

VARIABLE T2

: B35 ( V U T1 T2 -> S )             \ S=S1+S2, S1=V*T1, S2=(V-U)*T2

T2 !                   \ V U T1 T2 -> V U T1

ROT DUP ROT *             \ V U T1 -> U V V*T1=S1

SWAP ROT –             \ U V S1 -> S1 V-U

T2 @ *                   \ S1 V-U -> S1 [V-U]*T2=S2

+                   \ S=S1+S1

;

Манипуляции на стеке неоправданно усложняются с четырьмя и более элементами, поэтому мы сохраним значение T2 в одноименной переменной. Сперва мы вычисляем путь S1 в стоячей воде, затем считывая время T2 из переменной – путь S2 и соответственно ему скорость. Ответ есть сумма двух путей. Проверим написанное слово.

10 5 1 1 B35

Ok ( 15 )

1 час в стоячей воде со скоростью 10 – это путь равный 10*1=10 и такое же время со скоростью 10-5=5. В итоге общий путь 10+5=15.

рис 55

С вещественными данными слово будет иметь вид:

FVARIABLE FT2

: B35 ( V U T1 T2 -> S )             \ S=S1+S2, S1=V*T1, S2=(V-U)*T2

FT2 F!                   \ V U T1 T2 -> V U T1

FROT FDUP FROT F*       \ V U T1 -> U V V*T1=S1

FSWAP FROT F-             \ U V S1 -> S1 V-U

FT2 F@ F*             \ S1 V-U -> S1 [V-U]*T2=S2

F+                   \ S=S1+S1

;

10E 5E 1E 1E B35 F.

15.000000 Ok

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

рис 56

Пример 36. Без комментариев, сразу приведем решение.

: B36 ( V1 V2 S0 T -> S )             \ S=S0+(V1+V2)*T

2SWAP + * +             \ V1 V2 S0 T -> S0+T*(V1+V2)

;

3 5 100 2 B36

Ok ( 116 )

\ 3+5=8, 8*2=16, 16+100=116.

FVARIABLE FS0

: B36 ( V1 V2 S0 T -> S )             \ S=S0+(V1+V2)*T

FSWAP FS0 F!             \ V1 V2 S0 T -> V1 V2 T

FROT FROT F+ F*       \ V1 V2 T -> T*(V1+V2)

FS0 F@ F+             \ T*(V1+V2)+S0

;

3E 5E 100E 2E B36 F.

116.00000 Ok

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

рис 57

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

: B37 ( V1 V2 S0 T -> S )             \ S=|S0-T*(V1+V2)|

2SWAP + * – ABS       \ V1 V2 S0 T -> |S0-T*(V1+V2)|

;

3 5 100 2 B37

Ok ( 84 )

\ 3+5=8, 8*2=16, |100-16|=84. Проверим как работает модуль, для этого положим S0=10.

3 5 10 2 B37

Ok ( 6 )

\ 3+5=8, 8*2=16, |10-16|=6. Все верно, код работает корректно.

FVARIABLE FS0

: B37 ( V1 V2 S0 T -> S )             \ S=|T*(V1+V2)-S0|

FSWAP FS0 F!             \ V1 V2 S0 T -> V1 V2 T

FROT FROT F+ F*       \ V1 V2 T -> T*(V1+V2)

FS0 F@ F- FABS             \ T*(V1+V2) –> |T*(V1+V2)-S0|

;

3E 5E 100E 2E B37 F.

84.000000 Ok

3E 5E 10E 2E B37 F.

6.0000000 Ok

рис 58

Для вещественного аргумента мы получили тот же ответы в соответствующем формате.

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

: B38 ( A B -> X )                   \ X=-B/A

–1E F* FSWAP F/             \ A B -> -B/A

;

10E 3E B38 F.

–0.3000000 Ok

Код «-1E F*» можно заменить на «FNEGATE», который лучше, но для начала может быть менее наглядным.

: B38 ( A B -> X )                   \ X=-B/A

FNEGATE FSWAP F/ ;             \ A B -> -B/A

рис 59

Пример 39. Решение квадратного уравнения. Обратите внимание, что в названии вещественной переменной A опущен префиксы «F», так как в этом слове используется одна переменная одного типа применено такое упрощение. Если вас это пугает, то вы легко можете добавить его. Если все слова книги вы добавляете в один файл, то эта проблема будет актуальной, так же вы можете в названиях слов добавлять суффикс I (Integer), для функций с целочисленными аргументами и F, для вещественных, иначе SP-Forth, будет выводить сообщения «B39 isn't unique ()», что не является ошибкой, но предупреждением, о том, что при вызове слова будет вызвано определенное последней слово. Могут возникнуть скрытые ошибки логики работы. Так если вы определили целочисленное слово, затем вещественное и после вызываете попеременно слова с разными аргументами, то как было сказано выше – будет вызвана исключительно последняя реализация, что неизбежно приведет к ошибке, связанное с тем что слова будут работать с аргументами в разных стеках, в итоге, будет либо нехватка аргументов, либо порча других данных не предназначенных для вашего слова. Изменив названия при помощи суффиксов, вы избавитесь от этих проблем.

FVARIABLE A

: B39 ( A B C -> X1 X2)                   \ X1,X2=(-B+-SQRT(D))/2*A

FROT FDUP A F!       F*             \ A B C -> B C*A

FOVER FDUP F*                   \ B C*A -> B C*A B^2

FSWAP 4E F* F- FSQRT             \ B C*A B^2 -> B SQRT{B^2-4*C*A=D}

FSWAP -1E F* FSWAP             \ B D^0.5 -> -B D^0.5

FOVER FOVER F+ A F@             \ -B D^0.5 -> -B D^0.5 –B+D^0.5 A

2E F* F/                   \ -B D^0.5 –B+D^0.5 A -> -B D^0.5 [–B+D^0.5]/[A*2]=X1

FROT FROT F- A F@             \ -B D^0.5 X1 -> X1 –B-D^0.5 A

2E F* F/                   \ X1 –B-D^0.5 A -> X1 [–B-D^0.5]/[A*2]=X2

FOVER FOVER F<             \ X1 X2 –> X1 X2 X1<X2?

IF FSWAP THEN

;

1E 4E 1E B39 F. F.

–3.7320508 -0.2679491 Ok

X^2+4*X+1=0, D=4^2-4*1*1=16-4=12. X1,X2=(-4+-12^0.5)/(2*1)=-2+-SQRT(3)

X1=-2- 1,732= -3,732, X2=-2+1,732=-0,268.

А вот случай, когда D=0

1E 2E 1E B39 F. F.

–1.0000000 -1.0000000 Ok

Небольшая оптимизация кода (на две строчки короче предыдущего слова):

FVARIABLE A

: B39 ( A B C -> X1 X2)                   \ X1,X2=(-B+-SQRT(D))/2*A

FROT FDUP 2E F* A F!             \ A B C -> B C A

F* FOVER FDUP F*             \ B C A -> B C*A B^2

FSWAP 4E F* F- FSQRT             \ B C*A B^2 -> B SQRT{B^2-4*C*A=D}

FSWAP FNEGATE FSWAP       \ B D^0.5 -> -B D^0.5

FOVER FOVER F+ A F@ F/       \ -B D^0.5 -> -B D^0.5 [–B+D^0.5]/(2*A)=X1

FROT FROT F- A F@ F/             \ -B D^0.5 X1 -> X1 [–B-D^0.5]/(2*A)=X2

FOVER FOVER F<             \ X1 X2 –> X1 X2 X1<X2?

IF FSWAP THEN                   \ X2 X1 || X1 X2

;

Результат такой же, но «2*A» два раза не вычисляется, а сразу сохраняется в переменной «A», «-1E F*» заменен на «FNEGATE» – изменение знака «B» на противоположенный.

Если манипуляции со стеком вам не понятны, то мы можем переписать последнее слово с использованием трех переменных A, B, C, по классической схеме.

FVARIABLE A FVARIABLE B FVARIABLE C

: B39 ( A B C -> X1 X2)                         \ X1,X2=(-B+-SQRT(D))/2*A, X1,X2

C F! B F! A F!                         \ A B C ->

B F@ FDUP -1E F* FSWAP FDUP F*       \ -> -B B^2

A F@ C F@ F* -4E F* F+ FSQRT             \ -B B^2 -> -B {B^2+A*C*(-4)}^0.5=D^0.5

FOVER FOVER F+                   \ -B D^0.5 -> -B D^0.5 -B+D^0.5

A F@ 2E F* F/                         \ -B D^0.5 -B+D^0.5-> -B D^0.5 [-B+D^0.5]/[A*2]=X1

FROT FROT F-                         \ -B D^0.5 X1 -> X1 -B-D^0.5

A F@ 2E F* F/                         \ X1 [-B-D^0.5]/[A*2]=X2

FOVER FOVER F<                   \ X1 X2 X1<X2?

IF FSWAP THEN

;

1E 4E 1E B39 F. F.

–3.7320508 -0.2679491 Ok

Ответ как в предыдущих вариантах. Какой проще и понятнее решайте сами.

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

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

FVARIABLE A1 FVARIABLE B1 FVARIABLE C1

FVARIABLE A2 FVARIABLE B2 FVARIABLE C2

: B40 ( A1 B1 C1 A2 B2 C2 -> X Y ) \ X=(C1*B2-C2*B1)/D, Y=(A1*C2-A2*C1)/D, D=A1*B2-A2*B1

C2 F! B2 F! A2 F! C1 F! B1 F! A1 F!             \ A1 B1 C1 A2 B2 C2 ->

A1 F@ B2 F@ F* A2 F@ B1 F@ F* F-             \ -> A1*B2-A2*B1=D

FDUP .” D=“ FDUP F. CR                         \ D -> D D

C1 F@ B2 F@ F* C2 F@ B1 F@ F* F- FSWAP F/       \ D D -> D (C1*B2-C2*B1)/D=X

A1 F@ C2 F@ F* A2 F@ C1 F@ F* F- FROT F/       \ D X -> X (A1*C2-A2*C1)/D=Y

;

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

Первая строка – стандартное описание логики работы слова.

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

Вычисление D.

Дублируем D, так как он нам понадобится два раза, также мы его копируем и печатаем для проверки промежуточных вычислений. После отладки код «.” D=“ FDUP F. CR» можно удалять.

Вычисление X

Вычисление Y

Проверим корректность работы слова на следующих данных

3x+5y=7      (A1=3      B1=5      C1=7)

6x+y=4            (A2=6      B2=1      C2=4)

3E 5E 7E 6E 1E 4E B40 F. F.

D= -27.000000

1.1111111 0.4814814 Ok

рис 61

D=3*1-6*5=3-30=-27, совпадает с результатом выданном словом. X=(7*1-4*5)/(-27)=13/27=(0,481). Y=(3*4-6*7)/(-27)=(12-42)/(-27)=30/27=1,(1). Видим, что слово работает корректно.

Integer 1-30

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

Пример 1. Перевести см в м без округления. Здесь вы увидите всю красоту Форта.

Rm=Rcm/100 – целочисленно

: I1 ( Rcm -> Rm ) 100 / ;

503 I1

Ok ( 5 )

Что означает в 503 см содержится 5 полных метров.

рис 62

Пример 2. Отличается от предыдущего заменой 100 на 1000.

Mt= Mkg/1000

: I2 ( Mkg -> Mt ) 1000 / ;

12683 I2

Ok ( 12 )

рис 63

В 12683 кг 12 полных тонн.

Пример 3. Так же отличается от предыдущего константой. Теперь 1000 меняем на 1024.

DkB= DB/1024

: I3 ( DB -> DkB ) 1024 / ;

2050 I3

Ok ( 2 )

2050 B – это 2 полных kB.

рис 64

Пример 4. Так же простейшая задачка на целочисленное деление.

Для определенности A>B, результат равен A/B.

: I4 ( A B -> A/B ) / ;

15 4 I4

Ok ( 3 )

рис 65

В отрезке длиной 15 размещается 3 целых отрезка длиной 4.

Пример 5. Отличается от предыдущего примера заменой целочисленного деления на взятие остатка от деления.

: I5 ( A B -> остаток{A/B} ) MOD ;

15 4 I5

Ok ( 3 )

15/4 – остаток равен 3 – все верно, результат теста корректный.

рис 66

Примеры 1-5 настолько просты, что даже нет необходимости создавать соответствующие слова. Можно просто ввести число-операнд затем тело слова. Результат получите в скобках на стеке. Чтобы распечатать его и не засорять стек нажмите «.» и «Enter». Перепишем эти примеры для наглядности.

«503 100 / .»

«12683 1000 / .»

«2050 1024 / .»

«15 4 / .»

«15 4 MOD .»

рис 67

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

Пример 6. Вывести число десятков и единиц двузначного числа. Для этого используем операцию /MOD, которая одновременно вычисляет и целую часть, и остаток от деления.

: I6 ( AB -> A B ) 10 /MOD SWAP ;

45 I6

Ok ( 4 5 )

Чтобы вывести в таком же порядке как на стеке слегка изменим код (помним, что сначала напечатается вершина стека, поэтому перед выводом используем команду SWAP)

45 I6 SWAP . .

4 5 Ok

Теоретически, последним штрихом, будет оформление вывода в отдельное слово, как говорилось ранее в предыдущей группе задач, по схеме:

: I6. I6 SWAP . . ;

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

45 I6.

4 5 Ok

рис 68

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

Разумеется, эти два слова можно объединить в одно по схеме:

: I6. 10 /MOD SWAP SWAP . . ;

: I6. 10 /MOD . . ;

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

Пример 7. Вычислить сумму и произведение цифр двузначного числа. Дальнейшее развитие предыдущего примера.

: I7 ( AB -> A+B A*B )

10 /MOD       \ AB -> A B

2DUP +             \ A B -> A B A+B

ROT ROT *       \ A B A+B -> A+B A*B

;

45 I7

Ok ( 9 20 )

Сумма цифр числа 45 равна 4+5=9, а произведение 4*5=20, тест корректен.

Конец ознакомительного фрагмента.

Текст предоставлен ООО «Литрес».

Прочитайте эту книгу целиком, купив полную легальную версию на Литрес.

Безопасно оплатить книгу можно банковской картой Visa, MasterCard, Maestro, со счета мобильного телефона, с платежного терминала, в салоне МТС или Связной, через PayPal, WebMoney, Яндекс.Деньги, QIWI Кошелек, бонусными картами или другим удобным Вам способом.