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

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

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

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

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

3 B1 .

12  Ok

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

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

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

Пример 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 * ;    \ отличие только в названии

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

Пример 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. Слово работает корректно и вычисляются площадь и периметр соответственно стековой нотации, а выводятся по условию задачи.

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

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

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

S" lib\include\float.f" INCLUDED

S" lib\include\float2.f" INCLUDED

Но можно только вторую строчку.

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

5E-1

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

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

5E-1 F.

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

0.5000000  Ok

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

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

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

    314E-2 F* ;

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

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

1.5700000  Ok

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

Пример 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

Пример 3:

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

    FOVER FOVER        \ A B -> A B A B

                       \ Слово FOVER, дублирует слово под вершиной стека на ее вершину т.е. ( A B -> A B A )

                       \ Повторив его 2 раза получим ( A B -> A B A B )

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

                       \ Площадь вычислен – это просто произведение сторон

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

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

2E-1 3E-1 B3

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.» выведет на экран тоже самое значение.

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

: 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 A*B=S -> A*B=S A B

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

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

2E-1 3E-1 B3

 Ok

F. F.

1.0000000 0.0600000  Ok

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

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

0.0600000 1.0000000  Ok

Результаты по-прежнему верны.

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

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

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

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

Поясним код:

DUP 2DUP ( A -> A A A A )

2DUP, в отличие от DUP дублирует сразу 2 верхних элемента

* * ( A A A A -> A A*A*A=A^3 )

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

SWAP ( A A^3 -> A^3 A )

SWAP просто поменял местами два верхних элемента на стеке

DUP * (A^3 A -> A^3 A*A )

возвели в квадрат число на вершине стека

6 * (A^3 A*A -> A^3 6*A^2)

и умножили его на 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

Пример 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 для обычного стека, которое также оставляет количество его элементов, не считая возвращаемый параметр.

Теперь перепишем Пример 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)

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

;

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

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 раз больше. Надеюсь по комментариям стековой нотации работа слова понятна (она довольно тривиальна).

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

: 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. Результаты теста корректны.

Пример 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/ ;

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) – ИСТИНА

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

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

: Geometric_mean ( 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) 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) 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.

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