Недокументированные возможности программируемого микрокалькулятора МК-61

Оглавление


Предисловие

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

Целью использования недокументированных возможностей чаще всего является сокращение длины программы (самый "узкий" ресурс), реже – высвобождение дополнительных регистров памяти.
Большинство таких возможностей реализуется или используется только в контексте программного режима.
Иногда такие возможности используются для получения видео изображения, которое невозможно создать обычным способом.
Также знание недокументированных возможностей поможет понять, почему иногда программа ведёт себя не совсем так, или совсем не так, как ожидалось.

В данной документе не рассматривается получение и исследование сверхчисел и пустышек, которые исторически уже рассмотрены Еггогологией. Но из указанной Еггогологии взяты термины ЕГГ0Га и 3Г0ГГа, и кое-где используются для получения нестандартных чисел или результатов.

Далее для обозначения старших шестнадцатеричных цифр используется стандартное обозначение ABCDEF, при этом в примерах отображения используются вид цифр ПМК.
Например, число 8.FEDCBA9 (как инверсия числа 8.0123456), будет выглядеть как  8. EГCL-9   .
Следует понимать, что в ПМК шестнадцатеричные только цифры, но не числа, то есть ПМК по-прежнему считает каждый разряд как десятичный. Т.е. число FF, которое в обычном случае равно 255, будет интерпретироваться как F × 10 + F, т.е. 150 + 15 = 165.

Дробные числа или числа с большим порядком обычно записываются в научной нотации как ±m.mmmmmmmE±pp. Чтобы не путать знак экспоненты с шестнадцатеричной цифрой E далее такие числа будем указывать в форме ±m.mmmmmmm^±pp (^ вместо E), причем знак порядка будем указывать всегда, а для надежности и точку (десятичный разделить) для мантиссы. Например, 1.23^-02 будет выглядеть как  1.23     -02.

Т.к. ПМК может использовать не нормализованные числа, то это важно отличать, пример: не нормализованное 0.005^-03 это  0.005    -03, а оно же нормализованное – это 5.^-06  5.       -06. Не нормализованные числа в тексте так и будут записываться с ведущими нулями.


Программное адресное пространство

Документированное значение для адресов программы – 105 ячеек. Фактически всё немного сложнее. Напомним, что адресация ячеек использует только две цифры, поэтому для адресов более 99 используются шестнадцатеричные цифры.

Адрес формальныйАдрес фактическийДоп. адрес
0000
0101
9999
A0A0
A1A1
A2A2
A3A3
A4A4
A500
A601
A702
A803
A904
B005
B106
B200
B301
B402
B503
B604
B705
B806
B907
C008
C109
C210
C311
C412
C513
C614
C715
C816
C917
D018
D119
D220
D321
D422
D523
D624
D725
D826
D927
E028
E129
E230
E331
E432
E533
E634
E735
E836
E937
F038
F139
F240
F341
F442
F543
F644
F745
F846
F947
FA4801
FB4902
FC5003
FD5104
FE5205
FF5306

Первая побочная ветвь (возврат на нулевой адрес) короткая – адреса A5…B1, которые отображаются на адреса 00…06.

Вторая побочная ветвь длинней – адреса B2…F9, которые отображаются на адреса 00…37. Причем адреса C0…F9 являются темными, Например, если выполнить БП10FПРГ, и по адресам 10…12 ввести команды КНОПКНОПКНОП, то мы увидим  54 54 54 13. Но если затем перейти на адрес C5 (FАВТБПC5FПРГ), то получим          C5, хотя фактически этот будет тот же программный код.

На супертемные адреса FA…FF можно перейти только косвенно, т.к. впрямую адрес, начинающийся с цифры F не вбить. Впрочем, некоторые нестандартные последовательности команд в ручном режиме, описанные в конце приложения, позволяют вставлять команды, начинающиеся с F. А также для владельцев МК-52 есть штатный способ сделать "or" команд с отдельного модуля, с получением кодов с цифрой F.
Так вот, особенность супертемных адресов такая, что после перехода на него выполняется только одна(!) команда, а затем идёт переход на дополнительный адрес, который указан в последней колонке.

Если в адресе перехода вторая цифра тоже шестнадцатеричная, то как указано в предисловии, она прибавляется как есть (E = 14 и т.п.). Таким образом переход на адреса 9F (90 + 15) или A5 (100 + 5) или AC (100 + 12 = 110 + 2 = B2) или B2, будет эквивалентно переходу на адрес 00, только потом порядок команд будет отличаться.

Если сама команда двойная, т.е. с адресом перехода (например, БП, Fx<0, FL1), и при этом идёт "разрыв" адресации, то вторая часть команды (адрес перехода) считывается из нового места. Т.е. если по адресу 06 стоит команда БП, то в обычном потоке адрес перехода будет по адресу 07, но если попали сюда как на адрес B1, то адрес перехода будет по адресу 00.

Практическое применение


Косвенная адресация

Документированная возможность – регистр, используемый для косвенной адресации, содержит только целое неотрицательное число. Фактически значением его может быть любое число. Пусть число, которое содержится в регистре, который используется для косвенной адресации, записано в виде ±M1.M2M3M4M5M6M7M8^±P1P2, где M – цифры мантиссы, P – цифры порядка. При этом предполагается, что всегда 8 цифр мантиссы, просто последние могут быть нулевыми и обычно не отображаются.

При косвенном обращение сначала исходное число преобразуется по правилам, описанным ниже, а затем записывается обратно (для команд FFL0FFL3 иногда нет). При этом всегда(!) цифры M7M8 преобразованного числа содержат адрес перехода (или номер регистра). Обратите внимание, что преобразованное число не нормализуется перед сохранением обратно, тем самым могут быть числа с ведущими нулями. Обычно это используется или для генерации специальных изображений или в комбинации с другими недокументированными операциями, которым важны только цифры мантиссы (пример в разделе Таинственный регистр X2).

Вариант +M1.M2M3M4M5M6M7M8^+P1P2 , т.е. число больше или равно единицы (1.0 ⩽ N)

В этом случае к числу слева дописывается 7 − P2 нулей (только если результат неотрицательный), "выталкивая" остальную часть мантиссы право. Порядок числа (в нормализованном виде) при этом не меняется.
Для наглядности приведём таблицу преобразования на основе простой мантиссы, чтобы было видно, какие разряды куда попадают:

До 7-P2 Нулей После Оно же
 1.2345678    7 7  00000001.     1.          
 12.345678    6 6  00000012.     12.         
 123.45678    5 5  00000123.     123.        
 1234.5678    4 4  00001234.     1234.       
 12345.678    3 3  00012345.     12345.      
 123456.78    2 2  00123456.     123456.     
 1234567.8    1 1  01234567.     1234567.    
 12345678.    0 0  12345678.     12345678.   
 1.2345678 08 -1 0  1.2345678 08  1.2345678 08
 1.2345678 09 -2 0  1.2345678 09  1.2345678 09
 1.2345678 10 7 7  0.0000001 17  1.        10
 1.2345678 11 6 6  0.0000012 17  1.2       11
 1.2345678 96 1 1  0.1234567 97  1.234567  96
 1.2345678 97 0 0  1.2345678 97  1.2345678 97
 1.2345678 98 -1 0  1.2345678 98  1.2345678 98
 1.2345678 99 -2 0  1.2345678 99  1.2345678 99

Кстати, это правило показывает, что для обычных целых чисел, порядок которых (от нуля) совпадает с числом цифр − 1, слева дописывается такое число нулей, чтобы получилось 8 цифр. Т.е. число остается тем, что есть, что и соответствует документации. В случае, если кроме целой части есть и ещё и дробная, то же правило показывает, что дописываемые нули "вытеснят" дробную часть совсем, что часто используется в программах для сокращения: нет необходимости убирать дробную часть, т.к. косвенная адресация её сама уберёт.

Вариант +M1.M2M3M4M5M6M7M8^-P1P2 , т.е. само число неотрицательное, но меньше единицы (0.0 ⩽ N < 1.0)

В этом случае число дописываемых нулей определяется так. Вычисляется 1P2 − 3, и если последняя цифра результата < 8, то это и определяет число нулей. Иначе ничего не делается.

До 1P2−3 Нулей После Оно же
 1.2345678-01 8 0  1.2345678-01  1.2345678-01
 1.2345678-02 9 0  1.2345678-02  1.2345678-02
 1.2345678-03 10 0  1.2345678-03  1.2345678-03
 1.2345678-04 11 1  0.1234567-03  1.234567 -04
 1.2345678-05 12 2  0.0123456-03  1.23456  -05
 1.2345678-06 13 3  0.0012345-03  1.2345   -06
 1.2345678-07 14 4  0.0001234-03  1.234    -07
 1.2345678-08 15 5  0.0000123-03  1.23     -08
 1.2345678-09 16 6  0.0000012-03  1.2      -09
 1.2345678-10 7 7  0.0000001-03  1.       -10
 1.2345678-11 8 0  1.2345678-11  1.2345678-11
 1.2345678-12 9 0  1.2345678-12  1.2345678-12
 1.2345678-23 10 0  1.2345678-23  1.2345678-23
 1.2345678-24 11 1  0.1234567-23  1.234567 -24
 1.2345678-99 16 6  0.0000012-93  1.2      -99

Вариант -M1.M2M3M4M5M6M7M8^±P1P2 , т.е. само число отрицательное (N < 0.0)

В этом случае всё как указано выше, только дописываются не нули, а девятки. Знак самого числа сохраняется.
Пример: -123.         перейдет в -99999123.   , т.е. адрес перехода = 23.
Пример: -1.23     -02 не измениться, т.е. адрес перехода будет равен нулю.
Пример: -1.2345678-08 станет как -9.9999123-03, т.е. адрес перехода = 23.

Тут нужно сделать замечание. Есть предположение, что девятки не случайны, а равны 10 − 1, где 10 это знак минус (как шестнадцатеричная цифра A). Для проверки такого предположения возьмем число с цифрой вместо знака (см. Числа, у которых вместо знака стоит цифра для алгоритма получения). Например 2E.          , где двойка стоит на месте знака. При косвенной адресации: x→П9КП→x9П→x9. Двойка уменьшается на единицу и становится 21111111E.   . Аналогично для других чисел, например, если для 9E.           (которое после /-/) выполнить косвенную адресацию, то получим 98888888E.   .

Теперь рассмотрим ситуацию, когда само значение регистра ещё и меняется как описано в документации. Обращаю внимание, что изменения происходят не при любом косвенном обращении, а только когда значение адреса для перехода вычисляется. Например, в условном операторе Кx<04 регистр R4 будет модифицирован только когда x⩾0, т.е. когда понадобиться вычислить адрес перехода.

Регистры R0…R3

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

 123.        , что представляется как 1.23^02, перейдет в 0.0000123^+07, затем уменьшится до 0.0000122^+07 и превратиться в  00000122.   , т.е. адрес перехода = 22.
 1.23     -02, что представляется как 1.2300000^-02 уменьшится до 1.2299999^-02 превратиться в  1.2299999-02, т.е. адрес перехода = 99.
-123.        , что представляется как -1.23^+02, преобразуется в -9.9999123^+07, затем уменьшится до -9.9999122^+07, превратиться в -9.9999122.   , т.е. адрес перехода = 22.
-1.2345678-08, преобразуется в -9.9999123^-03, затем уменьшится до -9.9999122^-03, превратиться в -9.9999122-03, т.е. адрес перехода = 22.

Обратите внимание, что при уменьшении мантиссы до нуля порядок числа сохраняется. Например, если исходно было 1.^-20, то после преобразования будет 0.0000001^-13, а после уменьшения станет 0.0000000^-13, т.е.  0.      -13. Правда такой ноль, кроме необычного вида, в операциях ничем от простого нуля не отличается. Но в сочетании с другими недокументированными возможностями, которые могут объединить мантиссу и порядок от разных чисел (см. Таинственный регистр X2) это может пригодится. Таким же образом из 1.^90 получается  0.        97.

