PocketMK – эмулятор семейства популярнейших советских программируемых калькуляторов БЗ-34...МК-61. Основное отличие эмулятора состоит в использовании незадействованных адресов команд, позволивших добавить еще один регистр F, команды для специальных функций (x!, sinh, cosh, tanh, asinh, acosh, atanh, erf, J0, J1, Y0, Y1), команды добавления к регистру (M+) и команды вывода графики в растровое изображение размером 400×300 точек. Имеется два основных режима работы: режим калькулятора, режим редактирования программы (включается галочкой в панели "Программа").
PocketMK использует обратную польскую форму записи. Например, для вычисления выражения 2+3*(6+7) надо выполнить следующие действия:
2 B↑ 3 B↑ 6 B↑ 7 + * + или более просто 6 B↑ 7 + 3 * 2 +. Использование функций аналогично. Например, вычислим синус от угла 37 градусов 30 минут. Сначала превратим минуты в десятичную дробь: 37.5. Переключив PocketMK в режим deg (переключатель находится в верхнем левом углу в режиме калькулятора), вводим это число последовательным нажатием клавиш 3 7 . 5 и берем от него синус, нажав клавиши F sin. На индикаторе читаем значение синуса: 0.6087614290087207. Если нужно ввести большое число, можно воспользоваться командой ввода порядка EE. При этом для отрицательных порядков за ней следует ввести команду ±. Например,
1 . 7 8 EE 5 3 дает 1.78e53, 2 . 5 6 EE ± 7 дает 2.56e-7, 3 ± EE 6 дает -3e6
Внешний вид
PocketMK имеет два режима работы: режим калькулятора (по умолчанию) и режим ввода программы. В режиме калькулятора PocketMK работает в интерактивном режиме – любая нажатая клавиша сразу же выполняется. Вверху находятся кнопки переключения ввода углов, содержимое стека {X,Y,Z,T} и кнопки управления программой. В средней части располагается клавиатура, вид которой зависит от состояния функциональных клавиш «F» и «K». Внизу находятся кнопки панели инструментов и панель меню.
Кнопки переключения ввода углов имеют следующее значение: deg – углы считаются в градусах (период sin(x) равен 360), grad – углы считаются в градах (период sin(x) равен 400), rad – углы считаются в радианах (период sin(x) равен 2π).
Окно отображения стека расположено сверху посередине. «Главный» регистр X выделен цветом, поскольку именно с ним выполняются все основные операции – сложение, вычитание, умножение и деление, вычисление функций, запись и считывание регистров, операции условного перехода. Остальные регистры стека являются скорее «дополнительными» и выводятся серым цветом.
Кнопки управления выполнением программы позволяют выполнить программу пошагово (Step) и запустить программу на автоматическое выполнение с текущей программной позиции (Run). Для остановки программы (например, в случае зацикливания) служит кнопка Stop, заменяющая кнопку Run при запуске программы.
Клавиатура отображается в режиме калькулятора и в режиме редактирования программы. Ее вид зависит от нажатия функциональных клавиш «F» и «K», располагающихся в левой верхней части клавиатуры. Для удобства пользователя цвета кнопок изменяются вместе с изменением их функциональности. Кроме того, графические команды выделены зеленым цветом, а команды обращения к регистрам фиолетовым.
Часть кнопок имеет смысл только при выполнении программы (команды перехода, остановки программы и т.д., выделенные желтым). Действие остальных команд одинаково «полезно» в любом режиме. Отличие состоит в том, что в режиме калькулятора команды выполняются сразу, а в режиме ввода программы они записываются в программную память.
Регистры, стек и операции
PocketMK хранит числа в специальных переменных – регистрах. Каждый регистр памяти имеет свое обозначение в виде цифры или буквы. Шестнадцать из них обозначаются числами от 0 до 9 и начальными буквами латинского алфавита (A, B, C, D, E, F). По сути эти регистры являются обычными переменными, обращение к которым возможно произвольным образом. Еще четыре регистра образуют стек и обозначаются латинскими буквами (X, Y, Z, T). Прямой доступ возможен только к одному регистру из них – регистру X. Остальные регистры участвуют в вычислениях и доступны только с помощью команд работы со стеком. Последний из регистров (называемый B или X1) хранит информацию о предыдущем значении регистра X и может быть только прочитан с помощью команды Bx. Часто в описаниях программ перед регистром ставят букву R (например, RA – регистр А), чтобы отличать регистр от буквы. Регистры X, Y, Z, T отображаются на экране калькулятора. Регистры 0,1,...,F можно посмотреть в панели «Регистры».
При вводе в калькулятор число заносится в регистр X. Прочитать число в регистрах 0,1,...,F можно с помощью кнопки MR (от английского «memory read»). Например, команда MRA впишет число из регистра A в регистр Х. Аналогично команда MSA (от английского «memory save») запишет число из регистра Х в регистр А. Команда M+A прибавит число из регистра Х к числу, хранящемуся в регистре А. Команда M+ отсутствовала в МК-61 и была введена для удобства работы и программирования различных статистических приложений.
Каждая операция калькулятором выполняется либо над одним числом, находящимся в регистре X (операция одноместная: x2, sin, erf и т.д.), либо над двумя числами, одно из которых находится в регистре X, а другое - в регистре Y. Отличительной особенностью этого калькулятора является то, что операция, которую следует производить с двумя числами, выполняется после ввода двух чисел – используется так называемая польская или постфиксная запись выражений.
Числа, над которыми нужно совершить ту или иную арифметическую операцию, должны находиться в двух регистрах – X и Y. В регистр Y можно попасть только из регистра X. Делается это нажатием клавиши B↑. При этом в регистре X остается копия числа. Затем в регистр X записывается второе число, причем первое число стирается. В случае вычитания уменьшаемое должно находиться в Y, а вычитаемое – в X. При делении в Y должно находиться делимое, в X – делитель. После ввода числа в оба регистра, можно нажать клавишу выбранной операции. Результат ее будет помещен в регистр X. То, что было прежде в регистре Y, не сохранится. Здесь следует обратить внимание на то, что если в регистре X находился результат операции, то ввод нового числа в регистр X автоматически передвигает старое содержимое регистра X в регистр Y. Исключением являются операции очистки регистра Cx и операция B↑.
Наконец, в таблице приведено состояние стека до и после применения различных операций:
Операция
Стек до
Стек после
Ввод числа r из регистра или числа π
b x y z t
b r x y z
Ввод числа n после Сх, B↑
b x y z t
b n y z t
Ввод числа n после других операций
b x y z t
b n x y z
Команда Cx
b x y z t
b 0 y z t
Команда B↑
b x y z t
b x x y z
Команда Bx
b x y z t
b b x y z
Команда ↔
b x y z t
x y x z t
Команда ↻
b x y z t
x y z t x
Вычисление функции f(x)
b x y z t
x f y z t
Двуместная операция g
b x y z t
x g z t t
Команда B↓
b x y z t
x y z t t
Программирование
Составим теперь простую программу вычисления площади круга. Формула для вычисления площади круга известна: S=πD2/4, где D – диаметр круга. Константа π уже есть в калькуляторе. Величину D необходимо вручную ввести с клавиатуры (оно будет помещено в регистр X). Пусть D = 3. Для ручного расчета нужно нажать клавиши: x2 F pi * 4 /. На индикаторе читаем результат: 7.0685834705770345. Те же клавиши и в той же последовательности нужно будет нажать, когда мы станем вводить в калькулятор программу для вычисления площади круга.
Программа располагается в калькуляторе в виде отдельных команд, каждая из которых занимает свою ячейку программной памяти (некоторые управляющие – две ячейки – команда и адрес). Всего таких ячеек 160. Им присвоены номера, называемые адресами – от #00 до #F9. В силу совместимости с МК-61 нумерация сделана так, что первая цифра шестнадцатеричная, а вторая всего лишь десятичная. Например, адрес #F9 = 0xF*10+9 = 159. Такая вот смесь ☺ !
Чтобы ввести программу в калькулятор, надо перевести его в состояние, называемое режимом редактирования программы. В режиме редактирования PocketMK нажатие клавиш приводит к вводу команды в программную память вместо ее непосредственного выполнения. Для перехода в режим редактирования программы следует раскрыть панель программа и поставить галочку напротив Ввод программы. Стрелки в этой панели перемещают курсор (текущий адрес), кнопки Ins и Del добавляют и удаляют текущую/последнюю ячейку программы. Адрес первой команды в строке указывается перед двоеточием. Текущая позиция курсора выделена голубым. Курсор не показывается если он находится за последней командой (калькулятор готов к добавлению новой команды).
Нажимаем x2 и в программе появляется x2. Ее появление в таблице означает, что команда занесена в программную память. Одновременно прямоугольник сместился вправо – счетчик адресов увеличился на 1. Далее нажимаем F π и в программе появляется π (модификатор F в программе всегда опускается) и т.д. Таким образом вводятся все команды. Для останова работы калькулятора по программе необходимо ввести специальную команду: stop (клавиши F stop).
Чтобы проверить работу программы вернемся в режим калькулятора (сняв галочку), введем диаметр круга и запустим программу кнопкой Run. На мгновение мигнет индикатор Stop вместо Run и программа выдаст ответ. Кнопка Stop предназначена для останова программы (например, в бесконечном цикле). Для пошагового исполнения программы (в целях отладки или в качестве одного из способов ввода чисел) можно воспользоваться кнопкой Step. Ее действие полностью аналогично Run, но будет исполнена только текущая команда.
Следует отметить, что в отличие от калькулятора MK-61, переход в начало программы осуществляется автоматически, если курсор не виден (ожидается добавление новой команды). Кроме того, программа автоматически останавливается при достижении конца введенных команд (команда stop в конце программы не обязательна).
Команды перехода
Для безусловного перехода на адрес программы используется клавиша go (аналог команды БП – безусловный переход). После команды следует указать адрес перехода. Например, для того, чтобы перейти на адрес 60, надо нажать клавиши: go 6 0. Эта команда работает только в программном режиме и занимает две ячейки памяти: первая ячейка – код команды перехода, вторая – адрес перехода. Отмечу, что для совместимости с программами МК–61 пришлось ввести «странную» систему адресов. Всего ячеек 160 с адресами – от 00 до F9. При этом адрес состоит их двух цифр: первая цифра шестнадцатеричная, а вторая десятичная. Например, адрес #F9 = 0xF*10+9 = 159.
Чтобы вызвать подпрограмму, используется команда sub (аналог команды ПП – переход на подпрограмму). Так же, как и в команде безусловного перехода, необходимо указать адрес, с которого начинается подпрограмма. Команда возврата из подпрограммы – ret (аналог команды В/О). Подпрограммы могут вкладываться друг в друга.
Также есть специальные команды, которые изменяют порядок выполнения программы в зависимости от содержимого регистра X. Это команды x<0, x≥0, x=0, x≠0. Если условие выполняется, то управление передается на команду, следующую за командой условия (напомним, что команда условия занимает 2 ячейки – команда и адрес), в противном случае – управление передастся на указанный адрес. Например, с адреса 20 мы введем строку: F x<0 25, тогда если содержимое регистра X будет меньше нуля, то программа продолжится с адреса 22, а если ноль и больше, то с адреса 25.
Для организации циклов предусмотрены специальные команды L0, L1, L2, L3. После ввода этих команд также необходимо указать адрес перехода. Каждая команда ассоциирована с регистрами от 0 до 3, соответственно. После выполнения команды происходит вычитание единицы из этого регистра и сравнение результата с нулем. Если результат равен нулю, то программа продолжит свое выполнение со следующего адреса. Иначе – перейдет на указанный адрес перехода. Напишем программу вычисления факториала. Сначала перейдем в режим программирования с помощью меню или панели управления. Затем вводим программу: MS0 1 MR0 * L0 02. После ввода программы переходим в режим калькулятора, вводим число вводится с клавиатуры и нажимаем кнопку Run. В результате в регистре X появляется факториал числа. Можно проверить вычисленное значение нажав ↔ K x!.
Все команды PocketMK имеют уникальный номер в диапазоне 0...255. Полный список команд с указанием их кодов можно найти в таблице. Здесь номер строки дает старший разряд, номер столбца – младший разряд номера команды. Например, команда |x| имеет номер 0x31.
x0
x1
x2
x3
x4
x5
x6
x7
x8
x9
xA
xB
xC
xD
xE
xF
x00
0
1
2
3
4
5
6
7
8
9
.
±
EE
Cx
B↑
Bx
x10
+
–
*
/
↔
10x
ex
lg
ln
asin
acos
atan
sin
cos
tan
Y0
x20
π
sqrt
x2
1/x
xy
↻
B↓
sh
ch
th
erf
x!
asinh
acosh
atanh
Y1
x30
col
|x|
sign
cls
[x]
{x}
max
and
or
xor
not
rnd
pnt
line
rect
arc
x40
MS0
MS1
MS2
MS3
MS4
MS5
MS6
MS7
MS8
MS9
MSA
MSB
MSC
MSD
MSE
MSF
x50
stop
go
ret
sub
nop
J0
J1
x≠0
L2
x≥0
L3
L1
x<0
L0
x=0
wait
x60
MR0
MR1
MR2
MR3
MR4
MR5
MR6
MR7
MR8
MR9
MRA
MRB
MRC
MRD
MRE
MRF
x70
Kx≠00
Kx≠01
Kx≠02
Kx≠03
Kx≠04
Kx≠05
Kx≠06
Kx≠07
Kx≠08
Kx≠09
Kx≠0A
Kx≠0B
Kx≠0C
Kx≠0D
Kx≠0E
Kx≠0F
x80
Kgo0
Kgo1
Kgo2
Kgo3
Kgo4
Kgo5
Kgo6
Kgo7
Kgo8
Kgo9
KgoA
KgoB
KgoC
KgoD
KgoE
KgoF
x90
Kx≥00
Kx≥01
Kx≥02
Kx≥03
Kx≥04
Kx≥05
Kx≥06
Kx≥07
Kx≥08
Kx≥09
Kx≥0A
Kx≥0B
Kx≥0C
Kx≥0D
Kx≥0E
Kx≥0F
xA0
Ksub0
Ksub1
Ksub2
Ksub3
Ksub4
Ksub5
Ksub6
Ksub7
Ksub8
Ksub9
KsubA
KsubB
KsubC
KsubD
KsubE
KsubF
xB0
KMS0
KMS1
KMS2
KMS3
KMS4
KMS5
KMS6
KMS7
KMS8
KMS9
KMSA
KMSB
KMSC
KMSD
KMSE
KMSF
xC0
Kx<00
Kx<01
Kx<02
Kx<03
Kx<04
Kx<05
Kx<06
Kx<07
Kx<08
Kx<09
Kx<0A
Kx<0B
Kx<0C
Kx<0D
Kx<0E
Kx<0F
xD0
KMR0
KMR1
KMR2
KMR3
KMR4
KMR5
KMR6
KMR7
KMR8
KMR9
KMRA
KMRB
KMRC
KMRD
KMRE
KMRF
xE0
Kx=00
Kx=01
Kx=02
Kx=03
Kx=04
Kx=05
Kx=06
Kx=07
Kx=08
Kx=09
Kx=0A
Kx=0B
Kx=0C
Kx=0D
Kx=0E
Kx=0F
xF0
M+0
M+1
M+2
M+3
M+4
M+5
M+6
M+7
M+8
M+9
M+A
M+B
M+C
M+D
M+E
M+F
В целом система команд основана на системе команд популярных советских микрокалькуляторов БЗ-34 или МК-61. Так что практически любая программа с МК-61 (не использующая недокументированные возможности) будет работать и на PocketMK. Однако, есть и несколько отличий. PocketMK использует фортрановские символьные обозначения функций: asin для арксинуса, tan вместо tg для тангенса и т.д. Кроме того, имеется различное начертание ряда команд с более устоявшимся англоязычным обозначением:
PocketMK
БЗ-34
MK-61
MR
ИП
П→X
MS
П
Х→П
rnd
КСЧ
sign
КЗН
stop
С/П
С/П
ret
В/О
В/О
go
БП
БП
sub
ПП
ПП
В PocketMK добавлен ряд команд (выделены цветом в таблице):
графических (pnt, line, rect, arc, col, cls) для рисования точки (х,у), линии в точку (х,у), прямоугольника из текущей до точки (х,у), дуги из текущей до точки (х,у), задания цвета (можно задавать и вручную выбором из палитры) и очистки экрана;
прибавления регистра Х к числу в указанном регистре (команда М+);
удаления числа из стека (команда B↓).
Удалены редко использующиеся (по крайней мере автором) команды по преобразованию углов в минуты, секунды и обратно. Операция xy теперь стала настоящей двуместной операцией (содержимое регистра Y не сохраняется).
В нормальном виде клавиатура содержит довольно ограниченный набор часто используемых кнопок. Доступ к большинству научных функций и управляющих команд возможен после нажатия клавиш F, K или обоих сразу (для графических команд).
Ввод числа
Ввод числа осуществляется командами 0 1 2 3 4 5 6 7 8 9 . ± EE. Команды 0 1 2 3 4 5 6 7 8 9 . вводят непосредственно цифры мантиссы или порядка. Команда ± меняет знак числа в регистре Х, если введена до команды EE или знак порядка, если после. Команда EE указывает, что дальнейший ввод относится к вводу порядка. Нажатие любой другой клавиши (кроме функциональных F или K) прерывает ввод числа. При вводе нового числа предыдущее значение регистра Х поднимается вверх по стеку, если перед этим не была выполнена одна из команд Cx или B↑.
Важно! Команда EE превращает 0 в 1 (при этом в стеке меняется только значение регистра Х, остальные регистры стека сохраняют свои значения). Эта недокументированная особенность советских калькуляторов часто использовалась в программах.
Совет! Команда EE изменяет порядок любого числа в регистре Х. Поэтому для быстрого умножения числа на 10n в программе можно воспользоваться кодом EE n, занимающим всего две ячейки памяти при n=1...9.
Команды работы со стеком
К этой группе относятся команды Cx B↑ ↔ Bx ↻ B↓. Напомним, что PocketMK использует стек из 4х регистров (X,Y,Z,T) и регистр B для хранения предыдущего значения Х. Исходное значение стека будем считать равным b x y z t.
Cx
Обнуляет регистр Х. Ввод числа после этой команды не изменяет содержимое стека.
B↑
Поднимает число Х вверх. Стек изменяется следующим образом b x x y z. Ввод числа после этой команды не изменяет содержимое стека.
↔
Меняет содержимое регистров X и Y местами. Стек изменяется следующим образом x y x z t.
Bx
Возвращает значение регистра Вх в Х. Стек изменяется следующим образом b b x y z.
↻
Вращает содержимое стека. Стек изменяется следующим образом x y z t x.
B↓
Удаляет содержимое регистра Х со сдвигом стека вниз. Стек изменяется следующим образом x y z t t.
Двуместные операции
Операции выполняются над числами в регистрах X и Y. При этом содержимое регистра Y теряется, а регистры Z и T смещаются вниз. Значение регистра Х заносится в регистр В. Стек b x y z t изменяется следующим образом x g z t t, где g – результат операции. Рассмотрим команды подробнее.
+
Сложение чисел X=Y + X.
–
Разность чисел X=Y – X.
*
Умножение чисел X=Y * X.
/
Деление чисел X=Y / X.
xy
Возведение в степень X=XY.
and
Побитовое И. Дробная часть чисел отбрасывается.
or
Побитовое ИЛИ. Дробная часть чисел отбрасывается.
xor
Побитовое Исключающее ИЛИ. Дробная часть чисел отбрасывается.
max
Максимальное из чисел X и Y.
Математические функции
Вызов функции относится к одноместным операциям, не затрагивающим содержимое регистров Y, Z, T. Значение аргумента всегда заносится в регистр В. Результат действия функции помещается в регистр Х. Для обратных функций на советских калькуляторах использовались обозначения f(x)-1 (например, sin-1), в PocketMK они заменены на стандартные компьютерные обозначения с добавление буквы a перед именем функции (например, asin).
Синус sin(x), вычисляется в зависимости от выбора deg|grd|rad.
cos
Косинус cos(x), вычисляется в зависимости от выбора deg|grd|rad.
tan
Тангенс tan(x)=sin(x)/cos(x), вычисляется в зависимости от выбора deg|grd|rad.
asin
Арксинус asin(sin(x))=x, вычисляется в зависимости от выбора deg|grd|rad.
acos
Арккосинус acos(cos(x))=|x|, вычисляется в зависимости от выбора deg|grd|rad.
atan
Арктангенс atan(tan(x))=x, вычисляется в зависимости от выбора deg|grd|rad.
sh
Гиперболический синус sh(x) = (ex – e-x)/2.
ch
Гиперболический косинус ch(x)=(ex + e-x)/2.
th
Гиперболический тангенс th(x)=sh(x)/ch(x).
ash
Гиперболический арксинус ash(sh(x))=x.
ach
Гиперболический арккосинус ach(ch(x))=|x|.
ath
Гиперболический арктангенс ath(th(x))=x.
√x
Корень квадратный sqrt(x2) = |x|.
x2
Квадрат числа.
1/x
erf
Интеграл вероятности erf(x) = ∫exp(-x2)dx.
x!
Факториал x!=Г(x+1), Г(x) – гамма функция.
J0
Функция Бесселя J0.
J1
Функция Бесселя J1.
Y0
Функция Бесселя Y0.
Y1
Функция Бесселя Y1.
|x|
Абсолютное значение |x| = {x для x≥0; –x для x<0}
sign
Знак числа: 1 для x>0, 0 для x=0, -1 для x<0.
[x]
Целая часть числа.
{x}
Дробная часть числа.
rnd
Случайное число. В отличии от МК-61 не зависит от числа X. Из соображений совместимости, случайное число не изменяет стек (как в случае с π).
not
Побитовое отрицание. Дробная часть чисел отбрасывается.
Работа с регистрами
Для работы с регистрами предназначены три группы команд.
Команды MSN записывают значение регистра Х в регистр N. Значение регистра Х не изменяется.
Команды M+N прибавляют значение регистра Х к числу в регистре N и записывают результат в регистр N. Значение регистра Х не изменяется.
Команды MRN присваивают регистру X значение из регистра N. Стек b x y z t изменяется следующим образом b n x y z, где n – значение в регистре N.
Немножко в стороне стоит команда π. Ее действие полностью аналогично командам MRN, но в регистр Х записывается число π=3.141592653589793.
Каждая из команд присвоения регистру имеет отдельный уникальный номер. Это сделано для совместимости с программами для МК-61.
Совет! Ввод небольшого числа данных (до 16 чисел) в программу удобно выполнять с помощью заполнения регистров памяти перед запуском программы.
Совет! Ввод данных в программу можно выполнять с помощью кнопки Step. Кусок соответствующей программы будет выглядеть следующим образом: MS1 MS2 MS3 .... Идея состоит в пошаговом исполнении программы, когда число в регистре Х меняется пользователем на каждом шаге.
Совет! Часто программы используют константы (или типичные начальные условия). Их можно сохранить вместе с программой если пометить соответствующие регистры при сохранении.
Совет! Иногда удобней (особенно в играх) загрузить программу с заполненными регистрами заново, чем вводить цифры вручную.
Управление программой
Эта группа команд осуществляет управление выполнением программы. Как правило, команды из этой группы требуют 2 ячейки памяти: одна ячейка под саму команду, а вторая ячейка на адрес перехода для этой команды.
stop
Останавливает выполнение программы.
wait
Приостанавливает выполнение программы на 1 секунду. Удобно использовать в динамических играх.
nop
Не выполняет никаких действий.
go
Безусловный переход на адрес перехода.
sub
Вызов подпрограммы с адреса перехода.
ret
Возврат из подпрограммы. При пустом стеке возврата происходит возврат на нулевой адрес (начало программы).
x≠0
Если условие X≠0 выполнено, выполняется команда, следующая после адреса перехода, в противном случае производится переход по адресу перехода.
x<0
Если условие X<0 выполнено, выполняется команда, следующая после адреса перехода, в противном случае производится переход по адресу перехода.
x=0
Если условие X=0 выполнено, выполняется команда, следующая после адреса перехода, в противном случае производится переход по адресу перехода.
x≥0
Если условие X≥0 выполнено, выполняется команда, следующая после адреса перехода, в противном случае производится переход по адресу перехода.
L0
После выполнения команды происходит вычитание единицы из регистра 0. Если результат R0≤0, то продолжится выполнение со следующего адреса. Иначе – перейдет на указанный адрес перехода.
L1
После выполнения команды происходит вычитание единицы из регистра 1. Если результат R1≤0, то продолжится выполнение со следующего адреса. Иначе – перейдет на указанный адрес перехода.
L2
После выполнения команды происходит вычитание единицы из регистра 2. Если результат R2≤0, то продолжится выполнение со следующего адреса. Иначе – перейдет на указанный адрес перехода.
L3
После выполнения команды происходит вычитание единицы из регистра 3. Если результат R3≤0, то продолжится выполнение со следующего адреса. Иначе – перейдет на указанный адрес перехода.
Совет! Для быстрого возврата на нулевой адрес можно использовать команду ret. Это работает, если стек возвратов пустой (не выполняется подпрограмма в данный момент).
Совет! Использование команды wait и переключателя deg|grd|rad позволяет создавать динамические программы. Идея состоит в том, что функция cos выдает различные значения в зависимости от положения переключателя. Например, следующий код wait MR9 cos sign будет ожидать реакции пользователя 1 секунду и возвращать -1, 0, 1 для переключателя deg|grd|rad соответственно, если R9=100.
Косвенная адресация
Команды косвенной адресации начинаются с нажатия кнопки K и всегда занимают одну ячейку памяти. После нажатия K вводится одна из команд MS, MR, go, sub, x<0, x≥0, x=0, x≠0 и номер регистра. На клавиатуре команды косвенной адресации выделены фиолетовым цветом. При косвенной адресации (индексации) в командах KMS и KMR запись и считывание числа производится из регистра с номером равным остатку от деления на 16 числа в указанном регистре. Переход по адресу в командах Kgo, Ksub, Kx<0, Kx≥0, Kx=0, Kx≠0 производится по остатку от деления содержимого регистра на 160.
Перед выполнением действия значение регистра модифицируется. Если номер регистра 0, 1, 2, 3, то его значение уменьшается на единицу. Если номер регистра равен 4, 5, 6, то значение регистра увеличивается на единицу. Остальные регистры не изменяются.
Например, команда KMS0 поместит значение регистра X в регистр, номер которого указан в регистре 0, но меньший на 1. Команда KMR4 поместит в регистр X значение регистра, указанного в регистре 4, но больший на 1. Команда Kgo9 переведет работу программы на адрес, указанный в регистре 9. Аналогично переход на подпрограмму, адрес которой указан в регистре B, выполняется командой KsubB. Команда Kx=0A аналогична команде x=0 XX, но адрес перехода XX указывается в регистре A, и команда занимает одну ячейку памяти.
Следует обратить внимание, что если в регистр поместить дробное число, а затем к этому регистру применить команду косвенного вызова, то от помещенного числа будет отброшена дробная часть. Например, поместив число 12.34567 в регистр 9 и выполнив команду KMR9, в регистре 9 останется число 12. Эта «недокументированная» особенность в программах БЗ-34 часто использовалась для отделения целой части числа и была сохранена в PocketMK.
Графические команды
В PocketMK добавлены 6 графических команд: cls, col, pnt, line, rect, arc. Графический вывод производится в растровую картинку размером 400*300 с началом координат в левом нижнем углу. Все точки вне этих границ игнорируются.
Обработка ошибок
В ходе расчетов могут возникать ошибочные ситуации (например, деление на 0, извлечение корня из отрицательно числа и т.д.). В этом случае на экране показывается сообщение NaN или Infinity. Работа программы автоматически останавливается при возникновении ошибочной ситуации.
Советы
Команда EE изменяет порядок любого числа в регистре Х. Поэтому для быстрого умножения числа на 10n в программе можно воспользоваться кодом EE n, занимающим всего две ячейки памяти при n=1...9.
Ввод небольшого числа данных (до 16 чисел) в программу удобно выполнять с помощью заполнения регистров памяти перед запуском программы.
Ввод данных в программу можно выполнять с помощью кнопки Step. Кусок соответствующей программы будет выглядеть следующим образом: MS1 MS2 MS3 .... Идея состоит в пошаговом исполнении программы, когда число в регистре Х меняется пользователем на каждом шаге.
Часто программы используют константы (или типичные начальные условия). Их можно сохранить вместе с программой если пометить соответствующие регистры при сохранении.
Иногда удобней (особенно в играх) загрузить программу с заполненными регистрами заново, чем вводить цифры вручную.
Для быстрого возврата на нулевой адрес можно использовать команду ret. Это работает, если стек возвратов пустой (не выполняется подпрограмма в данный момент).
Использование команды wait и переключателя deg|grd|rad позволяет создавать динамические программы. Идея состоит в том, что функция cos выдает различные значения в зависимости от положения переключателя. Например, следующий код wait MR9 cos sign будет ожидать реакции пользователя 1 секунду и возвращать -1, 0, 1 для переключателя deg|grd|rad соответственно, если R9=100.
";
}
function updateKeys()
{
document.getElementById("prg").checked=prgON;
if(needR!=0)
{
// if(needR<0)
// document.getElementById("status").innerHTML = "Enter register";
// else if(needR>=0x200)
// document.getElementById("status").innerHTML = "Enter first digit of address";
// else
// document.getElementById("status").innerHTML = "Enter second digit of address";
keyCur = keyR;
}
else if(pressF==0 && pressK==0) keyCur = keyN;
else if(pressF==1 && pressK==0) keyCur = keyF;
else if(pressF==0 && pressK==1) keyCur = keyK;
else if(pressF==1 && pressK==1) keyCur = keyFK;
// if(needR==0) document.getElementById("status").innerHTML = "";
for(var i=0;i<30;i++)
{
var but = document.getElementById("button"+i);
var stl = "color:#000;background:#f9f9f9";
var key = keyCur[i]; but.disabled = false;
if(key==-3) { but.disabled = true; but.innerHTML=""; }
else if(key==-1)
{ but.innerHTML = "F"; stl = pressF>0?"color:#850;background:#fc8":"color:#fa0;background:#f9f9f9"; }
else if(key==-2)
{ but.innerHTML = "K"; stl = pressK>0?"color:#008;background:#88f":"color:#00f;background:#f9f9f9"; }
else if(needR!=0)
{
if(key<16)
but.innerHTML = key.toString(16).toUpperCase();
else
{
stl = "color:#f00;background:#f9f9f9";
but.innerHTML = "×";
}
}
else if(key<0)
{
stl = "color:#f0f;background:#f9f9f9";
but.innerHTML = cmds[-key].id.slice(0,-1);
}
else
{
const grph = [48,60,61,62,63,51];
const exeC = [81,83,87,88,89,90,91,92,93,94];
if(grph.includes(key)) stl = "color:#0d0;background:#f9f9f9";
if(exeC.includes(key)) stl = "color:#880;background:#f9f9f9";
if(key==0x0d) stl = "color:#F00;background:#f9f9f9";
but.innerHTML = cmds[key].id;
}
but.style = "width:55px;height:55px;border-radius:10px;font-size:15pt;padding:0px;"+stl;
}
updateRegs();
updateProg();
}
function handleKey(key)
{
const exeC = [81,83,87,88,89,90,91,92,93,94];
if(prgON)
{
if(pos<0 || pos>=160) pos = 0;
if(pos>prog.length) pos=prog.length;
prog[pos] = key; pos++;
if(exeC.includes(key))
{ needR = 0x200; updateKeys(); }
}
else if(!prgON) if(!exeC.includes(key))
{
var bb = x;
cmds[key].exec();
if(num=="") b = bb;
}
if(pressF!=0 || pressK!=0)
{ pressF = pressK = 0; updateKeys(); }
updateRegs();
updateProg();
}
function press(but)
{
var key = keyCur[but];
if(key==-3) return; // nothing to do
else if(key==-1) // key F
{ pressF = 1-pressF; updateKeys(); }
else if(key==-2) // key K
{ pressK = 1-pressK; updateKeys(); }
else if(key<0) // need RegId
{ needR = key; updateKeys(); }
else if(needR>0) // input RegId
{
// use "nop" for prev cmd if canceled
if(key>16) { pos--; prog[pos]=84; needR=0; updateKeys(); }
else if(needR>=0x200) { needR+=key-0x100; }
else // NOTE: for compatibility reason the pseudo-hex notation is used!!!
{ prog[pos] = 10*(needR%16)+key; pos++; needR=0; updateKeys(); }
}
else if(needR<0) // input RegId
{
if(key<16) key -= needR;
needR=0; updateKeys(); handleKey(key);
}
else handleKey(key);
}
function stopRun()
{
document.getElementById("butRun").innerHTML = "Run";
stop=false; if(interval) clearInterval(interval); interval=0;
}
function posDec()
{
pos--; if(pos<0) pos = prog.length-1;
updateProg();
}
function posInc()
{
pos++; if(pos>prog.length) pos = 0;
updateProg();
}
function posUp()
{
pos-=10; if(pos<0) pos = 0;
updateProg();
}
function posDwn()
{
pos+=10; if(pos>prog.length) pos = prog.length-1;
updateProg();
}
function posDel()
{
if(pos>=0 && pos0) prog.pop();
updateProg();
}
function posIns()
{
if(pos>=0 && pos=prog.length) pos=0;
stop = false;
interval = setInterval(step, 20);
document.getElementById("butRun").innerHTML = "Stop";
}
function step()
{
if(pos<0 || pos>=prog.length || stop || x-x!=0) { stopRun(); return; }
var kod = prog[pos], opt = pos+1=160) pos=0;
updateRegs();
updateProg();
}
function modifReg(rr)
{
if(rr<8) reg[rr] += (rr<4) ? -1:1;
reg[rr] = Math.floor(reg[rr]);
return reg[rr];
}
function fact(x)
{
var r = Math.exp(lgamma(x+1)); // this is good but approximate result
if(x==Math.floor(x)) r = Math.floor(r+0.5);
return r;
}
function lgamma(x)
{
const cof=[76.18009172947146,-86.50532032941677, 24.01409824083091,-1.231739572450155, 0.1208650973866179e-2,-0.5395239384953e-5];
var tmp = x+5.5 - (x+0.5)*Math.log(x+5.5);
var ser = 1.000000000190015;
for(var j=0;j<=5;j++) ser += cof[j]/(x+j+1);
return Math.log(2.5066282746310005*ser/x) -tmp;
}
function erf(x) // интеграл вероятности с точностью 1.5*10^-7 !!!
{
var s=1;
if(x<0) { s = -1; x = -x; }
var t=1/(1+0.32759*x);
var res=0.254829592*t;
res -= 0.284496736*t*t;
res += 1.42413741*t*t*t;
res -= 1.453152027*Math.pow(t,4);
res += 1.061405429*Math.pow(t,5);
res *= Math.exp(-x*x);
return s*(1-res);
}
function BesselJ0(x) {
var ax,z,xx,y,ans,ans1,ans2;
ax = Math.abs(x);
if (ax < 8.0) {
y = x*x;
ans1 = 57568490574.0+y*(-13362590354.0+y*(651619640.7+y*(-11214424.18+y*(77392.33017+y*(-184.9052456)))));
ans2 = 57568490411.0+y*(1029532985.0+y*(9494680.718+y*(59272.64853+y*(267.8532712+y*1.0))));
ans = ans1/ans2;
} else {
z = 8.0/ax;
y = z*z;
xx = ax-0.785398164;
ans1 = 1.0+y*(-0.1098628627e-2+y*(0.2734510407e-4+y*(-0.2073370639e-5+y*0.2093887211e-6)));
ans2 = -0.1562499995e-1+y*(0.1430488765e-3+y*(-0.6911147651e-5+y*(0.7621095161e-6 - y*0.934935152e-7)));
ans = Math.sqrt(0.636619772 / ax)*(Math.cos(xx)*ans1 - z*Math.sin(xx)*ans2);
}
return ans;
}
function BesselJ1(x) {
var ax,z,xx,y,ans,ans1,ans2;
ax = Math.abs(x);
if (ax < 8.0) {
y=x*x;
ans1 = x*(72362614232.0+y*(-7895059235.0+y*(242396853.1+y*(-2972611.439+y*(15704.48260+y*(-30.16036606))))));
ans2 = 144725228442.0+y*(2300535178.0+y*(18583304.74+y*(99447.43394+y*(376.9991397+y*1.0))));
ans = ans1/ans2;
} else {
z=8.0/ax;
y=z*z;
xx=ax-2.356194491;
ans1=1.0+y*(0.183105e-2+y*(-0.3516396496e-4+y*(0.2457520174e-5+y*(-0.240337019e-6))));
ans2=0.04687499995+y*(-0.2002690873e-3+y*(0.8449199096e-5+y*(-0.88228987e-6+y*0.105787412e-6)));
ans=Math.sqrt(0.636619772/ax)*(Math.cos(xx)*ans1-z*Math.sin(xx)*ans2);
if (x < 0.0) ans = -ans;
}
return ans;
}
function BesselY0(x) {
var z, xx, y, ans, ans1, ans2;
if (x < 8.0) {
y=x*x;
ans1 = -2957821389.0+y*(7062834065.0+y*(-512359803.6+y*(10879881.29+y*(-86327.92757+y*228.4622733))));
ans2 = 40076544269.0+y*(745249964.8+y*(7189466.438+y*(47447.26470+y*(226.1030244+y*1.0))));
ans = (ans1/ans2)+0.636619772*BesselJ0(x)*Math.log(x);
} else {
z=8.0/x;
y=z*z;
xx=x-0.785398164;
ans1 = 1.0+y*(-0.1098628627e-2+y*(0.2734510407e-4+y*(-0.2073370639e-5+y*0.2093887211e-6)));
ans2 = -0.1562499995e-1+y*(0.1430488765e-3+y*(-0.6911147651e-5+y*(0.7621095161e-6+y*(-0.934945152e-7))));
ans = Math.sqrt(0.636619772/x)*(Math.sin(xx)+ans1+z*Math.cos(xx)*ans2);
}
return ans;
}
function BesselY1(x) {
var z, xx, y, ans, ans1, ans2;
if (x < 8.0) {
y=x*x
ans1 = x*(-0.4900604943e13+y*(0.1275274390e13+y*(-0.5153438139e11+y*(0.7349264551e9+y*(-0.4237922726e7+y*0.8511937935e4)))));
ans2 = 0.2499580570e14+y*(0.4244419664e12+y*(0.3733650367e10+y*(0.2245904002e8+y*(0.1020426050e6+y*(0.3549632885e3+y)))));
ans = (ans1/ans2)+0.636619772*(BesselJ1(x)*Math.log(x)-1.0/x);
} else {
z=8.0/x;
y=z*z;
xx=x-2.356194491;
ans1=1.0+y*(0.183105e-2+y*(-0.3516396496e-4+y*(0.2457520174e-5+y*(-0.240337019e-6))));
ans2=0.04687499995+y*(-0.202690873e-3+y*(0.8449199096e-5+y*(-0.88228987e-6+y*0.10578e-6)));
ans=Math.sqrt(0.636619772/x)*(Math.sin(xx)*ans1+z*Math.cos(xx)*ans2);
}
return ans;
}
mathgl-2.4.4/website/json/ 0000755 0001750 0001750 00000000000 13513030041 015560 5 ustar alastair alastair mathgl-2.4.4/website/json/json.html 0000644 0001750 0001750 00000011201 13513030041 017412 0 ustar alastair alastair
Example of dysplaying MathGL plots using JSON
Select sample
You can use mouse with pressed left button for rotation; with pressed middle button for shift; mouse wheel for zoom in/out. Double click will restore original view.
mathgl-2.4.4/website/json/mathgl.js 0000644 0001750 0001750 00000040454 13513030041 017401 0 ustar alastair alastair /***************************************************************************
* mathgl.js is part of Math Graphic Library
* Copyright (C) 2012 Alexey Balakin *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License as *
* published by the Free Software Foundation; either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
var obj;
var ctx;
var cw,ch;
var deg = Math.PI/180; //0.017453293;
function main()
{
ctx = document.getElementById("canvas").getContext("2d");
cw = document.getElementById("canvas").width;
ch = document.getElementById("canvas").height;
ctx.lineCap="round"; // global setting
mgl_init("alpha.json");
// mgl_init("alpha.jsonz");
var t1 = new Date();
mgl_draw_good(obj, ctx);
// draw_fast(obj, ctx);
var t2 = new Date();
document.getElementById("time").innerHTML = "Drawing time is "+(t2.getTime()-t1.getTime())+" ms. Number of primitives is "+obj.nprim+". Canvas size is "+obj.width+"*"+obj.height+" points.";
};
function mglChange()
{
var name = document.getElementById("select").value;
mgl_init(name+".json");
var t1 = new Date();
ctx.clearRect(0,0,cw,ch);
mgl_draw_good(obj, ctx);
// draw_fast(obj, ctx);
var t2 = new Date();
document.getElementById("time").innerHTML = "Drawing time is "+(t2.getTime()-t1.getTime())+" ms. Number of primitives is "+obj.nprim;
}
// mouse handling functions
function mglMouseUp()
{ obj.button = 0; obj.good = 0;
ctx.clearRect(0,0,cw,ch);
mgl_draw_good(obj, ctx); }
function mglMouseDown(event)
{
obj.good = 1;
obj.mouseX = event.clientX;
obj.mouseY = event.clientY;
obj.button = event.button+1;
}
function mglMouseMove(event)
{
var x = event.clientX-obj.mouseX;
var y = event.clientY-obj.mouseY;
switch(obj.button)
{
case 1: // rotate
mgl_rotate_down(obj, y*180/ch);
mgl_rotate_left(obj, x*180/cw); break;
case 2: // shift
mgl_shift_down(obj, y/ch);
mgl_shift_right(obj, x/cw); break;
case 3: // zoom
mgl_zoom_in(obj, Math.pow(1.003,x)); break;
}
if(obj.button)
{
obj.mouseX += x; obj.mouseY += y;
mgl_draw(obj, ctx);
}
}
function mglMouseWheel(event)
{
// var e = window.event;
var d = event.wheelDelta? event.wheelDelta:event.detail*(-120);
mgl_zoom_in(obj, Math.pow(1.002,d));
mgl_draw(obj, ctx);
}
function mglRestore()
{
mgl_restore(obj);
ctx.clearRect(0,0,cw,ch);
mgl_draw_good(obj,ctx);
}
// The function load data and set up rotation/zoom state
function mgl_init(name)
{
// now obtain JSON data
var req = new XMLHttpRequest(), txt;
req.open( "GET", name, false );
req.overrideMimeType('text\/plain; charset=x-user-defined');
req.send(null);
txt = req.responseText;
obj = JSON.parse(txt);
// copy original data for transformation
obj.pp = new Array();
for(var i=0;iMath.PI/2) t += Math.PI;
}
else t=0;
var c=Math.cos(t), s=Math.sin(t), d=prim[6]/200;
var b=[d*c, d*s, d*s, -d*c, obj.pp[n1][0],obj.pp[n1][1]];
var x=obj.coor[n2][0]*scl/100, y=obj.coor[n2][1]*scl/100, f=prim[8]*scl/1e5;
if(n3&8)
{
if(!(n3&4)) mgl_line_glyph(ctx, x,y, f,1,b);
else mgl_line_glyph(ctx, x,y, f,0,b);
}
else
{
if(!(n3&4)) mgl_fill_glyph(ctx, x,y, f,obj.glfs[n4],b);
else mgl_wire_glyph(ctx, x,y, f,obj.glfs[n4],b);
}
break;
}
}
// This function change coordinates according current transformations
// Usually this Function is called internally by draw()
function mgl_prepare(obj, skip)
{
// fill transformation matrix
if(!skip)
{
var dx = 1/Math.abs(obj.z[1]-obj.z[0]);
var dy = 1/Math.abs(obj.z[3]-obj.z[2]);
var cx=Math.cos(obj.tet*deg), sx=Math.sin(obj.tet*deg); // tetx
var cy=Math.cos(obj.phi*deg), sy=Math.sin(obj.phi*deg); // tety
var cz=Math.cos(obj.bet*deg), sz=Math.sin(obj.bet*deg); // tetz
obj.b = [obj.dx*dx*cx*cy, -obj.dx*dx*cy*sx, obj.dx*dx*sy,
obj.dy*dy*(cx*sy*sz+cz*sx), obj.dy*dy*(cx*cz-sx*sy*sz), -obj.dy*dy*cy*sz,
sx*sz-cx*cz*sy, cx*sz+cz*sx*sy, cy*cz,
cw/2*(1+dx-obj.z[1]-obj.z[0])/dx,
ch/2*(1+dy-obj.z[3]-obj.z[2])/dy, obj.depth/2, obj.dx*dx,obj.dy*dy,1];
}
// now transform points for found transformation matrix
var b = obj.b, i;
for(i=0;i=0) // TODO: check later when mglInPlot will be ready
obj.pp[i] = [b[9] + b[0]*x + b[1]*y + b[2]*z,
b[10] + b[3]*x + b[4]*y + b[5]*z,
b[11] + b[6]*x + b[7]*y + b[8]*z];
else
obj.pp[i] = [b[9]+b[12]*x,b[10]+b[13]*y,b[11]+b[14]*z];
}
if(obj.pf) for(var i=0;i=0) // TODO: check later when mglInPlot will be ready
{
obj.pp[i][0] = d*obj.pp[i][0] + (1-d)/2*obj.width;
obj.pp[i][1] = d*obj.pp[i][1] + (1-d)/2*obj.height;
}
}
// fill z-coordinates for primitives
if(!obj.fast)
{
for(i=0;i'
ctx.moveTo(x-s/2,y-s); ctx.lineTo(x-s/2,y+s);
ctx.lineTo(x+s,y); ctx.closePath();
ctx.stroke(); break;
// case 46: // '.'
default:
ctx.rect(x,y,1,1); ctx.fill(); break;
}
}
// This function for internal use only!!!
function mgl_fill_glyph(ctx, x,y, f,g,b)
{
var xx,yy,j;
var np=0; ctx.beginPath();
for(j=0;j