Регистры R4…R6

Для них преобразованное значение предварительно увеличивается на 1, так же без учета порядка и знака. При этом если было -99999999 (все девятки), то преобразуется в ноль. По аналогии с замечания для отрицательных чисел, число +99999999 (все девятки) преобразуется в число с цифрой на месте знака: 200000000.    (на месте знака стоит 2). Оно ведёт себя как обычный ноль. Примеры:

 123.        , что представляется как 1.23^+02, перейдет в 0.0000123^+07, затем увеличится до 0.0000124^+07 и превратиться в  00000124.   , т.е. адрес перехода = 24.
 1.23     -02, что представляется как 1.2300000^-02, увеличится до 1.2300001^+02 превратиться в  1.2300001-02, т.е. адрес перехода = 01.
-123.        , что представляется как -1.23^+02 преобразуется в -9.9999123^+07, затем увеличится до -9.9999124^+07, превратиться в -99999124.   , т.е. адрес перехода = 24.
-1.2345678-08, преобразуется в -9.9999123^-03, затем увеличится до -9.9999124^-03, превратиться в -9.9999124-03, т.е. адрес перехода = 24.

Команды FFL0FFL3

Для них действуют те же правила, что для регистров R0…R3, только перед уменьшением проверяется, что полученное число не равно единице (опять же без учета знака и порядка). В случае если получится единица, происходит завершение цикла и преобразованное значение НЕ записывается обратно в регистр. Примеры:

 123.        , что представляется как 1.23^+02, перейдет в 0.0000123^+07, затем уменьшится до 0.0000122^+07 и превратиться в  00000122.   , цикл продолжается.
 1.23     -02, что представляется как 1.2300000^-02 уменьшится до 1.2299999^-02 превратиться в  1.2299999-02, цикл продолжается.
 1.23        , преобразуется в 00000001, равно единице – цикл завершиться, а исходное число не измениться, т.е. останется  1.23        .
 1.23     -10, что представляется как 1.23^-10, преобразуется до 0.0000001^-03, равно единице – цикл завершиться, а исходное число не измениться.

Проверить "на единицу" исходное можно через регистры R7…Re. Если после КП→xRП→xR на экране мантисса будет 00000001 (и где-то точка), то значит цикл прервется на исходном числе.

Мантисса содержит шестнадцатеричные цифры

В этом случае уменьшение шестнадцатеричной цифры идёт до 9, а затем как обычно. При уменьшении ниже 0 идёт заём (уменьшение на 1 предыдущей, возможно так же шестнадцатеричной цифры), при это текущая цифра будет не F, а как обычно 9.

Переход так же будет на шестнадцатеричный адрес, но с этим мы уже знакомы (см. Программное адресное пространство).

К сожалению, если идёт увеличение, то число с шестнадцатеричными цифрами предварительно подвергается нормализации (справа налево, смысл как указано в списке терминов): буквы считаются как двузначные, с переносом лишней единицы в старший разряд. Например, сложение числа 9AE и 1 будет так: E = 14, значит последняя цифра 4 + 1 = 5 и 1 «в уме»; затем A = 10 + 1 в уме, будет 11, т.е. 1 и 1 в уме; далее 9 + 1 = 10. Итого 1015. Это значит, что косвенная адресация через R4…R6 всегда уберёт шестнадцатеричные цифры из числа. При этом нестандартный знак числа (см. замечание в варианте для отрицательных чисел) остается.

Порядок содержит шестнадцатеричные цифры

На самом деле без Еггогологии можно получить только 1.^±HH, используя оператор F10ˣ.

Для чисел вида 1.^+0H (одна цифра) никаких преобразований не производится, а значит адрес перехода всегда будет нулевым. Если идёт увеличение или уменьшение (через выбор регистра), то мантисса увеличивается или уменьшается в соответствии с правилами выше, а знак и порядок не меняется. Пример: если в регистр R0 записать  1.        0E и выполнить КП→x0, то результатом станет уменьшение мантиссы 100000000 до 09999999, т.е.  0.9999999 0E.

Для чисел вида 1.^-0H (тоже одна цифра) так же никаких преобразований не производится. Единственное, хочу заострить внимание на значении такого числа. В этом случае порядок подразумевается по модулю 16, т.е. -E, это -14 или 2. Получается, что 1.^-0E = 100, 1.^-0D = 1000, 1.^-0C = 10000, 1.^-0B = 100000, а... 1.^-0A не получается (будет сразу 1.^-10, им и останется).

Для двузначных чисел порядка ситуация сложнее. Возьмем, для примера 1.^+B0 ( 1.       L0). Кстати, 1.^+AA будет автоматически переведено в 1.^+B0, как и 1.^+0A будет автоматически переведено в 1.^+10 (чем-то именно A не нравиться, для остальных цифр такого не замечено, хотя и тут можно обмануть, если для 1^.-LL набрать ВП55). Так вот, по сути это число равно 1.^+110. Как видно, вместо знака порядка используется цифра, но для ПМК это тоже, что и знак, поэтому будет применено правило для отрицательных порядков, то есть допишется слева 7 (= 10 − 3 и меньше 8) нулей, что приведёт к 0.0000001^117. Т.е. в качестве адреса перехода будет 01, но само число из регистра уже лучше не извлекать, т.к. оно будет сверхчислом и на экране отображаться как ЕГГ0Г.

Если у двузначных чисел порядок отрицательный, то срабатывает правило "сумма = 160", это значит, что внутренне порядок меняется на число по модулю 160. Для примера, возьмем 1.^-C0 ( 1.       -C0). Это эквивалентно 1.^-120, а по правилу "сумма = 160", как 1.^+40. Используя правило преобразования для чисел больше единицы, получаем, что число будет преобразовано в 0.0000001^+47, соответственно адрес перехода 01, а полученное после косвенной адресации число можно просто извлечь из регистра: будет  0.0000001 47 Для сравнения, если взять 1.^-C1, то правило "сумма = 160", приведёт к 1.^+39, что не поддаётся преобразованию (9 > 7), а значит само число после косвенной адресации не измениться и останется  1.       -C1 (адрес перехода равен 00). И, да, к этому числу можно прибавить 0, чтобы увидеть  1.        39.

У правила "сумма = 160" возникает ещё один интересный побочный эффект. Детали см. в разделе Числа с отрицательной нулевой степенью.

Если идёт увеличение или уменьшение (через выбор регистра), то мантисса увеличивается или уменьшается в соответствии с правилами, уже описанными выше.

Значение – номер регистра, а не адрес

Если косвенное значение представляет не адрес, а номер регистра, то его номер также определятся двумя последними цифрами, разбиваясь на два варианта, когда первая цифра нулевая, и когда нет:
Первая нольПервая НЕ ноль
00→R0#0→Ra
01→R1#1→Rb
02→R2#2→Rc
03→R3#3→Rd
04→R4#4→Re
05→R5#5→R0
06→R6#6→R0
07→R7#7→R1
08→R8#8→R2
09→R9#9→R3
0A→Ra#A→R4
0B→Rb#B→R5
0C→Rc#C→R6
0D→Rd#D→R7
0E→Re#E→R8
0F→R0#F→R9

Практическое использование

Вот пример использования знаний косвенной адресации для номеров регистров: программа заполняет регистры R0…R3 инвертированными случайными числами, при этом используется только один регистр R0.

00.Сx01.x→П002.КСЧ03.КИНВ04.Кx→П005.П→x006.Fx≥007.0208.С/П

Ещё практических советов. Уменьшение шестнадцатеричной цифры для БЗ-34 было единственным способом из цифры E получить остальные цифры. В МК-61 с приходом бинарных операций стало проще, но т.к. первая цифра всё ещё не во власти этих операций, то через косвенную адресацию получается быстрее, чем через дробную часть и ввод порядка.

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


Таинственный регистр X2

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

В документации указано, что таковым является регистр X, но это не так. В режиме вычислений после каждой команды содержимое регистра X сразу же копируется в X2. И поэтому разницы между ними нет. При таком копировании производятся дополнительные проверки (например, на предмет переполнения). Видимо в целях экономии в программном режиме при выполнении большинства(!) команд такое копировании не производится. Более того (а это и есть самое полезное), некоторые команды позволяют сделать и обратное копирование.

X2-влияющие команды

Для начала опишем список команд, которые копируют содержимое X в X2 (после выполнения, конечно). Назовем такие команды X2-влияющие. Безусловно выполняют копирование X→X2 следующие команды: Выполняют копирование только при НЕ переходе на адрес следующие команды: Для условных операторов это означает, что копирование X→X2 производится, когда условие выполняется. Для циклов – когда цикл завершается.

Остальные операторы НЕ копируют X в X2. А это, например, означает, что регистр X во время таких операций может содержать сверхчисло (больше или равно 10100). При этом, конечно, нужно учитывать, чтобы команда сам по себе не делала такую проверку на переполнение. Например, F10ˣ делает проверку параметра перед выполнением, а F – нет. Для пояснения рассмотрим такую программу:

00.КНОП01.502.003.F10ˣ04.F05.Fx=006.0707.Сx08.С/П

Можно увидеть, что на шаге 04 в регистре X возникает сверхчисло. Причем оператор 05.Fx=0 не выполняется, т.е. идёт переход на адрес 07 (как адрес перехода). В соответствии с таблицей выше, в этом случае копирование X в X2 не происходит, а значит, ошибки не должно возникать. В чем легко убедиться, запустив программ. Но если в программе заменить оператор на 05.Fx≠0, то условие уже будет выполняться, и будет выполняться копирование X в X2, которое приведёт к ошибке. Что также проверяется запуском программы.

Хочу отметить, что команда В/О также является X2-влияющей, т.е. если перед возвратом из подпрограммы в регистре X будет сверхчисло, то произойдёт остановка по ошибке, причем возврат успеет отработать.

Всё это не имело бы практического смысла, если бы не было команд, которые делают обратное копирование X2→X.

Команда . (код 0A)

Данная команда в программном режиме восстанавливает в регистре X значение X2 (за исключением случая, когда идёт обычный ввод числа, например, 1.23, в этом случае поведение полностью соответствует документации и эквивалентно режиму вычислений). При этом содержимое стека не меняется. Также команда ничего не делает после X2-влияющей команды, но это и логично. На практике такую команду чаще всего используют для экономии регистров, реже – когда требуется ввод без изменения стека.
Пример: проверка битового флага и если его нет, то его установка. Пусть в R9 храниться некое число для работы с битами, а в R1 бит для проверки, тогда следующий фрагмент сначала сделает проверку доступности бита, а при недоступности, сделает его установку:

00.П→x901.П→x102.К03.К{x}04.Fx≠005.77  …  77..78.К79.x→П9

Получается, что оператор . по адресу 77 восстановит R1 в X без сдвига стека, что позволит сразу выполнить бинарную операцию. Обычно само значение из R1 "вычислено", а не храниться в регистре. Обратите внимание, что выбрана не X2-влияющая проверка с переходом.

Ещё пример – использовать оператор . как входной параметр подпрограммы, т.е. эта команда стоит первым оператором. В этом случае, например, после ввода пользователя (после команды С/П, которая всегда копирует X в X2), сначала выполняются некие вычисления, не затрагивающие X2 (в том числе сохранение этих результатов в регистры), а затем вызов подпрограммы, которая таким образом "видит" ввод пользователя.

Команда ВП (код 0C)

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

Итог

Причиной нестандартного поведения команд ВП и . является то, что подпрограммы ввода числа в ПМК, в том числе его порядка, работают напрямую с регистром X2. Рассмотрим пример, который затрагивает другую команду ввода числа:

01.B↑02.ВП03.204./-/05.С/П

Очевидно, что тут произойдёт деление на 100. Этот код ведёт себя в соответствии с документацией. Немного дополним его:

01.B↑02.ВП03.204.Fπ05.F06.F07./-/08.С/П

Если посмотреть, выглядит так, что после ввода порядка (умножения на 100) мы берем ещё π, два раза возводим его в квадрат, а затем делаем отрицательным. На самом деле это фрагмент делает то же, что и первый! Операции ввода числа работают с X2, в промежутке все команды – не X2-влияющие, они, конечно, будут выполнены, но результат будет отброшен при выполнении команды /-/, которая продолжит ввод числа (в данном случае порядка) в регистре X2. То, что промежуточные команды выполнялись, можно узнать по содержимому стека – в регистре Y будет входное значение, умноженное на 100, а в X1 – квадрат от π.

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

  1. Он самый быстрый, действует мгновенно, а не "задумываясь".
  2. Он не пропускает в программном режиме лишнюю команду, как делают все остальные операции, вызывающие ошибку (ах да, это тоже не документировано).

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

01.B↑02.F03.ВП04.F05.F06..07.F08.С/П

остановится по ошибке уже на команде . (т.е. следующим для исполнения будет адрес 07) и успеет возвести в квадрат только дважды (первый квадрат, как не X2-влияющий будет отброшен по команде ВП).

Шестнадцатеричная арифметика

Речь идёт об обычных арифметических операциях, но в ситуации, когда операнд представляет собой число, содержащее шестнадцатеричные цифры A…E (F убрано из рассмотрения как опасное, по крайней мере в качестве первой цифры).

Для простоты рассмотрим ситуации, когда число состоит из одной шестнадцатеричной цифры. Далее будем её обозначать буквой H. Для более ясного порядка операндов будем использовать стандартные обозначения X и Y.

Операция H + Y

YH ABCDE
0 01234
1 12345
2 23450
3 34501
4 45012
5 50123
6 01234
7 12345
8 23456
9 34567
A 45678
B 56789
C 678910
D 7891011
E 89101112
 10 |  1021222424
11 2122232425
12 2223242526
13 2324252627
14 2425262728
15 2526272829
16 2627282930
17 2728293031
18 2829303132
19 2930313233
1A 3031323334
1B 3132333435
1C 3233343520
1D 3334352021
1E 3435202122
1F 3520212223

Вычисление идёт как шестнадцатеричное, остаток по модулю 16, а от него берётся последняя цифра (чтобы осталась одна). X = ((X + Y) mod 16) mod 10.
Если Y двузначное, то уже две цифры X = ((X + Y) mod 256) mod 100, а значит получается как обычное сложение, если число двузначное.
Если Y дробное, то целая часть как выше, а дробная сохраняется.

Операция X + H

В этом случае осуществляется обычное сложение, только результат нормализуется. Например: 0 + A = 10, 9 + E = 23. В случае шестнадцатеричного X см. таблицу выше.

Операция Y - H

YH ABCDE
0 -10-1-2-3-4
1 -9-10-1-2-3
2 -8-9-10-1-2
3 -7-8-9-10-1
4 -6-7-8-9-10
5 -5-6-7-8-9
6 -4-5-6-7-8
7 -3-4-5-6-7
8 -2-3-4-5-6
9 -1-2-3-4-5
A 0-1-2-3-4
B 10-1-2-3
C 210-1-2
D 3210-1
E 43210
 10 |  015141312
11 116151413
12 217161514
13 318171615
14 419181716
15 520191817
16 621201918
17 722212019
18 823222120
19 924232221
1A 025242322
1B 110252423
1C 211102524
1D 312111025
1E 413121110
1F 514131211

Выглядит похоже на X = (Y − X) mod 16, но не всегда понятно, когда берётся заём, а когда нет. Число A какое-то особенное для двузначных.
Для трех- и выше значных повторяется как для двузначных, т.е. 100 − A = 90, 100 − B = 105,… , 109 − E = 111.

Операция H - X

XH ABCDE
0 1011121314
1 90123
2 89012
3 78901
4 67890
5 56789
6 45678
7 34567
8 23456
9 12345
A 01234
B -10123
C -2-1012
D -3-2-101
E -4-3-2-10
 10 |  01234
11 -10123
12 -2-1012
13 -3-2-101
14 -4-3-2-10
15 -5-4-3-2-1
16 -6-5-4-3-2
17 -7-6-5-4-3
18 -8-7-6-5-4
19 -9-8-7-6-5
1A -10-9-8-7-6
1B -1-10-9-8-7
1C -2-1-10-9-8
1D -3-2-1-10-9
1E -4-3-2-1-10
1F -5-4-3-2-1

Нарушения обычного вычитания только для нескольких чисел (выше и правее, начиная с B − 1, для двузначных ниже и левее A − 1B).
Кстати, особенность: когда из шестнадцатеричного числа вычитается его десятичный аналог, то ноль, на самом деле, не нормализованный (т.е. из 4-х значного будет 0000), и это можно использовать для получения нуля в любой степени. Пример (в ручном режиме):

  1. Получим цифру E документированным образом: 11КИНВК{x}ВП1К[x], или можно нестандартным: 1К×ВП
  2. Добавим порядок, который хотим получить: ВП99
  3. Затем скопируем в стек: В↑В↑СX
  4. Выполним сложение (с нулем) и вычитание: +-

И вот мы получили  0.        99.

Операция H × Y

YH ABCDE
0 00000
1 101010100
2 202020200
3 303030300
4 404040400
5 505050500
6 606060600
7 707070700
8 808080800
9 909090900
A 000000000
B 101010100
C 202020200
D 303030300
E 404040400
 10 |  100 1001001000
11 1101101101100
12 1201201201200
13 1301301301300
14 1401401401400
15 1501501501500
16 1601601601600
17 1701701701700
18 1801801801800
19 1901901901900
1A 2002002002000
1B 2102102102100
1C 2202202202200
1D 2302302302300
1E 2402402402400
1F 2502502502500

Поразительное однообразие, почти все ведут себя как 10. Не ясно, чем так отличается E, но ноль получается и для многозначных чисел.
Для двузначных соответственно, т.е. C × 20 = 200, но E × 20 = 0.

Операция X × H

XH ABCDE
0 00000
1 01234
2 4681012
3 4142310
4 8202024
5 5011325342
6 022445040
7 1033406354
8 2044526068
9 3055647382
A 0010203040
B 0010203040
C 0010203040
D 0010203040
E 00000
 10 |  0010203040
11 1021324354
12 0416284052
13 1411245350
14 082220504
15 990021052923922
16 000032904920920
17 010043900933934
18 020054912930948
19 030905924943962
1A 940960980020
1B 940960980020
1C 940960980020
1D 940960980020
1E 0010203040
1F 0010203040

Тут уже трудно поддаётся логике. На практике автор как-то использовал D. С одной стороны, это изображение, с другой – коэффициент 10 (см. таблицу H × Y), а самое основное – это проверка битового сдвига. При умножении дробной части, содержащей 1, 2, 4, 8 (как бы биты) возможен выход за диапазон, т.е. 0.5 или 1.6. Так вот, при умножении на D по указанной таблице, результат получался из одной цифры, если всё нормально, или из двух, при выходе за диапазон.

Для двухзначных результат бывает не нормализованным: обратите внимание на ведущие нули в некоторых случаях. На этом фоне 14 × E выглядит как белая ворона.

Операция H ÷ X

XH ABCDE
0 ЕГГ0ГЕГГ0ГЕГГ0ГЕГГ0ГЕГГ0Г
1 01234
2 55.566.57
3 3.33333333.666666644.33333334.6666666
4 2.52.7533.253.5
5 22.22.42.62.8
6 1.66666661.833333322.16666662.3333333
7 1.42857141.57142851.71428571.85714282
8 1.251.3751.51.6251.75
9 1.11111111.22222221.33333331.44444441.5555555
A 11.11.21.31.4
B 8.4444443^-0111.25252521.34343431.4343434
C ЕГГ0ГЕГГ0Г11.231.3
D 4.^-016.^-018.^-0111.2
E 5.2929292^-012.2929292^-015.2929292^-08.2929292^-011
 10 |  0.^-011.^-012.^-013.^-014.^-01
11 9.090909^-010.^-010.9090909^-011.8181818-012.7272727-01
12 8.3333333^-019.1666666^-010.^-010.8333333-011.6666666-01
13 7.6923076^-018.4615384^-019.2307692^-010.^-010.7692307-01
14 7.1428571^-017.8571428^-018.5714285^-019.2857142-010.^-01
15 6.6666666^-017.3333333^-018.^-018.6666666-019.3333333-01
16 6.25^-016.875^-017.5^-018.125-018.75-01
17 5.8823529^-016.4705882^-017.0588235^-017.6470588-018.2352941-01
18 5.5555555^-016.1111111^-016.6666666^-017.2222222-017.7777777-01
19 5.2631578^-015.7894736^-016.3157894^-016.8421052-017.368421-01
1A 5.^-015.1^-016.3157894-016.1^-017.^-01
1B 5.0330001^-016.^-017.0001032-017.8330001-018.2330001-01
1C 6.^-017.0003809^-017.8100038-018.1810003-018.100038-01
1D 7.0005899^-017.9000589^-018.^-019.0005899-019.9000589-01
1E 5.64^-015.90002^-016.4^-016.60002-017.24-01
1F 5.4000299^-015.3223099^-016.153223-017.0002999-017.7000299-01

Здесь кроме случая X = 1, и некоторых двузначных это обычное деление нормализованного шестнадцатеричного числа. Пример: E ÷ 7 = 14 ÷ 7 = 2.
Правда и здесь встречаются не нормализованные числа (пример D ÷ 12, или C ÷ 11, которое на порядок меньше, чем A ÷ 11).
Встречаются и не нормализованные нули, пример: С ÷ 12, и выглядит, как указано:  0.       -01. Впрочем, в косвенной адресации мы уже встречались с подобными нулями.

Операция Y ÷ H

YH ABCDE
0 9.090909^-019.9099099^-014.44444439.9099099^-019.9099099^-01
1 ЕГГ0Г9.099099^-019.9099099^-019.9099099^-019.9099099^-01
2 ЕГГ0Г8.4444443^-019.099099^-019.9099099^-019.9099099^-01
3 ЕГГ0Г6.4444443^-01ЕГГ0Г9.099099^-019.9099099^-01
4 ЕГГ0Г4.4444443^-01ЕГГ0Г8.^-019.099099^-01
5 ЕГГ0Г2.4444443^-01ЕГГ0Г0.^-012.929292^-02
6 ЕГГ0Г6.4444443^-01ЕГГ0Г2.^-013.2929292^-01
7 ЕГГ0Г4.4444443^-01ЕГГ0Г4.^-016.2929292^-01
8 ЕГГ0Г2.4444443^-01ЕГГ0Г09.2929292^-01
9 ЕГГ0Г0.4444443^-01ЕГГ0Г2.^-012.2929292^-01
A 18.4444443^-01ЕГГ0Г4.^-015.2929292^-01
B 1.11ЕГГ0Г6.^-012.2929292^-01
C 1.21.252525218.^-015.2929292^-01
D 1.31.34343431.2318.2929292^-01
E 1.41.43434341.31.21
 10 |  ЕГГ0Г9.099099.90990999.90990999.9099099
11 ЕГГ0Г9.099099ЕГГ0Г9.89.0292929
12 ЕГГ0Г9.099099ЕГГ0Г09.3292929
13 ЕГГ0Г9.099099ЕГГ0Г0.29.6292929
14 ЕГГ0Г9.099099ЕГГ0Г0.49.9292929
15 ЕГГ0Г9ЕГГ0Г90.22922929
16 ЕГГ0Г9.2525252ЕГГ0Г9.20.5292929
17 ЕГГ0Г9.3434343ЕГГ0Г9.49.2292929
18 ЕГГ0Г9.4343434ЕГГ0Г9.69.5292929
19 ЕГГ0Г9.5252525ЕГГ0Г9.89.8292929
1A ЕГГ0Г9.69.99099099.99099099.9909909
1B ЕГГ0Г9.85252529.99099099.99099099.9909909
1C ЕГГ0Г9.94343439.99099099.99099099.9909909
1D ЕГГ0Г0.03434349.99099099.99099099.9909909
1E ЕГГ0Г9.0990999.99099099.99099099.9909909
1F ЕГГ0Г7.44444439.99099099.99099099.9909909

Тут логики не наблюдается. Очень похожие, но разные числа: 9.099099^-01 и 9.9099099^-01 (или 9.099099 и 9.9099099). Также интересно, что ноль разделить на H вовсе не ноль.
А самое главное, тут не просто ЕГГ0Г, а плохой ЕГГ0Г, который ранее не встречался. После его появления ПМК в дальнейшем (до выключения ПМК) при выполнении многих операций всегда выдаёт ЕГГ0Г. Вот список таких операций.

Ошибка возникает как в режиме вычислений, так и в программном режиме. Интересно, что F выдаёт ошибку, а FВx – нет.

"Синии" функции работают нормально. Видимо такой ЕГГ0Г "сводит с ума" только один микроконтроллер.

Результат функций над шестнадцатеричными числами

HF(H) ABCDE
F 001020300
F 3.16227763.31662473.46410163.60555123.7416573
F1/x ЕГГ0Г9.099099^-019.9099099^-019.9099099^-019.9099099^-01
F 22026.46759874.133162754.78442413.371202604.3
F10ˣ 1.^+101.^+0L1.^+0C1.^+0Г1.^+0E
Flg 141.82368142.4027442.81635443.126564
Fln 2.302585196.30258597.63591898.58829999.302585
F 106.6631773^+412.5277867^+426.551706^+421.3383338^+43
К[x] 01234

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

Практическое применение

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

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


Числа, у которых вместо знака стоит цифра

Рассматривается работа с числами, у которых вместо знака стоит цифра. В дальнейшем будем её называть знакоцифра. Автору известны только способы получения чисел, где знакоцифра получается из диапазона 2…9. Более того, есть основание думать, что кроме них допустимы только ещё только две цифры: A = минус, представляет отрицательные числа, и 1, которая отображается как пустое место и представляет положительные числа. Для ПМК БЗ-34 было возможно отображение и других цифр, но в МК-61 это не работает.

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

Способы получения

  1. Хвосты 0C-оборотней. В этом случае используются знания Еггогологии. 0С-оборотни – это числа в диапазоне 1.^400…9.9999999^499. Для получения таких чисел (точнее "хвостов") воспользуемся программой:

    00.КНОП01.502.003.F10ˣ04.F05.F06.F07.×08.x→П909.П→x910.П→xc11.ВП12.713.С/П

    На вход ей передатся число (0…8.9999999], на выходе получаем "хвост" оборотня из регистра Rc. Поэтому их и называют 0C-оборотни, что сами они на экран дают ноль, а "хвост" сбрасывают в регистр Rc. Передавать на вход девятку неинтересно, т.к. на выходе получим 10, точнее цифру А, т.е. обычный минус.
    Пример, передав 1, получим 2E.          , передав 6, получим 7E.          , и т.д. Нажав /-/ получим другую знакоцифру (вычислить какую именно легко, т.к. сумма таких парных знакоцифр = 11). Кстати, если мантисса нулевая, то после /-/ будет -0 для любой знакоцифры.

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

  2. Использование косвенной адресации регистров R4…R6. О том, что при использовании шестнадцатеричных чисел в косвенной адресации для R4…R6 идёт их "нормализация" уже было упомянуто в Мантисса содержит шестнадцатеричные цифры .

    Так вот, если первая цифра 8-значного числа тоже шестнадцатеричная, то перенос из старшего разряда происходит в знаковый разряд, т.е. появляется знакоцифра.

    Для начала возьмем обычную цифру E (для краткости нестандартным способом), которая потом пригодится: 1К-ВПx→Пe. Затем сделаем её первой (число 8-значным): ВП7x→П4. Получили  E0000000.   . Если теперь использовать косвенное увеличение: КП→x4П→x4, то получим 240000001.   . Тут из цифры E сделан перенос единицы в знаковый разряд. В отличии от 0С-оборотней мы пока получили только знакоцифру два (или 9, если нажать /-/), зато остальные цифры мантиссы легко выбрать нужные (кроме шестнадцатеричных, которые косвенной адресацией нормализуются), а с помощью восстановление X2 с заменой первой цифры на цифру из числа в X можно и первую цифру сделать шестнадцатеричной. Введем простую программу:

    00.<->01.КНОП02.ВП03.С/П

    Эта программа меняет у числа в X первую цифру на первую цифру из числа в Y. Соответственно, возьмем цифру E и наше новое число со знакоцифрой два и объединим их: П→xeП→x4В/ОС/П. Получили 2E0000001.   . Если такое число провести через косвенную адресацию: x→П4КП→x4П→x4, то получим 340000002.   . Таким же образом можно получить остальные знакоцифры (до 9 включительно). Кому эстетически не нравиться хвостик (в данном случае 2), его можно сбросить до нуля, уже через косвенную адресацию с использованием R0…R3.

Какой знак у числа?

Первый вопрос, который приходит на ум, глядя на такие числа, а они положительные или отрицательные? В дальнейшем для обозначения будем использовать 2N, 3N, … 9N, где знакоцифра указана явно, а мантисса (может + порядок) обозначена буквой N.
2N 3N 4N 5N 6N 7N 8N 9N
Fx<0 НетНетНетДаДаДаДаДа
Fx≥0 ДаДаДаДаДаНетНетНет

Как видно, далеко не всё так прозрачно. 5N и 6N получаются и отрицательные и положительные. Сравнения с нулем, кстати, для всех проходят корректно (не ноль, если N не ноль).
Рассмотрим другие функции, для косвенного определения знака числа.

Арифметика с этими числами

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

Тут нужно остановиться на нуле. Как указано в функции К{x}, может получится ноль со знакоцифрой. Так вот, он ведёт себя как обычный ноль. Единственное отличие – это в функциях сравнения. Такой ноль ведёт себя так, как указано ранее для чисел со знакоцифрой. В частности число 50.           операторы Fx<0, Fx≥0, Fx=0 пропустят, как удовлетворяющее условию.

Произвольная мантисса и выводы

Как видно, почти все операции уничтожают знакоцифру. Гарантированная доступная возможность менять мантиссу без изменения знакоцифры – это использовать косвенную адресацию. И конечно мантиссу можно заранее подготовить. Пример (использую программу и цифру E, полученную выше):

П→xe99999998В/ОС/П Получили  E9999998.   . Затем x→П4КП→x49П→x4В/ОС/П. Получим 299999999.    (Кстати, если нажать /-/, то получим девять девяток). Теперь снова сделаем над ним операцию x→П4КП→x4П→x4. Мы увидим довольно симпатичный не нормализованный ноль 300000000.   , которому можно поставить любую первую цифру (но не знакоцифру), как сделано только что по программе. Кстати, если бы мы так сделали с девятью девятками, то получили бы -00000000.   .

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

Например, хотим получить 321, где 3 – знакоцифра. Используя короткую программу, указанную в начале раздела, у числа  10000019.   , заменим первую цифру на E, получим  E0000019.   . После косвенного увеличения станет 240000020.   . Снова заменим на E, получим 2E0000020.   . Повторное косвенное увеличение приведёт к 340000021.   . Теперь, после замены первой цифры на ноль, останется 321.         .

Ещё пример. Хочу получить π/2 (для работы с тригонометрическими функциями), и знакоцифру девять. π/2 = 1.5707963. Начнем с  15707962.   , заменим первую цифру на E, получим  E5707962.   . После косвенного увеличения станет 245707963.   . Нажмем /-/, получим 945707963.    и сделаем замену первой цифра на 1: 915707963.   . Осталось только ВП7/-/x→П1, и мы получили, что хотели 91.5707963   . Можно проверить тригонометрические функции на нем, например синус о него даёт -1.

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

  1. Ставим первой шестнадцатеричную цифру (E из Re) по минипрограмме.
  2. Если все цифры мантиссы уже шестнадцатеричные, то завершаем.
  3. Уменьшаем с помощью ВП1/-/ порядок на единицу.
  4. У полученного значения через косвенную адресацию в R7…Rd убираем дробную часть. При этом вместо первой цифры будет вписано знакоцифра − 1, а наша шестнадцатеричная цифра отодвинется. Переходим на п.1.

По такому алгоритму и цифре E легко получается 2EEEEEEEE.   .

Понятно, что в пункте 1 алгоритма можно вписать любую цифру (кроме E). И начать с числа с любой знакоцифрой. Таким образом можно получить практически любую мантиссу с любой знакоцифрой (кроме цифр F или ведущих нулей).

Более того, такие знакоцифры влияют и на остальное. Возьмем, для примера Восстановление X2 с отбрасыванием первой цифры . На самом деле указанная там последовательность при восстановлении вместо первой цифры записывает знакоцифру − 1, что для положительного числа равно нулю, т.е. отбрасывание цифры. А вот если знакоцифра есть, то эта последовательность запишет её минус один вместо первого знака. Отсюда же понятно, почему для отрицательных запишет 9, это знак минус, как цифра A (= 10) минус один. Причем знакоцифра останется на месте.

На практике автор не использовал такие числа просто в силу того, что в момент активного использования ПМК не обладал этими знаниями. И хотя нестандартность поведения многих функций с такими числами определяется только реакцией на знак числа, всё равно можно предположить, что и это можно использовать для оптимизации.


Числа с отрицательной нулевой степенью

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

Способ получения

Как можно получить число в такой степенью? Для начала получим число с отрицательной степенью, содержащей шестнадцатеричные цифры. Например 1^.-E0 ( 1.       -E0): 10/-/К-ВПF10ˣ. Получилось число как 1.^-140 или по правилу 160 = 1.^20. Теперь если убрать ещё 20 из порядка: ВП20/-/x→П1. то мы получим 1.^-00 ( 1.       -00).

Если вы думаете, что мантисса может быть только единица, то обрадую, что можно и другие. Для исследования чисел с разными мантиссами приведем программу, которая регистры R1…Re заполняет числами с нулевой отрицательной степенью, а на месте первой цифры будет стоять номер регистра (да, да, для последнего это будет E.^-00).

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 1 3 x→П0 1 8 К К{x} /-/ ВП
 10 |  2 F10ˣ В↑ ВП /-/ 5 0 x→П1 0 <->
 20 |  КНОП ВП П→x1 <-> КНОП ВП x→Пe Кx→Пe FL0 18
 30 |  С/П

Тут тоже используются недокументированные возможности. Большую часть (адреса с 00 по 17) это получение (документированное) той же 1^.-00, что мы сделали руками, только без ЕГГ0Га. Причем не через E0, а через L0 (просто число 13 нужно для цикла по оставшимся регистрам R2…Re). Из-за особенностей поведения ВП в программном режиме X нужно копировать в X2. Первый раз нам помогает /-/ по адресу 08, второй раз уже явно делаем В↑ по адресу 12. А вот с 18 адреса начинается "шаманство". Фрагмент программы 18…21 увеличивает текущую цифру (в шестнадцатеричном смысле). Эта особенность была рассказана в разделе про регистр X2 и команду ВП. А фрагмент 22…25 (тот же раздел) подставляет эту цифру в число из R1. Потом с помощью регистра Re мы сохраняем это в нужном месте и повторяем.

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

Сложение и вычитание

Тут порядок операндов неважен. Правило такое: если целая часть второго операнда состоит только из одной цифры (сюда же попадают шестнадцатеричные цифры и дробная часть может быть любая), то этот операнд (т.е. всё число) сначала умножается на 10 (то же и для шестнадцатеричных чисел), а затем выполняется операция с N. Примеры:
2.^-00 + 5.1 = 53: П→x25.1+ =  53.         .
2.1 − 3.^-00 = 18: 2.1П→x3- =  18.         .
π − 3.^-00 = 28.415926; FπП→x3- =  28.415926   .
E.1 + 2^.-00 = 43: 1.1К-ВПКНОПП→x2+ =  43.         .

Для двух и более значных операндов, выполняется как обычно. Примеры:
2.^-00 + 51 = 53: П→x251+ =  53.         .
21 − 3.^-00 = 18: 21П→x3- =  18.         .
eπ − 3.^-00 = 20.14069: FπFП→x3- =  20.14069    .
E1 + 2^.-00 = 43: 11К-ВПКНОПП→x2+ =  43.         .

Если дробное число (целая часть нулевая), то выполняется как обычно. Примеры:
0.5 + 4.^-00 = 4.5: 0.5П→x4+ =  4.5         .
E.1^-01 + 4.^-00 = 5.41: 0.9FК-ВПКНОПП→x4+ =  5.41        .

Сами с собой числа N правила умножения на 10 не придерживаются и выполняются как обычно.
5^.-00 + 2.^-00 = 7: П→x5П→x2+ =  7.          .
2^.-00 − 3.^-00 = -1: П→x2П→x3- = -1.          .

Так же как обычно работает, если второй операнд нулевой.
9.^-00 + 0 = 9: П→x90+ =  9.          . Для сравнения:
9.^-00 + 1 = 9: П→x91+ =  19.         .

Умножение и деление

При умножении, когда число N перед операцией находится в регистре Y, а число в регистре X меньше единицы (в абсолютном значении), то получается число со степенью -160, т.е. само число N в операции воспринимается как число в степени -160 (кстати, с учетом цикличности ПМК на 1000 степени можно считать это числом в 840 степени). В остальных случаях ведёт как обычное умножение. Пример:
1.5 × 2^.-00 = 3: П→x21.5× =  3.          .
0.7 × 2^.-00 = 1.4^-160: П→x20.7× =  0.          . Вроде бы ноль, но это потому, что при нормализации числа в степени такого порядка преобразуются в ноль. Для проверки составим программу, которая полученное число умножит на 1^.80: 01.П→x202.003..04.705.×06.807.008.F10ˣ09.×10.С/П
После В/0С/П на экране будет  1.4      -80

Если число в X больше единицы, но имеет порядок не меньше 60, то число N снова воспринимается, как число в степени -160.
1.^62 × 7^.-00 = 7.^-98: П→x762F10ˣ× =  7.       -98.
1.^60 × 7^.-00 = 7.^-100: П→x760F10ˣ× =  0.          .
-1.^99 × 7^.-00 = -7.^-61: П→x799F10ˣ/-/× = -7.       -61.

Если число в X меньше единицы, но имеет порядок меньше -40, то умножение снова проводится как обычно:
1.^-43 × 6^.-00 = 6.^-43: П→x643/-/F10ˣ× =  6.       -43.
1.^-40 × 6^.-00 = 6.^-200: П→x640/-/F10ˣ× =  0.          .
-1.^-99 × 6^.-00 = -6.^-99: П→x699/-/F10ˣ/-/× = -6.       -99.

Напомню, если число N находится в регистре X, то происходит обычное умножение, но если в Y тоже N (может другое), то будет как выше степень -160.
3^.-00 × 7^.-00 = 2.1^-159: П→x3П→x7× =  0.          .
Для проверки 01.П→x302.П→x703.×04.805.006.F10ˣ07.×08.С/П =  2.1      -79.

Для деления ситуация похожая. Необычность возникает, только когда делят на N (N в регистре X). Если число в регистре Y по модулю в диапазоне 1 ⩽ Y < 10 (сюда же попадают шестнадцатеричные цифры), то в операциях деления N выглядит как число со степенью -160.
8^.-00 ÷ 2 = 4: П→x82÷ =  4.          .
18 ÷ 2^.-00 = 9: 18П→x2÷ =  9.          .
0.8 ÷ 2^.-00 = 0.4: 0.8П→x2÷ =  4.       -01.
8 ÷ 2^.-00 = 4^.160: 8П→x2÷ =  ЕГГ0Г.      .
Для проверки составим программу: 00.Сx01.802.П→x203.÷04.705.006.F10ˣ07.÷08.С/П
После В/0С/П на экране будет  4.        90.

Единственное исключение – это когда в Y тоже число N. Тогда деление снова проходит как обычно.
8^.-00 ÷ 2^.-00 = 4: П→x8П→x2÷ =  4.          .

"Жёлтые" функции

"Жёлтые" функции – те, которые вызываются через кнопку F.

"Синие" функции

"Синие" функции – те, которые вызываются через кнопку К.

Итог

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

Трюки по оптимизации

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

Демонстрационная программа

Описание интерфейса программы

В качестве демонстрации используется модификация программы "Пещера сокровищ", опубликованной в журнале Техника Молодежи №7 за 1987.

Для тех, кто не хочет идти по ссылке (или высматривать мелкий шрифт скана) опишем игру. Это трехмерный лабиринт (3 этажа размером 7×4), где каждая ячейка на этаже может быть или свободна, или занята – "стена". Цель игры – находясь в некоторой начальной точке лабиринта пройти весь (выход в левой нижней точке на третьем этаже – влево) и собрать максимальное количество сокровищ. Для понимания адресации вот план 1-го этажа из публикации в журнале:

1234567
1
2
4
8 X

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

С учетом этого выход из лабиринта (который предопределен) – влево от точки 3.8.

У игрока есть ограниченные ресурсы:

Ресурс можно пополнить, поискав его (т.е. его может и не быть). Поэтому далее будем называть их кладами. При это на 1-м этаже будет еда (+9, в оригинале +10), на 2-м – динамит (+4) и только на 3-м сокровища (+1). Особенность – как только нашли, то на всех этажах в той же ячейке кладов уже нет.

Управление игрока – задание команды на движение, или поиск кладов, или подрыв стены (последней ячейки, где не удалось пройти). Каждая команда расходует еду (подрыв, понятно, ещё и динамит). Код направления движения определяется положением цифр на клавиатуре относительно центра: 2,4,6,8,±5 – вниз/влево/вправо/вверх и выше/ниже этажом. Подрыв стены нулем (в оригинале число Fπ, но обычно при неудаче прохода выдаёт 0, так что сразу и удобнее его использовать для команды подрыва). Поиск – число 10 (в оригинале ноль, но 1/0 как бы намекает на найдем/нет).

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

При поиске клада: если не найдено – ноль. Если найдено, то показывает новое количество ресурса, а в регистре Y букву типа клада и позицию, где нашли, например:  E.00002     . При этом (как в оригинале) с разбойником, который защищал клад, можно бороться (В↑) или пропустить (0), затем С/П. Если боремся, то в результате общее количество (с учетом найденных кладов) данного ресурса может как увеличиться, так и уменьшиться.

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

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

Разбор программы

Полный код программы приведем в конце, а по ходу изложения будем приводить фрагменты, изучая трюки и использование недокументированных особенностей ПМК, без которых не удалось обойтись, с учетом всех улучшений. Сначала общий алгоритм программы. Как в оригинале сказано, это демонстрация возможностей ПМК работать с отдельными битами (шестнадцатеричные числа). Планы этажей и кладов представляются битовыми масками (хватает одного числа на весь этаж), а местоположение игрока фактически определяет один бит. Именно он "двигается", накладываясь на битовые маски, определяя возможность пройти. Так же биты в масках удаляются (при взятии клада) или устанавливаются (при удалении стенок). Большая часть кода программы – это преобразование хода игрока в правильные битовые операции, проверка границ, ресурсов и т.п.

Распределение регистров во многом совпадает с оригиналом.

Итак, начнем с… начала. Как начинается игра (предполагая что глобальные константы вбиты, как и сама программа)? А вот так: задаётся начальное положение игрока на плане, например  1.0000001   , затем БП48С/П. Тут же фрагментик:

 # |  00 01 02 03 04 05 06 07 08 09
 40 |  x→Пb 6
 50 |  x→Пd F x→Пe 0 x→П0 КСЧ F10ˣ FВx F К
 60 |  Кx→П0 П→x0 Кx≥08

Положение игрока запоминается в Rb (как бы куда хотим пойти). Начальное количество динамита берем 6, а не 4, т.к. два придётся потратить на удаление возможной стенки в месте игрока – мы же ещё не знаем лабиринт. 36 (6²) – как начальное количество еды. А вот с регистром R0 проводим хитрые манипуляции. Благодаря особенностям косвенной адресации при первом обращении ноль превратится в -99999999, что соответствует регистру R3, затем в -99999998 – R2 и так пока не дойдет до R0, где -99999996 перепишется новым сгенерированным значением (уже положительным!), это и отслеживаем в цикле. Цикл возвращается на адрес 55, который и записан в R8 (точнее -55, но на адресацию это не влияет, а почему минус объясню позже). Сами операнды для К получаются из КСЧ через функции F10ˣ и F, которые "размазывают" цифры мантиссы случайного числа на все разряды и делают его более случайным. Причем испытания показали, что выгодней сделать FВx и F вместо повтора КСЧ и F10ˣ для второго операнда (таки циклиться датчик, а эти операции его "сбивают"). Любители усложнения лабиринта (больше стенок и меньше кладов), могут заменить К на К

Следующий фрагмент, это снова начало…, но уже в программном виде (я отображаю фрагменты так, чтобы было понятнее позднее что и как):

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  В/О С/П

Да, такой вот маленький фрагмент, поэтому явно требуются пояснения.

Для начала хочу обратить внимание, что в R7 у нас записано 0.5, что при косвенной адресации даёт нулевой адрес (не меняя R7), а это значит, что мы всегда можем быстро вернуться на начало через Кx≠07, или подобное. Поэтому С/П и располагается в самом начале.

Но, есть и другой момент. Можно расположить в конце адресного пространства подпрограмму без В/О, т.к. благодаря побочной ветви адресного пространства этот В/О "сыграет". Более того, могу сразу сказать, что несмотря на наличие четырёх логических подпрограмм программа содержит всего этот один В/О. Две подпрограммы удалось наложить на хвост другой, а начальный В/О смог "сыграть" в двух разных адресных побочных ветвях.

Иногда разницу в методах возврата используют для различения ситуации. Например, в оригинале в начале стоит Cx, куда возвращается управление при неудаче, или на +01 при удаче. Но мне удалось сэкономить даже на этой команде. Позднее поясню как.

Прежде, чем продолжить код инициализации, который мы бросили, хочу привести основную подпрограмму, которая делает битовую проверку. Сначала приведем формализацию: на входе ожидается в регистре X план этажа для проверки (битовая маска). В регистре Y положение игрока (бит). После исполнения: значение Y безусловно запишется в регистр Rb. Если битовая проверка прошла успешно (совпадение), то значение из Y также попадет в регистр Ra, в Y останется дробная часть (положение на этаже), а само значение будет уже в X. Если битовая проверка не успешна, то вернет ноль в регистрах X и Y. Посмотрим, как это делается.

 # |  00 01 02 03 04 05 06 07 08 09
 40 |  К К{x} <-> x→Пb Кmax Кx≠07 x→Пa

Если с битовым умножением и копированием Y в Rb всё ясно, то вот Кmax явно использован "недокументировано". При неудаче ноль из регистра Y будет "недокументировано" скопирован как "самое большое число" в X. Т.е. при неудаче будет ноль (что от подпрограммы и требуется). А если битовая операция успешная (есть дробная часть), то ничего не произойдёт (т.е. исходный Y останется в X, а дробная часть в Y). И результат будет скопирован в Ra.

На самом деле эта подпрограмма полезна ещё в другом случае. Если (на входе) в одном из регистров X или Y число состоит из одной цифры, то эта процедура безусловно даст на выходе ноль. Эта возможность тоже используется в программе, поэтому назовем её также процедурой очистки.

Но внимательный читатель спросит, а где же В/О? А он расположен по адресу... ноль! Так вот, тут используется побочная ветвь адресного пространства, но только не после 105 адреса, а после F9-го. Да, адрес F9 = 47, а значит если подпрограмма начинается не с адреса 41 (как будто), а с того же адреса, но как F3, то после адреса F9 (47) управление перейдет на 00, т.е. на ту самую В/О.

Здесь уже уже можно раскрыть, что константа с регистре R9 (он используется для вызова этой подпрограммы) заканчивается на F3 (можно и на ED). Точнее в R9 храниться  4. 3     -08 (или  4.EГ     -08, кому как больше нравиться). Зачем вначале идёт 4 и такой порядок будет пояснено позднее (хотя если читали весь документ, то об этом уже упоминалось). Главное, что такой "довесок" не меняет косвенной адресации. Упомяну только, что после "использования" в косвенной адресации значение в R9 станет не нормализованным  0.00004 3-03, зато сразу покажет адрес перехода.

Продолжим код инициализации (начальной генерации лабиринта). Точнее он уже закончился и плавно переходит в подпрограмму, которая "взрывает" стенки. В чем смысл такого "слияния"? Во-первых не требуются какие либо БП, во-вторых, нам всё равно нужно обеспечить, чтобы в точке высадки стенок не было, т.е. как бы убрать её (даже если её там не было). Вот поэтому и было вначале на две динамита больше. Итак:

 # |  00 01 02 03 04 05 06 07 08 09
 60 |  П→xd 2 - Кx≥07 x→Пd

Тут вроде понятно, что из ресурса динамита (Rd) убирается два, проверяется что что-то осталось (не отрицательное) и остаток сохраняется обратно. Если динамита не осталось, то переходит на начало, с этой "-2", что по смыслу программы и нужно для сигнализации, что динамита нет. Причем сохранение остатка делается после проверки, чтобы не было -2, -4, -6 и т.д. Продолжим.

 # |  00 01 02 03 04 05 06 07 08 09
 60 |  П→xb 4
 70 |  - Кx<09 П→xb КП→xb К Кx→Пb КБП9

Напомню, что координаты взламываемой стены находятся в Rb. Вначале проверяем, что это значение в пределах регистров R1…R3 (т.е. < 4), причем R0 тоже пройдет проверку, но это не страшно и вот почему. На самом деле в Rb число меньше единицы может быть или в виде одной цифры (например, хотели с первого этажа 1.0002 пойти вниз и получили 0.0002) или просто ноль (есть такой путь при попытке выхода за границы этажа). Главное, что число будет из одной цифры, т.е. со второго разряда цифр нет, а значит в этом случае план этажа извлечется из плана кладов R0, потому что косвенная адресация через Rb будет равна нулю (да, последние две цифры мантиссы будут нулевыми, даже для числа 1.^-07, что можно посмотреть в главе по косвенной адресации). Затем операция К ничего не изменит (нет цифр у второго операнда) и вернет обратно в R0 значение неизменным. А если число больше или равно 4, то мы перейдем на процедуру очистки (зануления) через регистр R9. С учетом работы остальной части программы (это можно потом прикинуть) для Rb >= 4 число в Rb либо состоит из одной цифры (взлом левой стенки) и процедура очистки безусловно сработает, либо это "крыша" (Rb = 4.{…}). Но в R4 у нас тоже константа из одной цифры, а значит процедура очистки тоже сработает.

Для R1…R3 это операция установит бит из Rb в плане этажа. В этом случае в X у нас план этажа, в Y точка взлома, которую мы тут устанавливаем. В этом случае основная процедура успешно пройдет битовую проверку (мы же только что этот бит поставили) и завершиться, записав новое расположение в регистре Ra. Т.е. мы сразу сделаем ход в новое место.

Итак. Инициализация закончена. Игрок в точке высадки, стенки там нет. Мы познакомились с основной процедурой из регистра R9, она же процедура очистки и процедурой взлома стены (с адреса 63), которая тщательно проверяет все варианты. Теперь можно вернуться на точку остановки (начальный С/П) и начать самый частый (основной) код:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  П→xe 1 - Кx≥07 x→Пe <->

Тут, очевидно, уменьшается еда, похожим образом, как динамит, учитывая, что будет выдано -1 при неудаче. Обращаю внимание, что если исключить наш случай, когда регистров просто не хватает, тут всё равно делается экономия на команде. Всё-таки x→ПRП→xR – это две команды, а <-> – только одна. Но это конечно при условии, что запомненное значение не понадобиться ещё раз. Далее.

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  2 ÷
 10 |  Fx≠0 63

Вот и проверка команды на взлом стены. "Но позвольте, зачем сначала деление, а только потом проверка на ноль?" спросите вы. Ответ в том, что нам не хватает регистров (кстати, обратите внимание, что рабочий регистр Rb с последней попытки хода пока не задействован, и взлом будет на его основе). Поэтому выбор пользователя (точнее получается уже полвыбора ☺) мы запоминаем в регистре… X2. Да, да, вспомните, ведь проверка условий при успехе запоминает X в X2. А дальше посмотрим, как мы это используем:

 # |  00 01 02 03 04 05 06 07 08 09
 10 |  Fπ + x→Пb ВП

Ага, вот и выбор пользователя преобразован в номер регистра для коэффициента умножения. Для прояснения, приведу таблицу с возможным выбором игрока (ноль уже исключили), и то, что попало в регистр Rb:

Ход игрокаПосле ÷ 2После + 3
214
425
±5±2.5Что-то больше нуля
636
847
1058

Вариант ±5 не будет использовать регистр Rb, как мы увидим позднее.

Обратите внимание, что мы на самом деле сложили не с тройкой, а с числом π. Почему? Дело в том, что команда Fπ не X2-влияющая, к тому же при косвенной адресации дробная часть значения не имеет (вспоминаем – всё это из главы по косвенной адресации). Т.е. для нас всё равно, будет в Rb число 7 или 7.1415926, при КП→xb он всё равно извлечет 0.5 (значение в R7).

А самое главное, это то, что останется после команды ВП. Вспомним из главы про X2, что сочетание этой команды с предшествующей командой сохранения в регистр "съест" первую цифру числа из X2 (важно ещё то, что в результате сложения с π значение в X не отрицательное) и восстановит его в X. А что у нас в X2? Это значение во второй колонке таблицы выше. Т.е. он для всех вариантов из таблицы оставит ноль, и только от ±2.5 оставит ±0.5. Вот сколько может сделать одна недокументированная команда! Далее обработка варианта с вверх/вниз (±0.5):

 # |  00 01 02 03 04 05 06 07 08 09
 10 |  Fx≠0 D4 КЗН П→xa
 20 |  БП EA

Что он делает? Он преобразует движение между этажами ±0.5 в ±1 (тоже, кстати, трюк вместо умножения на 2) и переходит на некий адрес EA. Другие движения будут обработаны с адреса D4 (если кто не знает, при наборе программы и вводе таких адресов вместо цифр нажимаются кнопки с соответствующими буквами). Что за странные адреса, спросите вы? А дело в том, что в конце всё это закончиться на основной процедуре, которая расположена по нестандартным адресам, а значит и текущую ветвь выполнения нужно "завернуть". D4 – тот же адрес 22, т.е. как раз окончание этого кода, а EA равно 38. Точнее, адресу 38 соответствует адрес F0, но такое не ввести, поэтому мы заменяем аналогом (трюк). Ладно, займемся этим адресом, когда туда дойдем, а сейчас продолжим движение по этажу (или проверку клада), но сначала таинственное пропускание вперед:

 # |  00 01 02 03 04 05 06 07 08 09
 20 |  П→x6

Так, пришла пора раскрыть, что же находится в регистре R6, которое почти 0.1. Там храниться число D.^-02 ( Г.       -02). Дело в том, что в операции умножения, если D находится слева (в X), то оно ведёт себя как 10 (кто забыл, просмотрите главу про шестнадцатеричную арифметику), а для степени -02 получается как 0.1 (10 × 10⁻² = 10⁻¹). А когда справа (в Y), то гораздо хитрее. Это "хитрее" нам понадобится позднее, поэтому пока загоняем число в стек, чтобы оно было "справа" (я мысленно стек представляю как X1–X–Y–Z–T, если кто-то по другому, то "справа" поменяйте на другое).

 # |  00 01 02 03 04 05 06 07 08 09
 20 |  П→xa К{x} КП→xb × Fx≥0 77

Ну вот, наконец-то умножение дробной части числа Ra (положение на этаже) на коэффициент движения (не забыли, что у нас в Rb?). Тут же проводится отсечка варианта поиска клада. Дело в том, что в R8 (вариант 10 / 2 + 3) число отрицательное (-55, если кто забыл, и вот оказывается для чего минус), а значит поиск уходит на адрес 77. Но это не всё. Тут мы опять используем регистр X2 (через X2-влияющую операцию сравнения). Мы сохраняем новое положение игрока на этаже в X2, но не потому что нет регистра, а для экономии команд: x→ПbП→xb это две команды, а ., всего одна, а сравнение всё равно нужно делать. Именно поэтому сравнение делается не сразу после КП→xb, а после умножения, чтобы запомнить в X2 итог.

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

Рассмотрим для начала левую границу. Как мы можем за нее попасть? Только если перед этим было число M.N (M=1,2,3; N=1,2,4,8), а затем мы N умножили на 10. Понятно, что потом мы "дробную" часть приплюсуем к этажу M. В данном случае она очень даже не дробная, но в результате сложения итоговых вариантов не так много, и самое главное, они будет в диапазоне 2…11 и состоять только из одной цифры (кроме 11). А одна цифра бинарную конъюнкцию (К) "не переживет" в процедуре очистки. Вариант c 11 (влево от 3.8) – это исключение, поэтому он и выбран как выход из лабиринта. Причем будет выполнена операция 1 1 КП→xb. Т.е. даже регистр Rb (b = 11, кто забыл) выбран не случайно (трюк с подтасовкой регистров)! В общем, левая граница "защитилась" автоматически.

Правая граница. Там значение дробной части становится порядка 1.^-08 и при сложении с номером этажа просто потеряется (точности не хватит на 9 цифр), опять же оставляя только одну целую часть, которая будет 1…3 и конъюнкцию не переживет. Исключение составляет 8.^-08 (округление до 1.^-07), но и его мы победим позднее.

Верх и низ. При переходе верхней границы (1 × 0.5) получается число, оканчивающиеся на 5, а при переход нижней (8 × 2), число из двух цифр. Вот тут нам и пригодится таинственное D.^-02 из R6. Используя знания шестнадцатеричной арифметики можно узнать, что при умножении (D.^-02 как раз осталось справа) на число 1, 2, 4 или 8 результат будет содержать только одну цифру, а на 5 и тем более 16 уже больше. Это поясняет последующий код:

 # |  00 01 02 03 04 05 06 07 08 09
 20 |  ×
 30 |  К К{x} Кx=09

Здесь кроме того, что указано, применяется ещё один трюк. После умножения будет число с одной или более цифрой. Как же вычислить одна или больше? Приходит на помощь операция К при том, что в регистре Y остался ноль ещё после команды ВП по адресу 15 (какой автор предусмотрительный, даже давний ноль у него при деле ☺). В результате будет 8. – всё хорошо или 8.{с чем-то} – плохо, а после К{x} либо ноль, либо 0.{что-то}. Вот это плохое "что-то" последняя команда и отправит на процедуру очистки. Для очистки важно, чтобы в одном из регистров X или Y было число с одной цифрой – число ноль в Y вполне подходит (да, в логических бинарных операциях второй операнд не исчезает, но это таки документировано). Продолжим далее.

 # |  00 01 02 03 04 05 06 07 08 09
 30 |  . П→x9 -

Первое что мы делаем, это восстанавливаем X2 (новое положение на этаже) ещё с команды сравнения по адресу 17, т.к. все команды в промежутке были не X2-влияющие. А потом корректируем значение на маленькую величину порядка 4^.-08. Это не изменит итог ни для кого, кроме 8.^-08. В этом случае оно тоже станет очень маленьким. Вот где заблокирован "телепорт" и вот зачем в R9 к адресу переход были добавлены четвёрка вначале и порядок -08. Далее проще:

 # |  00 01 02 03 04 05 06 07 08 09
 30 |  П→xa К[x] + x→Пb
 40 |  КП→xb

Мы к нашему новому значению на этаже прибавляем номер этажа (целая часть Ra), заталкиваем новые координаты в Y, а в X оставляем план этажа. На самом деле в Rb могло получится не только 1…3, но и другое, поэтому идёт переход на основную процедуру (она идёт следом с адреса 41 = F3, и мы её уже смотрели), которая отбросит все нестандартные этажи (одна цифра в числе), а также случай, когда битовая карта этажа просто не совпадет с положением игрока. Обращаю внимание, что основная процедура запомнит в Rb полное значение нового положения, т.е. если даже по этажу не пройти, мы запомним точку, куда хотели попасть. В случае удачи эта процедура автоматически обновит содержимое Ra.

Вспомним про адрес перехода 38 = EA, куда мы попадаем при движении между этажами. Оказывается – банальный плюс, но с учетом раннего ±1 и извлеченного Ra, это "то, что доктор прописал" (вверх/вниз). Так вариант с движение вверх/вниз плавно влился в текущий кусок кода (очередной трюк).

Ну вот основная часть кода рассмотрена, остался поиск клада (и разбойник!). Там тоже не всё так просто.

 # |  00 01 02 03 04 05 06 07 08 09
 70 |  П→xa П→x0 КПП9
 80 |  Кx≠07 П→x0 К x→П0

Тут мы для проверки битов (возможности взять клад) пользуемся основной процедурой. При этом в регистр Y ставим содержимое Ra, чтобы при удаче, оно же и сохранилось в Ra (это факт действия основной процедуры). Понятно, что если кладов уже нет (убран бит в нужном месте), то по нулю уходим на начало. Далее убираем бит клада из маски в регистре R0.

А теперь мы приведем код, который из номера этажа получит шестнадцатеричную цифру, из соответствия 1 = E, 2 = D, 3 = C. В оригинале автор использовал для этого заранее сохраненную константу "E" и цикл с уменьшением её через регистр R0. Мы сделаем тоже самое без использования дополнительных регистров и короче, пользуясь недокументированными возможностями.

 # |  00 01 02 03 04 05 06 07 08 09
 80 |  П→x5 П→xa + КИНВ К{x} КСЧ
 90 |  ВП

Тут снова придётся вспомнить раздел про регистр X2 и команду ВП, которая восстанавливает его. Сначала с помощью сложения с 10 (константа из R5) мы номер этажа загоняем во второй разряд. Тут порядок операндов важен, т.к. последний П→x и будет содержаться в X2. Затем с помощью инверсии и отсеканию целой части (первой цифры) мы получим, что нужная буква находится в первом разряде. Но в X вроде ещё что-то есть? Так вот, восстановление X2 через ВП, сделает так, что при восстановлении X2 только первая цифра останется из X, т.е. 2.00004 восстановиться в D.00004. То, что нам нужно. Ещё нужно помнить, что перед ВП должна быть любая не X2-влияющая команда, которая будет выполнена, но результат проигнорирован. Обычно для этих целей используется КНОП, но мы используем КСЧ. Дело в том, что основная процедура может использовать Кmax с нулем в Y, а это "сбрасывает" датчик случайных чисел. Для последующих игр (вдруг захочется пройти другой лабиринт) желательно "оживить" датчик случайных чисел ("с паршивой овцы хоть шерсти клок" – даже из игнорируемой команды пытаемся получить эффект). Идем далее.

 # |  00 01 02 03 04 05 06 07 08 09
 90 |  x→Пb КП→xb П→xb 1 - F + С/П

Сохраняем полученную букву с дробной частью в Rb и оставляем его в стеке для информации игроку. Потом не только извлекаем старое значение ресурса, но и заодно отсекаем дробную часть от буквы (при косвенной адресации), чтобы потом преобразовать её в нужную добавку. Тут нужно пояснить, что именно происходит для вычислении "добавки", в зависимости от значения регистра X (C, D или E). При вычитании срабатывают правила шестнадцатеричной арифметики.

X После "− 1" После x²
C 11 (сокровище)
D 24 (динамит)
E 39 (еды)

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

 # |  00 01 02 03 04 05 06 07 08 09
 90 |  Fsin
 A0 |  1 + × К[x] Кx→Пb

Вот собственно и всё, финальный В/О по адресу A5 (или 00) "сыграет", чтобы "сбить" программу с малой побочной ветви и вернуть в нормальное русло.

Полный текст программы

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  В/О С/П П→xe 1 - Кx≥07 x→Пe <-> 2 ÷
 10 |  Fx≠0 63 Fπ + x→Пb ВП Fx≠0 D4 КЗН П→xa
 20 |  БП EA П→x6 П→xa К{x} КП→xb × Fx≥0 77 ×
 30 |  К К{x} Кx=09 . П→x9 - П→xa К[x] + x→Пb
 40 |  КП→xb К К{x} <-> x→Пb Кmax Кx≠07 x→Пa x→Пb 6
 50 |  x→Пd F x→Пe 0 x→П0 КСЧ F10ˣ FВx F К
 60 |  Кx→П0 П→x0 Кx≥08 П→xd 2 - Кx≥07 x→Пd П→xb 4
 70 |  - Кx<09 П→xb КП→xb К Кx→Пb КБП9 П→xa П→x0 КПП9
 80 |  Кx≠07 П→x0 К x→П0 П→x5 П→xa + КИНВ К{x} КСЧ
 90 |  ВП x→Пb КП→xb П→xb 1 - F + С/П Fsin
 A0 |  1 + × К[x] Кx→Пb

Начальные значения констант (которые не меняются между играми):

Вот последовательность для ввода констант:
55/-/x→П8
4473В↑808КК{x}ВП7/-/x→П9
10x→П5
2x→П4
F1/xx→П7
22КИНВК{x}ВП1К[x]ВП2/-/x→П6

Положение переключателя Р-ГРД-Г должно быть в Р. Перед первой игрой (или каждой, если не хотите аккумулировать сокровища) регистр Rc нужно обнулить.

И напомню порядок начала игры: задаётся начальное положение игрока на плане, например 1.0000001 – можно быстро получить как П→x9F10ˣ (кто-то можета подумать, что автор даже это предусмотрел, но нет, это, но только это, случайно так совпало). Затем БП48С/П

Список трюков и недокументированных особенностей в программе

которые мы использовали в программе для её сокращения (чтобы всё влезло).

Как не странно, даже такую "плотную" программу можно ещё улучшить. Например, если потребовать в начале каждой игры после ввода начального положения нажатия В↑ перед C/П. Тогда начало можно переместить на адрес 44, где уже есть команда x→Пb, и убрать её с адреса 48. При этом, кстати, будет реализован редкий трюк, связанный с исполнением одних и тех же команд, но в разных адресных ветках вычисления (и с разными целями и результатом). А потом, например, использовать сэкономленную команду для начального обнуления количества сокровищ, например x→Пс по адресу 54.


Приложение. Все команды

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

КодВидНазваниеКомментарий
00…09 09 Ввод числа Ввод цифр идёт даже через границу С/П. Т.е. если в начале программы идёт цифра(ы), а перед её запуском тоже ввод, то он продолжиться. То же касается и разделителя ., он может как заканчивать ввод в режиме вычислений, так и начинать в программе – будет воспринят как разделитель целой и дробной части.
Второе нажатие . для разделения разрядов игнорируется.
Если предыдущая команда была не ввод цифры или разделителя, а так же не команда В↑, то предварительно осуществляется сдвиг стека под новое число.
0A . Разделитель целой и дробной части Кроме того, что указано выше восстановление X2→X. См. Таинственный регистр X2.
0B /-/ Смена знака Предыдущее значение НЕ копируется в X1. Cм. также Таинственный регистр X2.
0C ВП Ввод порядка Если X = 0, то он заменяется на 1. Для многих чисел ввод порядка означает умножение на 10 в соответствующей степени.
После ввода порядка можно ещё раз нажать ВП и ввести число – в этом случае порядки складываются.
Знак порядка можно ввести как сразу после ВП, так и после ввода цифр.
Также см. Таинственный регистр X2.
0D Сx Сброс X в ноль Важное свойство – стек не двигается.
0E В↑ Сдвиг стека Если последующая команда – ввод числа, то будет ввод в X, если извлечение из памяти или Fπ – то стек ещё раз сдвинется.
0F FВx Полный сдвиг, включая X1 Единственная документированная и вводимая команда с цифрой F в коде.
 10 |  + Сложение Результат проверяется только при X→X2, т.е. можно использовать для получения сверхчисел.
11 - Вычитание Результат проверяется только при X→X2, т.е. можно использовать для получения сверхчисел.
12 × Умножение Результат проверяется при X→X2, т.е. можно использовать для получения сверхчисел.
13 ÷ Деление Ошибка деления на ноль возникает безусловно, остальное при X→X2.
14 <-> Обмен X и Y
15 F10ˣ Возведение в степень числа 10 Аргументы на переполнение порядка (сверхчисло) проверяется безусловно, а не при X→X2.
16 F Возведение в степень числа e (экспонента) Аргументы на переполнение порядка (сверхчисло) проверяется безусловно, а не при X→X2.
17 Flg Десятичный логарифм Аргументы (должно быть больше нуля) проверяется безусловно, а не при X→X2.
18 Fln Натуральный (экспоненциальный) логарифм Аргументы (должно быть больше нуля) проверяется безусловно, а не при X→X2.
19 Fsin⁻¹ Арксинус Аргументы (должно |X|⩽1) проверяется безусловно, а не при X→X2. Для ГРД/Г и X = 0 выводит X = 00.
1A Fcos⁻¹ Арккосинус Аргументы (должно |X|⩽1) проверяется безусловно, а не при X→X2. arccos(1) для ГРД/Г даёт не нормализованное 00.
1B Ftg⁻¹ Арктангенс
1C Fsin Синус
1D Fcos Косинус
1E Ftg Тангенс Аргументы проверяется безусловно, а не при X→X2. При X = π/2 + n × π будет ошибка.
1F Пустой оператор Обычным образом такую команду не ввести.
 20 |  Fπ Число π (пи) Кроме того, что сдвигает стек (это документировано), также копирует предыдущее X в X1, как арифметическая операция (а это нет).
21 F Квадратный корень Аргументы проверяется безусловно (X⩾0), а не при X→X2.
21 F Возведение в квадрат Результат проверяется при X→X2, т.е. можно использовать для получения сверхчисел.
23 F1/x Обратная величина Ошибка деления на ноль возникает безусловно, остальное - при X→X2.
24 F Возведение в степень. Ошибка, если X = 0. Не принимает отрицательный X, даже когда это математически допустимо.
Ошибка переполнения возникает безусловно, а не при X→X2.
Стек не сокращается, в отличие от обычных арифметических операций. Т.е. число в Y остается на месте (позволяя ещё раз возвести в ту же степень).
25 F Подтягивание стека
26 К°→′ Перевод угловых или временных величин из обычной формы в часть целой Если дробное значение ⩾0.6, то будет ошибка. Целая часть не меняется, даже если шестнадцатеричное число.
27 К Ошибка ЕГГ0Г.
28 К× Ошибка ЕГГ0Г.
29 К÷ Ошибка ЕГГ0Г.
2A К°→′" Перевод угловых или временных величин из обычной формы в часть целой, включая секунды Если дробное значение ⩾0.6, то будет ошибка. Целая часть не меняется, даже если шестнадцатеричное число.
2B…2E Ошибка ЕГГ0Г.
2F Пустой оператор Обычным образом такую команду не ввести.
 30 |  К°←′" Перевод угловых или временных величин из части целого в обычную форму, включая секунды Если дробная часть ⩽ 2^.-07, то обратное преобразование уже не срабатывает. Если = 1^.-07 то уже и прямое преобразование не меняет значение. Если целая часть шестнадцатеричное число и нет дробной части или оно = 1^.-07 – оно нормализуется, как в операциях с шестнадцатеричными цифрами. Иначе остается без изменений.
31 К∣x∣ Модуль
32 КЗН Знак числа Отрицательный ноль преобразуется в ноль.
33 К°←′ Перевод угловых или временных величин из части целого в обычную форму Если дробная часть ⩽ 2^.-07, то обратное преобразование уже не срабатывает. Если = 1^.-07 то уже и прямое преобразование не меняет значение. Если целая часть шестнадцатеричное число и нет дробной части или оно = 1^.-07 – оно нормализуется, как в операциях с шестнадцатеричными цифрами. Иначе остается без изменений.
34 К[x] Целая часть Простое отбрасывание дробной части, а не математическое нахождение целой части, т.е. [-1.2] = -1, а не -2.
Если число целое и шестнадцатеричное – оно преобразуется, как в операциях с шестнадцатеричными цифрами. Если есть дробная часть, то целая шестнадцатеричная часть остается без изменений.
35 К{x} Дробная часть Простое отбрасывание целой части, а не математическое нахождение целой части, т.е. {-1.2} = -0.2, а не 0.8.
Для целых чисел по модулю < 107 (точнее числа, которым нужно меньше восьми знакомест) получается отрицательный ноль (в сравнениях идёт как отрицательное число и как ноль). Т.е. {-1234567} = -0, но {-12345678} = 0.
Шестнадцатеричные цифры (в дробной части) сохраняются.
36 Кmax Максимум Ноль исключение – самое большое число.
Обмена X и Y не происходит, т.е. либо X и Y после операции совпадают (в Y был максимум), либо остаются как есть (кроме копирования X в X1).
37 К Логическое умножение (AND) В логических операциях первая цифра заменяется на 8, а над остальными цифрами мантиссы проводится побитовая операция, как с шестнадцатеричными числами. Порядок и знак исходных чисел значения не имеют, они могут быть даже пустышкой. Результат всегда в форме 8.HHHHHHH
Стек для двухоперандных операций не сокращается, что позволяет выполнить операцию ещё раз.
38 К Логическое сложение (OR)
39 К Логическое исключающее или (XOR)
3A КИНВ Логическая инверсия (NOT)
3B КСЧ Случайное число Единица не бывает. Ноль бывает в редких случаях (когда в Y шестнадцатеричное число, то последовательность может выдавать и ноль). Датчик не очень хороший, часто циклиться (от применения команд с К).
Сбросить на начало (как при включении) можно с помощью операции Кmax, когда регистр Y нулевой.
3C Ошибка ЕГГ0Г.
3D То же, что и команда К°→′" (код 2A)
3E Копирование Y в X (а X→X1) Те же действия, что и пара команд FВ↑, только не X2-влияющая.
3F Пустой оператор Обычным образом такую команду не ввести.
40…4E x→ПR Сохранение X в регистр R0…Re
4F Сохранение X в регистр R0 Обычным образом такую команду не ввести.
 50 |  С/П Стоп/пуск
51 БП Безусловный переход
52 В/О Возврат обратно Стек обратных адресов подпрограмм из 5 ячеек, вначале нулевых. При возврате из подпрограммы берётся значение верхней ячейки стека + 1 (для определения точки возврата) и стек сдвигается, поэтому сразу после включения ПМК команда В/О эквивалентна БП01. Но если стек адресов возврата заполнился до конца, то последний адрес стека начинает копироваться и В/О будет на этот адрес + 1. Для зануления стека, в этом случае, можно воспользоваться знаниями Еггогологии и вызвать нулевого 3Г0ГГа:

CxВ↑÷ВПFCx<->Cx

Или запустить ненадолго бесконечную программу из одного оператора, например 00.KПП9, при условии, что R9 = 0.
53 ПП Вызов подпрограммы Переход на адрес подпрограммы, указанный следующей командой. Этот адрес запоминается в стеке обратных адресов (см. В/О).
54 КНОП Пустой оператор
55 К1 Пустой оператор
56 К2 Пустой оператор
57 Fx≠0 Если не ноль Пропуск адреса перехода, и выполнение следующей после него команды, если X не равно нулю (в большинстве языков обычно наоборот, переход на адрес при выполнении условия).
58 FL2 Цикл по регистру R2
59 Fx≥0 Если больше или равно нулю Пропуск адреса перехода, и выполнение следующей после него команды, если X больше или равно нулю (в большинстве языков обычно наоборот, переход на адрес при выполнении условия).
5A FL3 Цикл по регистру R3
5B FL1 Цикл по регистру R1
5C Fx<0 Если меньше нуля Пропуск адреса перехода, и выполнение следующей после него команды, если X меньше нуля (в большинстве языков обычно наоборот, переход на адрес при выполнении условия).
5D FL0 Цикл по регистру R0
5E Fx=0 Если равно нулю Пропуск адреса перехода, и выполнение следующей после него команды, если X равно нулю (в большинстве языков обычно наоборот, переход на адрес при выполнении условия).
5F Зависание Визуально воспринимается как зависание. ПМК не реагирует на ввод команд и ничего не отображает.
Обычным образом такую команду не ввести.
60…6E П→xR Извлечение из регистра R0…Re в X
6F Извлечение из регистра R0 в X Обычным образом такую команду не ввести.
70…7E Кx≠0R Косвенный условный переход при неравенстве нулю, адрес перехода в регистре R0…Re Пропуск адреса перехода, и выполнение следующей после него команды, если X не равно нулю (в большинстве языков обычно наоборот, переход на адрес при выполнении условия).
7F Косвенный условный переход при неравенстве нулю, адрес перехода в регистре R0 Пропуск адреса перехода, и выполнение следующей после него команды, если X не равно нулю (в большинстве языков обычно наоборот, переход на адрес при выполнении условия).
Обычным образом такую команду не ввести.
80…8E КБПR Косвенный безусловный переход на адрес перехода в регистре R0…Re
8F Косвенный безусловный переход на адрес перехода в регистре R0 Обычным образом такую команду не ввести.
90…9E Кx≥0R Косвенный условный переход если больше или равно нулю, адрес перехода в регистре R0…Re Пропуск адреса перехода, и выполнение следующей после него команды, если X больше или равно нулю (в большинстве языков обычно наоборот, переход на адрес при выполнении условия).
9F Косвенный условный переход если больше или равно нулю, адрес перехода в регистре R0 Пропуск адреса перехода, и выполнение следующей после него команды, если X больше или равно нулю (в большинстве языков обычно наоборот, переход на адрес при выполнении условия).
Обычным образом такую команду не ввести.
A0…AE КППR Косвенный вызов подпрограммы по адресу в регистре R0…Re
AF Косвенный вызов подпрограммы по адресу в регистре R0 Обычным образом такую команду не ввести.
B0…BE Кx→ПR Косвенное сохранение X в регистр, по номеру в регистре R0…Re
BF Косвенное сохранение X в регистр, по номеру в регистре R0 Обычным образом такую команду не ввести.
C0…CE Кx<0R Косвенный условный переход если меньше нуля, адрес перехода в регистре R0…Re Пропуск адреса перехода, и выполнение следующей после него команды, если X меньше нуля (в большинстве языков обычно наоборот, переход на адрес при выполнении условия).
CF Косвенный условный переход если меньше нуля, адрес перехода в регистре R0 Пропуск адреса перехода, и выполнение следующей после него команды, если X меньше нуля (в большинстве языков обычно наоборот, переход на адрес при выполнении условия).
Обычным образом такую команду не ввести.
D0…DE КП→xR Косвенное извлечение X из регистра, по номеру в регистре R0…Re
DF Косвенное извлечение X из регистра, по номеру в регистре R0 Обычным образом такую команду не ввести.
E0…EE Кx=0R Косвенный условный переход если равно нулю, адрес перехода в регистре R0…Re Пропуск адреса перехода, и выполнение следующей после него команды, если X равно нулю (в большинстве языков обычно наоборот, переход на адрес при выполнении условия).
EF Косвенный условный переход если равно нулю, адрес перехода в регистре R0 Пропуск адреса перехода, и выполнение следующей после него команды, если X равно нулю (в большинстве языков обычно наоборот, переход на адрес при выполнении условия).
Обычным образом такую команду не ввести.
F0…FF Пустой оператор Обычным образом такую команду не ввести.

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

Команды F0…FE получаются по адресам 30…44 после выполнения в режиме вычислений пары команд В/ОКППR (R = 0…e), которые переведут ПМК в режим ввода программы и вставят код с цифрой F. При этом в регистрах R0…R3 должно быть число заканчивающееся на 1 или 2 (точнее такое, чтобы после косвенной адресации заканчивалось на 1 или 2). Если там будет другое (например, ноль, как после включения ПМК), то для R1…R3 вставится F0…F2 по адресу 30…32, а для R0 это будет самый быстрый способ получить 3Г0ГГа (причем перехода в режим ввода программы не произойдёт).

Последовательность 5 команд FF (через одну) можно получить по любому адресу, если стек возврата "забить" числом, начинающимся с пустышки, и перед переходом в режим ПРГ нажимается БП "Адрес". Тогда начиная с адреса "Адрес"-11 через одну будут проставлены команды FF.
Для начала получим адрес с начальной пустышкой, например число F8, и сохраним в регистр, например в R7. Не забыть при этом в конце убрать такое число с начальной пустышкой из RX!
888В↑77KK{x}ВП2x→П7Cx
Теперь по этому "темному" адресу вставим команду вызова подпрограммы по R7:
KБП7FПРГKПП7FАВТKБП7
Потом 5 раз (чтобы забить стек возврата) нажмем ПП
После этого уже выбираем, где нам нужны команды FF. Пусть мы хотим их с адреса 80. Тогда делаем переход на 80+11=91 БП91 и FПРГFАВТ
Теперь по адресам 80, 82, 84, 86, 88 добавилась команда FF. Если нужно побольше, например ещё и по адресам 81, 83, 85, 87, 89, то просто снова заполним стек возврата KБП7, 5 раз ПП, и после БП92 и FПРГFАВТ получим то, что хотели.
Не забудьте в конце почистить стек возврата (как указано в примечание для команды В/О)!


Список терминов

ЕГГ0Г
В общем случае отображение об ошибке операции, в частности числа в диапазоне 1100…1199.
3Г0ГГ
Числа в диапазоне 1200…1299.
Еггогология
Изучение недокументированных возможностей ПМК, главным образом в области сверхчисел. См. https://ru.wikipedia.org/wiki/Еггогология
Знакоцифра
Цифра, которая располагается на месте знака. См. Числа, у которых вместо знака стоит цифра.
Не нормализованные числа
Числа, у которых отображаются ведущие нули.
Нормализация
Нормализация – вывод числа без ведущих нулей. Также в контексте преобразования числа с шестнадцатеричными цифрами означает преобразование в число с десятичными числами, с переносом лишней десятки в старший разряд.
ПМК
Программируемый микрокалькулятор. В контексте статьи – МК-61.
Пустышка
Число, у которого первая цифра представлена шестнадцатеричной цифрой F. Такие цифры не отображаются ПМК, т.е. выглядят как пустое знакоместо.
Регистр X2
Или экранный регистр – см. Таинственный регистр X2.
Сверхчисло
Число, порядок которого превышает документированный диапазон чисел для ПМК, т.е. порядок не в диапазоне -99…99, например 1120, или 4-180.
Темные адреса
Шестнадцатеричные адреса, располагаясь на которых в режиме ввода программы ПМК не отображает коды операций (тёмный экран).