Целью использования недокументированных возможностей чаще всего
является сокращение длины программы (самый "узкий" ресурс),
реже – высвобождение дополнительных регистров памяти.
Большинство таких возможностей реализуется или используется только в
контексте программного режима.
Иногда такие возможности используются для получения видео изображения,
которое невозможно создать обычным способом.
Также знание недокументированных возможностей поможет понять, почему
иногда программа ведёт себя не совсем так, или совсем не так, как ожидалось.
В данной документе не рассматривается получение и исследование сверхчисел и пустышек, которые исторически уже рассмотрены Еггогологией. Но из указанной Еггогологии взяты термины ЕГГ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. Не нормализованные числа в тексте так и будут записываться с ведущими нулями.
Адрес формальный | Адрес фактический | Доп. адрес |
---|---|---|
00 | 00 | |
01 | 01 | |
… | ||
99 | 99 | |
A0 | A0 | |
A1 | A1 | |
A2 | A2 | |
A3 | A3 | |
A4 | A4 | |
A5 | 00 | |
A6 | 01 | |
A7 | 02 | |
A8 | 03 | |
A9 | 04 | |
B0 | 05 | |
B1 | 06 | |
B2 | 00 | |
B3 | 01 | |
B4 | 02 | |
B5 | 03 | |
B6 | 04 | |
B7 | 05 | |
B8 | 06 | |
B9 | 07 | |
C0 | 08 | |
C1 | 09 | |
C2 | 10 | |
C3 | 11 | |
C4 | 12 | |
C5 | 13 | |
C6 | 14 | |
C7 | 15 | |
C8 | 16 | |
C9 | 17 | |
D0 | 18 | |
D1 | 19 | |
D2 | 20 | |
D3 | 21 | |
D4 | 22 | |
D5 | 23 | |
D6 | 24 | |
D7 | 25 | |
D8 | 26 | |
D9 | 27 | |
E0 | 28 | |
E1 | 29 | |
E2 | 30 | |
E3 | 31 | |
E4 | 32 | |
E5 | 33 | |
E6 | 34 | |
E7 | 35 | |
E8 | 36 | |
E9 | 37 | |
F0 | 38 | |
F1 | 39 | |
F2 | 40 | |
F3 | 41 | |
F4 | 42 | |
F5 | 43 | |
F6 | 44 | |
F7 | 45 | |
F8 | 46 | |
F9 | 47 | |
FA | 48 | 01 |
FB | 49 | 02 |
FC | 50 | 03 |
FD | 51 | 04 |
FE | 52 | 05 |
FF | 53 | 06 |
Первая побочная ветвь (возврат на нулевой адрес) короткая – адреса A5…B1, которые отображаются на адреса 00…06.
Вторая побочная ветвь длинней – адреса B2…F9, которые отображаются на адреса 00…37. Причем адреса C0…F9 являются темными, Например, если выполнить БП 10 FПРГ, и по адресам 10…12 ввести команды КНОП КНОП КНОП, то мы увидим 54 54 54 13. Но если затем перейти на адрес C5 (FАВТ БП C5 FПРГ), то получим 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.
При косвенном обращение сначала исходное число преобразуется по правилам, описанным ниже, а затем записывается обратно (для команд FFL0…FFL3 иногда нет). При этом всегда(!) цифры M7M8 преобразованного числа содержат адрес перехода (или номер регистра). Обратите внимание, что преобразованное число не нормализуется перед сохранением обратно, тем самым могут быть числа с ведущими нулями. Обычно это используется или для генерации специальных изображений или в комбинации с другими недокументированными операциями, которым важны только цифры мантиссы (пример в разделе Таинственный регистр X2).
До | 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 цифр. Т.е. число остается тем, что есть, что и соответствует документации. В случае, если кроме целой части есть и ещё и дробная, то же правило показывает, что дописываемые нули "вытеснят" дробную часть совсем, что часто используется в программах для сокращения: нет необходимости убирать дробную часть, т.к. косвенная адресация её сама уберёт.
До | 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 |
Тут нужно сделать замечание. Есть предположение, что девятки не случайны, а равны 10 − 1, где 10 это знак минус (как шестнадцатеричная цифра A). Для проверки такого предположения возьмем число с цифрой вместо знака (см. Числа, у которых вместо знака стоит цифра для алгоритма получения). Например 2E. , где двойка стоит на месте знака. При косвенной адресации: x→П9 КП→x9 П→x9. Двойка уменьшается на единицу и становится 21111111E. . Аналогично для других чисел, например, если для 9E. (которое после /-/) выполнить косвенную адресацию, то получим 98888888E. .
Теперь рассмотрим ситуацию, когда само значение регистра ещё и меняется как описано в документации. Обращаю внимание, что изменения происходят не при любом косвенном обращении, а только когда значение адреса для перехода вычисляется. Например, в условном операторе Кx<04 регистр R4 будет модифицирован только когда x⩾0, т.е. когда понадобиться вычислить адрес перехода.
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.
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.
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 (и где-то точка), то значит цикл прервется на исходном числе.
Переход так же будет на шестнадцатеричный адрес, но с этим мы уже знакомы (см. Программное адресное пространство).
К сожалению, если идёт увеличение, то число с шестнадцатеричными цифрами предварительно подвергается нормализации (справа налево, смысл как указано в списке терминов): буквы считаются как двузначные, с переносом лишней единицы в старший разряд. Например, сложение числа 9AE и 1 будет так: E = 14, значит последняя цифра 4 + 1 = 5 и 1 «в уме»; затем A = 10 + 1 в уме, будет 11, т.е. 1 и 1 в уме; далее 9 + 1 = 10. Итого 1015. Это значит, что косвенная адресация через R4…R6 всегда уберёт шестнадцатеричные цифры из числа. При этом нестандартный знак числа (см. замечание в варианте для отрицательных чисел) остается.
Для чисел вида 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 набрать ВП 5 5). Так вот, по сути это число равно 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 |
00.Сx 01.x→П0 02.КСЧ 03.КИНВ 04.Кx→П0 05.П→x0 06.Fx≥0 07.02 08.С/П
Ещё практических советов. Уменьшение шестнадцатеричной цифры для БЗ-34 было единственным способом из цифры E получить остальные цифры. В МК-61 с приходом бинарных операций стало проще, но т.к. первая цифра всё ещё не во власти этих операций, то через косвенную адресацию получается быстрее, чем через дробную часть и ввод порядка.
Особенность не уменьшения числа по окончании цикла, с учетом того, что фактически число может сильно отличаться от единицы (см. пример выше) позволяет использовать команды FLx для быстрой проверки значения регистра с одновременным переходом при удаче/неудаче.
В документации указано, что таковым является регистр X, но это не так. В режиме вычислений после каждой команды содержимое регистра X сразу же копируется в X2. И поэтому разницы между ними нет. При таком копировании производятся дополнительные проверки (например, на предмет переполнения). Видимо в целях экономии в программном режиме при выполнении большинства(!) команд такое копировании не производится. Более того (а это и есть самое полезное), некоторые команды позволяют сделать и обратное копирование.
Остальные операторы НЕ копируют X в X2. А это, например, означает, что регистр X во время таких операций может содержать сверхчисло (больше или равно 10100). При этом, конечно, нужно учитывать, чтобы команда сам по себе не делала такую проверку на переполнение. Например, F10ˣ делает проверку параметра перед выполнением, а Fx² – нет. Для пояснения рассмотрим такую программу:
00.КНОП 01.5 02.0 03.F10ˣ 04.Fx² 05.Fx=0 06.07 07.Сx 08.С/П
Можно увидеть, что на шаге 04 в регистре X возникает сверхчисло. Причем оператор 05.Fx=0 не выполняется, т.е. идёт переход на адрес 07 (как адрес перехода). В соответствии с таблицей выше, в этом случае копирование X в X2 не происходит, а значит, ошибки не должно возникать. В чем легко убедиться, запустив программ. Но если в программе заменить оператор на 05.Fx≠0, то условие уже будет выполняться, и будет выполняться копирование X в X2, которое приведёт к ошибке. Что также проверяется запуском программы.
Хочу отметить, что команда В/О также является X2-влияющей, т.е. если перед возвратом из подпрограммы в регистре X будет сверхчисло, то произойдёт остановка по ошибке, причем возврат успеет отработать.
Всё это не имело бы практического смысла, если бы не было команд, которые делают обратное копирование X2→X.
00.П→x9 01.П→x1 02.К∧ 03.К{x} 04.Fx≠0 05.77 … 77.. 78.К∨ 79.x→П9
Получается, что оператор . по адресу 77 восстановит R1 в X без сдвига стека, что позволит сразу выполнить бинарную операцию. Обычно само значение из R1 "вычислено", а не храниться в регистре. Обратите внимание, что выбрана не X2-влияющая проверка с переходом.
Ещё пример – использовать оператор . как входной параметр подпрограммы, т.е. эта команда стоит первым оператором. В этом случае, например, после ввода пользователя (после команды С/П, которая всегда копирует X в X2), сначала выполняются некие вычисления, не затрагивающие X2 (в том числе сохранение этих результатов в регистры), а затем вызов подпрограммы, которая таким образом "видит" ввод пользователя.
Пример. Пусть нам нужно обработать ввод выбора пользователя для перемещения в некоем 3-ном лабиринте. Обычно используются клавиши 2,4,6,8,±5 (что соответствует направлению движения). Рассмотрим такую последовательность (в предположении, что выбор пользователя храниться в регистре R9 и значение 0 имеет ещё какой-то дополнительный смысл):
00.П→x9 01.2 02.÷ 03.Fx≠0 04.77 05.Fπ 06.+ 07.x→П9 08.ВП 09.Fx=0 10.55 11.КП→x9
Что здесь происходит? На шаге два мы получим одно из чисел 1, 2, 3, 4, ±2.5 или 0. Условным оператором мы не только отсекаем вариант с нулем, но делаем X→X2. Далее к полученному числу сразу прибавляем π и сохраняем в R9 для дальнейшей косвенной адресации (пусть в R4…R7 хранятся коэффициенты умножения для выполнения движения). Обращаю внимание, что тут использованы не X2-влияющие команды. После команды ВП мы "восстановим" в X то значение, что было после деления на шаге 02, только без первой цифры, т.е. ноль (для 1…4) или ±0.5, чтобы использовать это для последующего ветвления программы (умножение на коэффициент деления, а для ±0.5 можно будет взять знак числа и т.д.). Без использования ВП потребовалось бы использовать или дополнительный регистр, или дополнительные команды по манипуляции со стеком.
Дополнительные условия. Если содержимое X2 равно нулю, то будет восстановлена единица.
Если в момент восстановления (выполнения команды
ВП) содержимое
регистра X меньше нуля,
то вместо удаления первой цифры у числа X2,
она будет заменена на 9.
Например, после выполнения программы
00.Fπ 01./-/ 02.Fπ 03.× 04.x→П9 05.ВП 06.С/П
на экране будет -9.1415926 в R9 будет -π × π (команда по адресу 01 X2-влияющая).
С учетом знаний по знакоцифрам можно провести более простое правило: эта последовательность при восстановлении X2 (если X2 не ноль) первую цифру меняет на знакоцифру числа в регистре X минус 1. Тогда для обычных чисел получается:
Рассмотрим последовательность КНОП ВП. При этом КНОП взята как более нейтральная, могут быть и другие не X2-влияющие команды, назовем это первой командой. Тут важно учитывать:
Пример:
00.Fπ 01.КИНВ 02.КЗН 03.FВx 04.<-> 05.КНОП 06.ВП 07.С/П
В результате получим 1.ELE-6Г9 . Тут дробная часть понятна – это инверсия числа π которое сохраняется X2-влияющей командой по адресу 03. А интересна тут цифра 1 на первом месте, которая появилась в стеке по команде КЗН. Именно она подставляется вместо восьмерки при восстановлении.
Интересно, что первой цифрой может быть и шестнадцатеричная, тем самым можно получить то, что в режиме вычислений трудно сделать. Например,
00.КНОП 01.9 02.F1/x 03.5 04.× 05.КИНВ 06./-/ 07.К{x} 08.КНОП 09.ВП 10./-/ 11.9 12.9 13.С/П
Будет 10(!) "минусов". --.--------99. Тут X2-влияющая команда по адресу 06 фиксирует 8.AAAAAAA (заодно добавляя минус), а оператор дробной части оставляет на первом месте тоже цифру A. Всё это последовательность с ВП успешно объединяет, а порядок -99 в конце дописывается для красоты.
Ещё пример. Пусть нужна подпрограмма, которая из первой цифры (целой части) дробного числа в регистре X делает букву. Например, 1→E, 2→D,… 4→A. В режиме вычислений для этого подошла бы последовательность
00.1 01.0 02.+ 03.КИНВ 04.К{x} 05.ВП 06.1 07.К[x]
С учетом специфики выполнения команды ВП в программном режиме нужно ещё скопировать X→X2 после команды К{x}, т.е.
00.1 01.0 02.+ 03.КИНВ 04.К{x} 05.В↑ 06.ВП 07.1 08.К[x]
Это +1 команда, к тому же стек будет испорчен. Рассмотрим
00.1 01.F10ˣ 02.+ 03.КИНВ 04.К{x} 05.КНОП 06.ВП
Здесь, благодаря восстановлению единицы (шаг 0), на шаге 06, останется только одна шестнадцатеричная цифра. Так недокументированная последовательность сделала подпрограмму короче на два шага.
Нужно понимать, что если число в X2 не нормализовано, то всё равно заменяется первая цифра. Воспользуемся знаниями косвенной адресации:
00.КНОП 01.4 02.x→П7 03.КБП7 04.П→x7 05.КНОП 06.ВП 07.С/П
В данном случае 4 после косвенного перехода становится 00000004, а значит замена первой цифры приведёт к 40000004. , в чем легко убедиться, запустив программу.
Теперь рассмотрим особенности. Если число X до начала последовательности было нулем, то вместо первой цифры устанавливается ноль. Обычно это значит, что число будет без первой цифры (начальный ноль не значащий). Но если он уже и так там был (например, число – это результат косвенной адресации с ведущими нулями), тогда никаких полезных действий не будет. Впрочем, иногда, именно это различие можно использовать, чтобы узнать, выполнялась ли косвенная адресация или нет.
Если X2 нулевое, то тут как бы проявляется известный трюк с ВП, который 0 превращает в 1. В данном случае первая цифра числа X увеличивается на 1. Вот фрагмент:
00.КНОП 01.0 03.<-> 04.КНОП 05.ВП 06.С/П
Если на вход этой программе дать 5, то выдаст 6. А если 9, то выдаст… A(!), потом аналогично B, C, D, E, F. Впрочем, последнее лучше тут же заменить на 0 и прочистить стек – пустышки коварны (во всяком случае, если на вход передать F, то программа однозначно будет перекручена во что-то неузнаваемое). Ещё пример:
00.F1/x 01.КНОП 02.ВП 03.С/П
На вход -9. , на выходе первая цифра от обратной величины -1. .
Для условных переходов это работает только если идёт переход на адрес. Если же условие выполняется (т.е. без перехода), то поведение будет как указано выше (фактически команда будет проигнорирована). Если первая после перехода не ВП, то тоже по правилам для ВП ранее. Пример.
00.КНОП 01.1 02.0 03.x→П8 04.Fx² 05.КБП8 … 10.ВП 11.С/П
После остановке на экране будет 70. , т.е. восстановлено X2 = 10 (вместо 100), и первая цифра заменена на 7. Ещё пример.
00.КНОП 01.КНОП 02.8 03.x→П9 04.КП→x9 05.П→x9 06.Fπ 07.КБП9 08.ВП 09.С/П
По уже указанными правилам будет 70000008. . Если заменить команду по адресу 07 на Кx=09, то ничего не измениться, т.к. условие не выполниться и будет переход. Но если заменить на Кx≠09, то уже условие выполниться, перехода не будет, и сработает старое правило, т.е. будет использована первая цифра числа в X, т.е. 30000008. .
00.КНОП 01.0 02.<-> 03.КНОП 04.ВП 05.ВП 06.С/П
На вход 6, на выходе D (6 + 7 = 13). На вход D, на выходе B (13 + 14 = 27 = 16 + 11).
00.КНОП 01.1 02.5 03.Fx² 04.Fx² 05.КНОП 06.. 07.ВП 08.С/П
Результат 55, потому что 15² = 225, 225² = 50625 и «выигрывает» ВП, который восстановит X2(15), с первой цифрой 5. Если убрать второй КНОП, то результат будет 25 (2 от 225), т.е. второй Fx² будет проигнорирован, как будто команда . отодвинула ВП вглубь. Если поменять КНОП и . местами, то будет 15, т.к. . восстановит 15 и уже с ним работает ВП, как в ручном режиме. Другой пример:
00.КНОП 01.1 02.5 03.Fπ 04.x→П9 05.КНОП 06.. 07.ВП 08.С/П
Здесь . отодвигает до x→П9, т.е. восстановится 15 с цифрой 3, т.е. 35. Если убрать КНОП, то поведение будет как у x→П9 ВП, т.е. 15 без первой цифры (5)".
01.B↑ 02.ВП 03.2 04./-/ 05.С/П
Очевидно, что тут произойдёт деление на 100. Этот код ведёт себя в соответствии с документацией. Немного дополним его:01.B↑ 02.ВП 03.2 04.Fπ 05.Fx² 06.Fx² 07./-/ 08.С/П
Если посмотреть, выглядит так, что после ввода порядка (умножения на 100) мы берем ещё π, два раза возводим его в квадрат, а затем делаем отрицательным. На самом деле это фрагмент делает то же, что и первый! Операции ввода числа работают с X2, в промежутке все команды – не X2-влияющие, они, конечно, будут выполнены, но результат будет отброшен при выполнении команды /-/, которая продолжит ввод числа (в данном случае порядка) в регистре X2. То, что промежуточные команды выполнялись, можно узнать по содержимому стека – в регистре Y будет входное значение, умноженное на 100, а в X1 – квадрат от π.Ещё пример. Известно (хотя не документировано), что для запрета ввода точки при вводе порядка, сочетание команд ВП и . генерит ошибку. Более того, в отличии от остальных способов получения ошибки он отличается тем, что
Но это сочетание также работает с X2, т.е. игнорирует все не X2-влияющие команды. В связи с этим становится ясно, что следующий фрагмент:
01.B↑ 02.Fx² 03.ВП 04.Fx² 05.Fx² 06.. 07.Fx² 08.С/П
остановится по ошибке уже на команде . (т.е. следующим для исполнения будет адрес 07) и успеет возвести в квадрат только дважды (первый квадрат, как не X2-влияющий будет отброшен по команде ВП).Для простоты рассмотрим ситуации, когда число состоит из одной шестнадцатеричной цифры. Далее будем её обозначать буквой H. Для более ясного порядка операндов будем использовать стандартные обозначения X и Y.
Y∖H | A | B | C | D | E |
---|---|---|---|---|---|
0 | 0 | 1 | 2 | 3 | 4 |
1 | 1 | 2 | 3 | 4 | 5 |
2 | 2 | 3 | 4 | 5 | 0 |
3 | 3 | 4 | 5 | 0 | 1 |
4 | 4 | 5 | 0 | 1 | 2 |
5 | 5 | 0 | 1 | 2 | 3 |
6 | 0 | 1 | 2 | 3 | 4 |
7 | 1 | 2 | 3 | 4 | 5 |
8 | 2 | 3 | 4 | 5 | 6 |
9 | 3 | 4 | 5 | 6 | 7 |
A | 4 | 5 | 6 | 7 | 8 |
B | 5 | 6 | 7 | 8 | 9 |
C | 6 | 7 | 8 | 9 | 10 |
D | 7 | 8 | 9 | 10 | 11 |
E | 8 | 9 | 10 | 11 | 12 |
10 | | 10 | 21 | 22 | 24 | 24 |
11 | 21 | 22 | 23 | 24 | 25 |
12 | 22 | 23 | 24 | 25 | 26 |
13 | 23 | 24 | 25 | 26 | 27 |
14 | 24 | 25 | 26 | 27 | 28 |
15 | 25 | 26 | 27 | 28 | 29 |
16 | 26 | 27 | 28 | 29 | 30 |
17 | 27 | 28 | 29 | 30 | 31 |
18 | 28 | 29 | 30 | 31 | 32 |
19 | 29 | 30 | 31 | 32 | 33 |
1A | 30 | 31 | 32 | 33 | 34 |
1B | 31 | 32 | 33 | 34 | 35 |
1C | 32 | 33 | 34 | 35 | 20 |
1D | 33 | 34 | 35 | 20 | 21 |
1E | 34 | 35 | 20 | 21 | 22 |
1F | 35 | 20 | 21 | 22 | 23 |
Вычисление идёт как шестнадцатеричное, остаток по модулю 16, а от
него берётся последняя цифра (чтобы осталась одна).
X = ((X + Y) mod 16) mod 10.
Если Y двузначное, то уже две цифры X = ((X + Y) mod 256)
mod 100, а значит получается как
обычное сложение, если число двузначное.
Если Y дробное, то целая часть как выше,
а дробная сохраняется.
Y∖H | A | B | C | D | E |
---|---|---|---|---|---|
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 | 1 | 0 | -1 | -2 | -3 |
C | 2 | 1 | 0 | -1 | -2 |
D | 3 | 2 | 1 | 0 | -1 |
E | 4 | 3 | 2 | 1 | 0 |
10 | | 0 | 15 | 14 | 13 | 12 |
11 | 1 | 16 | 15 | 14 | 13 |
12 | 2 | 17 | 16 | 15 | 14 |
13 | 3 | 18 | 17 | 16 | 15 |
14 | 4 | 19 | 18 | 17 | 16 |
15 | 5 | 20 | 19 | 18 | 17 |
16 | 6 | 21 | 20 | 19 | 18 |
17 | 7 | 22 | 21 | 20 | 19 |
18 | 8 | 23 | 22 | 21 | 20 |
19 | 9 | 24 | 23 | 22 | 21 |
1A | 0 | 25 | 24 | 23 | 22 |
1B | 1 | 10 | 25 | 24 | 23 |
1C | 2 | 11 | 10 | 25 | 24 |
1D | 3 | 12 | 11 | 10 | 25 |
1E | 4 | 13 | 12 | 11 | 10 |
1F | 5 | 14 | 13 | 12 | 11 |
Выглядит похоже на X = (Y − X) mod 16, но не всегда понятно,
когда
берётся заём, а когда нет. Число A какое-то особенное для двузначных.
Для трех- и выше значных повторяется как для двузначных,
т.е. 100 − A = 90, 100 − B = 105,… ,
109 − E = 111.
X∖H | A | B | C | D | E |
---|---|---|---|---|---|
0 | 10 | 11 | 12 | 13 | 14 |
1 | 9 | 0 | 1 | 2 | 3 |
2 | 8 | 9 | 0 | 1 | 2 |
3 | 7 | 8 | 9 | 0 | 1 |
4 | 6 | 7 | 8 | 9 | 0 |
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 | -1 | 0 | 1 | 2 | 3 |
C | -2 | -1 | 0 | 1 | 2 |
D | -3 | -2 | -1 | 0 | 1 |
E | -4 | -3 | -2 | -1 | 0 |
10 | | 0 | 1 | 2 | 3 | 4 |
11 | -1 | 0 | 1 | 2 | 3 |
12 | -2 | -1 | 0 | 1 | 2 |
13 | -3 | -2 | -1 | 0 | 1 |
14 | -4 | -3 | -2 | -1 | 0 |
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), и это можно использовать
для получения нуля в любой степени. Пример (в ручном режиме):
И вот мы получили 0. 99.
Y∖H | A | B | C | D | E |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 |
1 | 10 | 10 | 10 | 10 | 0 |
2 | 20 | 20 | 20 | 20 | 0 |
3 | 30 | 30 | 30 | 30 | 0 |
4 | 40 | 40 | 40 | 40 | 0 |
5 | 50 | 50 | 50 | 50 | 0 |
6 | 60 | 60 | 60 | 60 | 0 |
7 | 70 | 70 | 70 | 70 | 0 |
8 | 80 | 80 | 80 | 80 | 0 |
9 | 90 | 90 | 90 | 90 | 0 |
A | 00 | 00 | 00 | 00 | 0 |
B | 10 | 10 | 10 | 10 | 0 |
C | 20 | 20 | 20 | 20 | 0 |
D | 30 | 30 | 30 | 30 | 0 |
E | 40 | 40 | 40 | 40 | 0 |
10 | | 100 | 100 | 100 | 100 | 0 |
11 | 110 | 110 | 110 | 110 | 0 |
12 | 120 | 120 | 120 | 120 | 0 |
13 | 130 | 130 | 130 | 130 | 0 |
14 | 140 | 140 | 140 | 140 | 0 |
15 | 150 | 150 | 150 | 150 | 0 |
16 | 160 | 160 | 160 | 160 | 0 |
17 | 170 | 170 | 170 | 170 | 0 |
18 | 180 | 180 | 180 | 180 | 0 |
19 | 190 | 190 | 190 | 190 | 0 |
1A | 200 | 200 | 200 | 200 | 0 |
1B | 210 | 210 | 210 | 210 | 0 |
1C | 220 | 220 | 220 | 220 | 0 |
1D | 230 | 230 | 230 | 230 | 0 |
1E | 240 | 240 | 240 | 240 | 0 |
1F | 250 | 250 | 250 | 250 | 0 |
Поразительное однообразие, почти все ведут себя как 10. Не ясно, чем
так отличается E, но ноль получается и для многозначных чисел.
Для двузначных соответственно, т.е. C × 20 = 200, но E × 20 = 0.
X∖H | A | B | C | D | E |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 2 | 3 | 4 |
2 | 4 | 6 | 8 | 10 | 12 |
3 | 4 | 1 | 4 | 23 | 10 |
4 | 8 | 2 | 0 | 20 | 24 |
5 | 50 | 11 | 32 | 53 | 42 |
6 | 0 | 22 | 44 | 50 | 40 |
7 | 10 | 33 | 40 | 63 | 54 |
8 | 20 | 44 | 52 | 60 | 68 |
9 | 30 | 55 | 64 | 73 | 82 |
A | 00 | 10 | 20 | 30 | 40 |
B | 00 | 10 | 20 | 30 | 40 |
C | 00 | 10 | 20 | 30 | 40 |
D | 00 | 10 | 20 | 30 | 40 |
E | 0 | 0 | 0 | 0 | 0 |
10 | | 00 | 10 | 20 | 30 | 40 |
11 | 10 | 21 | 32 | 43 | 54 |
12 | 04 | 16 | 28 | 40 | 52 |
13 | 14 | 11 | 24 | 53 | 50 |
14 | 08 | 22 | 20 | 50 | 4 |
15 | 990 | 021 | 052 | 923 | 922 |
16 | 000 | 032 | 904 | 920 | 920 |
17 | 010 | 043 | 900 | 933 | 934 |
18 | 020 | 054 | 912 | 930 | 948 |
19 | 030 | 905 | 924 | 943 | 962 |
1A | 940 | 960 | 980 | 0 | 20 |
1B | 940 | 960 | 980 | 0 | 20 |
1C | 940 | 960 | 980 | 0 | 20 |
1D | 940 | 960 | 980 | 0 | 20 |
1E | 00 | 10 | 20 | 30 | 40 |
1F | 00 | 10 | 20 | 30 | 40 |
Тут уже трудно поддаётся логике. На практике автор как-то использовал D. С одной стороны, это изображение, с другой – коэффициент 10 (см. таблицу H × Y), а самое основное – это проверка битового сдвига. При умножении дробной части, содержащей 1, 2, 4, 8 (как бы биты) возможен выход за диапазон, т.е. 0.5 или 1.6. Так вот, при умножении на D по указанной таблице, результат получался из одной цифры, если всё нормально, или из двух, при выходе за диапазон.
Для двухзначных результат бывает не нормализованным: обратите внимание на ведущие нули в некоторых случаях. На этом фоне 14 × E выглядит как белая ворона.
X∖H | A | B | C | D | E |
---|---|---|---|---|---|
0 | ЕГГ0Г | ЕГГ0Г | ЕГГ0Г | ЕГГ0Г | ЕГГ0Г |
1 | 0 | 1 | 2 | 3 | 4 |
2 | 5 | 5.5 | 6 | 6.5 | 7 |
3 | 3.3333333 | 3.6666666 | 4 | 4.3333333 | 4.6666666 |
4 | 2.5 | 2.75 | 3 | 3.25 | 3.5 |
5 | 2 | 2.2 | 2.4 | 2.6 | 2.8 |
6 | 1.6666666 | 1.8333333 | 2 | 2.1666666 | 2.3333333 |
7 | 1.4285714 | 1.5714285 | 1.7142857 | 1.8571428 | 2 |
8 | 1.25 | 1.375 | 1.5 | 1.625 | 1.75 |
9 | 1.1111111 | 1.2222222 | 1.3333333 | 1.4444444 | 1.5555555 |
A | 1 | 1.1 | 1.2 | 1.3 | 1.4 |
B | 8.4444443^-01 | 1 | 1.2525252 | 1.3434343 | 1.4343434 |
C | ЕГГ0Г | ЕГГ0Г | 1 | 1.23 | 1.3 |
D | 4.^-01 | 6.^-01 | 8.^-01 | 1 | 1.2 |
E | 5.2929292^-01 | 2.2929292^-01 | 5.2929292^-0 | 8.2929292^-01 | 1 |
10 | | 0.^-01 | 1.^-01 | 2.^-01 | 3.^-01 | 4.^-01 |
11 | 9.090909^-01 | 0.^-01 | 0.9090909^-01 | 1.8181818-01 | 2.7272727-01 |
12 | 8.3333333^-01 | 9.1666666^-01 | 0.^-01 | 0.8333333-01 | 1.6666666-01 |
13 | 7.6923076^-01 | 8.4615384^-01 | 9.2307692^-01 | 0.^-01 | 0.7692307-01 |
14 | 7.1428571^-01 | 7.8571428^-01 | 8.5714285^-01 | 9.2857142-01 | 0.^-01 |
15 | 6.6666666^-01 | 7.3333333^-01 | 8.^-01 | 8.6666666-01 | 9.3333333-01 |
16 | 6.25^-01 | 6.875^-01 | 7.5^-01 | 8.125-01 | 8.75-01 |
17 | 5.8823529^-01 | 6.4705882^-01 | 7.0588235^-01 | 7.6470588-01 | 8.2352941-01 |
18 | 5.5555555^-01 | 6.1111111^-01 | 6.6666666^-01 | 7.2222222-01 | 7.7777777-01 |
19 | 5.2631578^-01 | 5.7894736^-01 | 6.3157894^-01 | 6.8421052-01 | 7.368421-01 |
1A | 5.^-01 | 5.1^-01 | 6.3157894-01 | 6.1^-01 | 7.^-01 |
1B | 5.0330001^-01 | 6.^-01 | 7.0001032-01 | 7.8330001-01 | 8.2330001-01 |
1C | 6.^-01 | 7.0003809^-01 | 7.8100038-01 | 8.1810003-01 | 8.100038-01 |
1D | 7.0005899^-01 | 7.9000589^-01 | 8.^-01 | 9.0005899-01 | 9.9000589-01 |
1E | 5.64^-01 | 5.90002^-01 | 6.4^-01 | 6.60002-01 | 7.24-01 |
1F | 5.4000299^-01 | 5.3223099^-01 | 6.153223-01 | 7.0002999-01 | 7.7000299-01 |
Здесь кроме случая X = 1, и некоторых двузначных это обычное
деление нормализованного шестнадцатеричного числа. Пример:
E ÷ 7 = 14 ÷ 7 = 2.
Правда и здесь встречаются не нормализованные числа
(пример D ÷ 12, или C ÷ 11, которое на порядок меньше,
чем A ÷ 11).
Встречаются и не нормализованные нули, пример: С ÷ 12, и
выглядит, как указано:
0. -01.
Впрочем, в косвенной адресации мы
уже встречались с подобными нулями.
Y∖H | A | B | C | D | E |
---|---|---|---|---|---|
0 | 9.090909^-01 | 9.9099099^-01 | 4.4444443 | 9.9099099^-01 | 9.9099099^-01 |
1 | ЕГГ0Г | 9.099099^-01 | 9.9099099^-01 | 9.9099099^-01 | 9.9099099^-01 |
2 | ЕГГ0Г | 8.4444443^-01 | 9.099099^-01 | 9.9099099^-01 | 9.9099099^-01 |
3 | ЕГГ0Г | 6.4444443^-01 | ЕГГ0Г | 9.099099^-01 | 9.9099099^-01 |
4 | ЕГГ0Г | 4.4444443^-01 | ЕГГ0Г | 8.^-01 | 9.099099^-01 |
5 | ЕГГ0Г | 2.4444443^-01 | ЕГГ0Г | 0.^-01 | 2.929292^-02 |
6 | ЕГГ0Г | 6.4444443^-01 | ЕГГ0Г | 2.^-01 | 3.2929292^-01 |
7 | ЕГГ0Г | 4.4444443^-01 | ЕГГ0Г | 4.^-01 | 6.2929292^-01 |
8 | ЕГГ0Г | 2.4444443^-01 | ЕГГ0Г | 0 | 9.2929292^-01 |
9 | ЕГГ0Г | 0.4444443^-01 | ЕГГ0Г | 2.^-01 | 2.2929292^-01 |
A | 1 | 8.4444443^-01 | ЕГГ0Г | 4.^-01 | 5.2929292^-01 |
B | 1.1 | 1 | ЕГГ0Г | 6.^-01 | 2.2929292^-01 |
C | 1.2 | 1.2525252 | 1 | 8.^-01 | 5.2929292^-01 |
D | 1.3 | 1.3434343 | 1.23 | 1 | 8.2929292^-01 |
E | 1.4 | 1.4343434 | 1.3 | 1.2 | 1 |
10 | | ЕГГ0Г | 9.09909 | 9.9099099 | 9.9099099 | 9.9099099 |
11 | ЕГГ0Г | 9.099099 | ЕГГ0Г | 9.8 | 9.0292929 |
12 | ЕГГ0Г | 9.099099 | ЕГГ0Г | 0 | 9.3292929 |
13 | ЕГГ0Г | 9.099099 | ЕГГ0Г | 0.2 | 9.6292929 |
14 | ЕГГ0Г | 9.099099 | ЕГГ0Г | 0.4 | 9.9292929 |
15 | ЕГГ0Г | 9 | ЕГГ0Г | 9 | 0.22922929 |
16 | ЕГГ0Г | 9.2525252 | ЕГГ0Г | 9.2 | 0.5292929 |
17 | ЕГГ0Г | 9.3434343 | ЕГГ0Г | 9.4 | 9.2292929 |
18 | ЕГГ0Г | 9.4343434 | ЕГГ0Г | 9.6 | 9.5292929 |
19 | ЕГГ0Г | 9.5252525 | ЕГГ0Г | 9.8 | 9.8292929 |
1A | ЕГГ0Г | 9.6 | 9.9909909 | 9.9909909 | 9.9909909 |
1B | ЕГГ0Г | 9.8525252 | 9.9909909 | 9.9909909 | 9.9909909 |
1C | ЕГГ0Г | 9.9434343 | 9.9909909 | 9.9909909 | 9.9909909 |
1D | ЕГГ0Г | 0.0343434 | 9.9909909 | 9.9909909 | 9.9909909 |
1E | ЕГГ0Г | 9.099099 | 9.9909909 | 9.9909909 | 9.9909909 |
1F | ЕГГ0Г | 7.4444443 | 9.9909909 | 9.9909909 | 9.9909909 |
Тут логики не наблюдается. Очень похожие, но разные числа:
9.099099^-01 и 9.9099099^-01 (или 9.099099 и 9.9099099).
Также интересно, что ноль разделить на H вовсе не ноль.
А самое главное, тут не просто ЕГГ0Г, а плохой ЕГГ0Г,
который ранее не встречался. После его появления ПМК в дальнейшем
(до выключения ПМК) при выполнении многих операций
всегда выдаёт ЕГГ0Г. Вот список таких операций.
Ошибка возникает как в режиме вычислений, так и в программном режиме. Интересно, что F↻ выдаёт ошибку, а FВx – нет.
"Синии" функции работают нормально. Видимо такой ЕГГ0Г "сводит с ума" только один микроконтроллер.
H∖F(H) | A | B | C | D | E |
---|---|---|---|---|---|
Fx² | 00 | 10 | 20 | 30 | 0 |
F√ | 3.1622776 | 3.3166247 | 3.4641016 | 3.6055512 | 3.7416573 |
F1/x | ЕГГ0Г | 9.099099^-01 | 9.9099099^-01 | 9.9099099^-01 | 9.9099099^-01 |
Feˣ | 22026.467 | 59874.133 | 162754.78 | 442413.37 | 1202604.3 |
F10ˣ | 1.^+10 | 1.^+0L | 1.^+0C | 1.^+0Г | 1.^+0E |
Flg | 1 | 41.823681 | 42.40274 | 42.816354 | 43.126564 |
Fln | 2.3025851 | 96.302585 | 97.635918 | 98.588299 | 99.302585 |
Fx¹ | 10 | 6.6631773^+41 | 2.5277867^+42 | 6.551706^+42 | 1.3383338^+43 |
К[x] | 0 | 1 | 2 | 3 | 4 |
Про более редкие операции переводов градусов/часов сказано в приложении по командам. Числа, где порядок содержит шестнадцатеричные значения, рассмотрены в разделе косвенной адресации Порядок содержит шестнадцатеричные цифры .
Случаи с двойными и более шестнадцатеричными числами (в том числе в дробной части) не рассмотрены, как очень редко встречающиеся. Там тоже можно построить подобные таблицы, но проще посмотреть результат под конкретное число.
Напомню, что знакоцифра успешно "выживает" при косвенной адресации. Обратите внимание, что далее в этом разделе очень часто в примерах у чисел вначале указывается именно знакоцифра.
00.КНОП 01.5 02.0 03.F10ˣ 04.Fx² 05.Fx² 06.Fx² 07.× 08.x→П9 09.П→x9 10.П→xc 11.ВП 12.7 13.С/П
На вход ей передатся число (0…8.9999999], на выходе получаем
"хвост" оборотня из регистра Rc. Поэтому их
и называют 0C-оборотни, что сами они на экран дают ноль, а "хвост"
сбрасывают в регистр Rc. Передавать на вход
девятку неинтересно, т.к. на выходе получим 10, точнее цифру А,
т.е. обычный минус.
Пример, передав 1, получим
2E. ,
передав 6, получим
7E. ,
и т.д. Нажав /-/ получим другую знакоцифру
(вычислить какую именно легко, т.к. сумма таких парных знакоцифр = 11).
Кстати, если мантисса нулевая, то после /-/
будет -0 для любой знакоцифры.
Этот способ получения знакоцифры не очень удобен, тем более, что для получения других цифр мантиссы нужно сильно "напрячься", например многократно прокрутив счётчик косвенной адресации. Есть способ лучше.
Так вот, если первая цифра 8-значного числа тоже шестнадцатеричная, то перенос из старшего разряда происходит в знаковый разряд, т.е. появляется знакоцифра.
Для начала возьмем обычную цифру E (для краткости нестандартным способом), которая потом пригодится: 1 К- ВП x→Пe. Затем сделаем её первой (число 8-значным): ВП 7 x→П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 | 4N | 5N | 6N | 7N | 8N | 9N | |
Fx<0 | Нет | Нет | Нет | Да | Да | Да | Да | Да |
Fx≥0 | Да | Да | Да | Да | Да | Нет | Нет | Нет |
Как видно, далеко не всё так прозрачно. 5N и
6N получаются и отрицательные и положительные.
Сравнения с нулем, кстати, для всех проходят корректно (не ноль, если
N не ноль).
Рассмотрим другие функции, для косвенного определения знака числа.
+N | 2N | 3N | 4N | 5N | 6N | 7N | 8N | 9N | -N | |
+N | + | + | + | + | + | + | + | + | − | − |
2N | + | + | + | + | + | + | + | − | − | − |
3N | + | + | + | + | + | + | − | − | − | − |
4N | + | + | + | + | + | − | − | − | − | − |
5N | + | + | + | + | − | − | − | − | − | − |
6N | + | + | + | − | − | − | − | − | − | − |
7N | + | + | − | − | − | − | − | − | − | − |
8N | + | − | − | − | − | − | − | − | − | + |
9N | − | − | − | − | − | − | − | − | + | + |
-N | − | − | − | − | − | − | − | + | + | + |
Тут нужно остановиться на нуле. Как указано в функции К{x}, может получится ноль со знакоцифрой. Так вот, он ведёт себя как обычный ноль. Единственное отличие – это в функциях сравнения. Такой ноль ведёт себя так, как указано ранее для чисел со знакоцифрой. В частности число 50. операторы Fx<0, Fx≥0, Fx=0 пропустят, как удовлетворяющее условию.
П→xe 9 9 9 9 9 9 9 8 В/О С/П Получили E9999998. . Затем x→П4 КП→x4 9 П→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. , а затем применим следующий алгоритм:
По такому алгоритму и цифре E легко получается 2EEEEEEEE. .
Понятно, что в пункте 1 алгоритма можно вписать любую цифру (кроме E). И начать с числа с любой знакоцифрой. Таким образом можно получить практически любую мантиссу с любой знакоцифрой (кроме цифр F или ведущих нулей).
Более того, такие знакоцифры влияют и на остальное. Возьмем, для примера Восстановление X2 с отбрасыванием первой цифры . На самом деле указанная там последовательность при восстановлении вместо первой цифры записывает знакоцифру − 1, что для положительного числа равно нулю, т.е. отбрасывание цифры. А вот если знакоцифра есть, то эта последовательность запишет её минус один вместо первого знака. Отсюда же понятно, почему для отрицательных запишет 9, это знак минус, как цифра A (= 10) минус один. Причем знакоцифра останется на месте.
На практике автор не использовал такие числа просто в силу того, что в момент активного использования ПМК не обладал этими знаниями. И хотя нестандартность поведения многих функций с такими числами определяется только реакцией на знак числа, всё равно можно предположить, что и это можно использовать для оптимизации.
Если вы думаете, что мантисса может быть только единица, то обрадую, что можно и другие. Для исследования чисел с разными мантиссами приведем программу, которая регистры 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. В общем случае, нулевая степень (пусть с минусом) в математическом смысле ничего не меняет, т.е. число должно быть как бы без степени. И большинство операций так его и воспринимаю, но есть исключения. Пройдемся по всем операциям/функциям, использую значения регистров, внесенные программой выше.
Для двух и более значных операндов, выполняется как обычно. Примеры:
2.^-00 + 51 = 53:
П→x2
5
1
+ =
53. .
21 − 3.^-00 = 18:
2
1
П→x3
- =
18. .
eπ − 3.^-00 = 20.14069:
Fπ
Feˣ
П→x3
- =
20.14069 .
E1 + 2^.-00 = 43:
1
1
К-
ВП
КНОП
П→x2
+ =
43. .
Если дробное число (целая часть нулевая), то выполняется как обычно.
Примеры:
0.5 + 4.^-00 = 4.5:
0
.
5
П→x4
+ =
4.5 .
E.1^-01 + 4.^-00 = 5.41:
0
.
9
Fx²
К-
ВП
КНОП
П→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:
П→x9
0
+ =
9. .
Для сравнения:
9.^-00 + 1 = 9:
П→x9
1
+ =
19. .
Если число в X больше единицы, но имеет порядок не меньше 60, то число
N снова воспринимается, как число в степени -160.
1.^62 × 7^.-00 = 7.^-98:
П→x7
6
2
F10ˣ
× =
7. -98.
1.^60 × 7^.-00 = 7.^-100:
П→x7
6
0
F10ˣ
× =
0. .
-1.^99 × 7^.-00 = -7.^-61:
П→x7
9
9
F10ˣ
/-/
× =
-7. -61.
Если число в X меньше единицы, но имеет порядок меньше -40, то
умножение снова проводится как обычно:
1.^-43 × 6^.-00 = 6.^-43:
П→x6
4
3
/-/
F10ˣ
× =
6. -43.
1.^-40 × 6^.-00 = 6.^-200:
П→x6
4
0
/-/
F10ˣ
× =
0. .
-1.^-99 × 6^.-00 = -6.^-99:
П→x6
9
9
/-/
F10ˣ
/-/
× =
-6. -99.
Напомню, если число N находится в регистре X, то происходит обычное
умножение, но если в Y тоже N (может другое),
то будет как выше степень -160.
3^.-00 × 7^.-00 = 2.1^-159:
П→x3
П→x7
× =
0. .
Для проверки
01.П→x3
02.П→x7
03.×
04.8
05.0
06.F10ˣ
07.×
08.С/П =
2.1 -79.
Для деления ситуация похожая. Необычность возникает,
только когда
делят на N (N в регистре X). Если число в регистре Y по модулю в
диапазоне 1 ⩽ Y < 10 (сюда же попадают шестнадцатеричные цифры),
то в операциях деления N выглядит как число со степенью -160.
8^.-00 ÷ 2 = 4:
П→x8
2
÷ =
4. .
18 ÷ 2^.-00 = 9:
1
8
П→x2
÷ =
9. .
0.8 ÷ 2^.-00 = 0.4:
0
.
8
П→x2
÷ =
4. -01.
8 ÷ 2^.-00 = 4^.160:
8
П→x2
÷ =
ЕГГ0Г. .
Для проверки составим программу:
00.Сx
01.8
02.П→x2
03.÷
04.7
05.0
06.F10ˣ
07.÷
08.С/П
После В/0
С/П на экране будет
4. 90.
Единственное исключение – это когда в Y тоже число N. Тогда
деление снова проходит как обычно.
8^.-00 ÷ 2^.-00 = 4:
П→x8
П→x2
÷ =
4. .
F√.
При взятии корня вычисляется как обычно, но результат делится на
1048, т.е. степень числа будет -48. Пример:
√(4.^-00) = 2.^-48:
П→x4
F√ =
2. -48.
√(E.^-00) = 3.7416573^-48:
П→xe
F√ =
3.7416573-48.
F1/x.
Обратная величина считает число N числом в степени -160.
1 ⁄ (2^.-00) = 5.^159:
П→x2
F1/x =
ЕГГ0Г. .
Для проверки составим программу:
00.П→x2
01.F1/x
02.7
03.0
04.F10ˣ
05.÷
06.С/П
После В/0
С/П на экране будет
5. 89.
Тригонометрические функции рассмотрим только на нескольких значениях.
Для Fsin только в радианах будут отличия:
X | Р | ГРД | Г |
---|---|---|---|
1.^-00 | 8.4147098^-01 | 1.5707317^-02 | 1.7452405^-02 |
1 | 8.4147103^-01 | 1.5707317^-02 | 1.7452405^-02 |
Для Fcos тоже:
X | Р | ГРД | Г |
---|---|---|---|
2.^-00 | 4.4721363^-01 | 9.9950656^-01 | 9.9939082^-01 |
2 | -4.1614688^-01 | 9.9950656^-01 | 9.9939082^-01 |
Для Ftg тоже:
X | Р | ГРД | Г |
---|---|---|---|
3.^-00 | 3 | 4.7158802^-02 | 5.2407778^-02 |
3 | -1.4254648^-01 | 4.7158802^-02 | 5.2407778^-02 |
Для Fsin⁻¹ все отличаются:
X | Р | ГРД | Г |
---|---|---|---|
1.^-00 | 7.853981^-01 | 50.000003 | 45.000002 |
1 | 1.5707963 | 100 | 90 |
2.^-00 | 1.1071486 | 70.483276 | 63.434949 |
2 | ЕГГ0Г | ЕГГ0Г | ЕГГ0Г |
Для Fcos⁻¹ все отличаются:
X | Р | ГРД | Г |
---|---|---|---|
1.^-00 | 7.8539812^-01 | 49.999997 | 44.999998 |
1 | 0 | 00 | 00 |
2.^-00 | 4.6364761^-01 | 29.516724 | 26.565051 |
2 | ЕГГ0Г | ЕГГ0Г | ЕГГ0Г |
Ftg⁻¹ для чисел N всегда даёт ноль:
X | Р | ГРД | Г |
---|---|---|---|
1.^-00 | 0 | 0 | 00 |
1 | 7.853981^-01 | 50.000003 | 45.000002 |
8.^-00 | 0 | 0 | 00 |
8 | 1.4464413 | 92.083315 | 82.874983 |
Fx²
вычисляет как обычно, но порядок числа при этом уменьшается на 160.
При нормализации получается ноль:
2^.00² = 4^.-160:
П→x2
Fx²
даёт 0. .
8^.00² = 6.4^-159:
П→x8
Fx²
даёт 0. .
Но небольшая проверка показывает истинную картину:
00.П→x8
01.Fx²
02.7
03.0
04.F10ˣ
05.×
06.С/П
После В/0
С/П на экране будет
6.4 -89.
Feˣ вычисляется как обычно.
Flg выдаёт так:
X | Flg |
1^.-00 | 0 |
---|---|
2^.-00 | -43.128418 |
3^.-00 | -42.952326 |
4^.-00 | -42.827388 |
5^.-00 | -42.730478 |
6^.-00 | -42.651297 |
7^.-00 | -42.58435 |
8^.-00 | -42.526358 |
9^.-00 | -42.475205 |
A^.-00 | -42.429448 |
B^.-00 | -1.6057671 |
C^.-00 | -1.0267076 |
D^.-00 | -6.1309386^-01 |
E^.-00 | -3.0288357^-01 |
Fln тоже начиная с B.^-00 выдаёт другой порядок:
X | Fln |
1^.-00 | 0 |
---|---|
2^.-00 | -99.306853 |
3^.-00 | -98.901388 |
4^.-00 | -98.613706 |
5^.-00 | -98.390562 |
6^.-00 | -98.208241 |
7^.-00 | -98.05409 |
8^.-00 | -97.920559 |
9^.-00 | -97.802776 |
A^.-00 | -97.697415 |
B^.-00 | -3.6974155 |
C^.-00 | -2.3640818 |
D^.-00 | -1.4117008 |
E^.-00 | -6.974152 ^-01 |
F10ˣ выдаёт ошибку не из-за переполнения, а как обычно: порядок вне диапазона. Хотя отрицательные значения как результат возведения в степень конечно удивляют.
X | F10ˣ |
1^.-00 | ЕГГ0Г |
---|---|
2^.-00 | -13.02585 |
3^.-00 | -36.051703 |
4^.-00 | -59.077562 |
5^.-00 | -7.0398713 |
6^.-00 | -6.4318666 |
7^.-00 | -5.5095304 |
8^.-00 | -3.9441377 |
9^.-00 | -7.0326326^-01 |
A^.-00 | ЕГГ0Г |
B^.-00 | ЕГГ0Г |
C^.-00 | ЕГГ0Г |
D^.-00 | ЕГГ0Г |
E^.-00 | 1 |
Fxʸ ведёт себя тоже нестандартно. Если число N показатель степени, т.е. в регистре Y, то нормально работает только если число в X не меньше числа e (экспонента), а иначе даёт единицу.
Fxʸ | 1.^-00 | 1 | 2.^-00 | 2 | 3^.-00 | 3 |
1 | 1 | 1 | 1 | 1 | 1 | 1 |
2 | 1 | 2 | 1 | 4 | 1 | 7.9999993 |
2.7182817 | 1 | 2.7182817 | 1 | 7.3890551 | 1 | 20.085531 |
2.7182818 | 2.7182818 | 2.7182818 | 7.3890557 | 7.3890557 | 20.085535 | 20.085535 |
3 | 2.9999994 | 2.9999994 | 8.9999984 | 8.9999984 | 26.999992 | 26.999992 |
Если N основание, т.е. в регистре X, то ситуация печальней.
1.^-00 работает как обычно, т.е. даёт единицу при любом показателе.
2^.-00 быстро скатывается в ошибку:
Fxʸ | 2 | 2.29 | 2.30 | 2.31 | 2.32 |
2.^-00 | 5.5355546^-87 | 1.7215634^-99 | 0(=6.3773317^-100) | ЕГГ0Г(=2.3624032^-100) | ЕГГ0Г(просто ошибка аргумента) |
Для остальных показателей и степеней выдаёт ошибку, кроме степени 1. Причем опять идёт изменение уровня начиная с B^.-00.
Fxʸ | 1 |
2.^-00 | 7.4401479^-44 |
3.^-00 | 1.1160242^-43 |
4.^-00 | 1.4880297^-43 |
5.^-00 | 1.8600371^-43 |
6.^-00 | 2.2320432^-43 |
7.^-00 | 2.6040517^-43 |
8.^-00 | 2.9760592^-43 |
9.^-00 | 3.3480707^-43 |
A.^-00 | 3.7200738^-43 |
B.^-00 | 2.478751^-02 |
C.^-00 | 9.4035621^-02 |
D.^-00 | 2.437284^-01 |
E.^-00 | 4.9787053^-01 |
К[x] считает числа N дробными, и выдаёт ноль.
К{x} считает также, и соответственно, не меняет их, даже шестнадцатеричные.
Кmax работает как обычно, причем она, например, числа 8 и 8^.-00 считает равными.
К|x| тоже работает как обычно, т.е. если отрицательное, то минус уберёт, а мантиссу и порядок не трогает.
КЗН тоже работает как обычно, т.е. даёт ±1.
Набор функций
К°→′,
К°→′",
К°←′",
К°←′,
которые обычно редко используются, здесь помогают расширить
диапазон мантисс для чисел N. Дело в том, что они также считают
такие числа дробными, но в отличии от остальных функций при
обработке не трогают порядок. Это значит, что число по-прежнему
остается с нулевой отрицательной степенью (если конечно в результате
не понизится порядок). Пример:
П→x3
К°→′"
К°←′
К°←′
выдаст
2.9999999-00, если чуть изменить:
П→x3
К°→′"
К°←′"
К°←′",
то уже 2.9945599-00
Или
П→xe
К°←′
К°←′
/-/
даст --.16 -00.
Понятно, что таким образом набор мантисс ограничен, но всё-таки позволяет разнообразить набор чисел N.
Логические функции, как обычно, вообще не обращают внимание на порядок и делают свою обычную работу, убирая всё лишнее.
Иногда ради такой "подгонки" делают перестроение программы: перемешивание независимых кусков программы, располагая их по разным адресам. Сюда же относится пример из журнала ТМ №9 за 1985:
60.Fx<0 61.61 62.Fx≥0 63.63 64.С/П
Когда перед остановкой выводилось содержимое нужного регистра при проверке условия. Экономия на том, что адрес перехода совпадает с командой извлечения из регистра.00.Fx≠0 01.06 02.П→x9 03.1 04.+ 05.x→П9
Решение с удалением условия:00.КЗН 01.П→x9 02.+ 03.x→П9
В случае, если X может быть и отрицательным, будет чуть длиннее, но всё равно короче прямого решения:00.КЗН 01.К|x| 02.П→x9 03.+ 04.x→П9
00.КП→x2 01.БП 02.77
которое, кстати, портит стек (и для исправления может потребоваться ещё команда), сделать:00.FL2 01.77
Разумеется, счётчик должен не кончаться или сразу после этого кода идёт проверка по его окончанию. При это для "бесконечности" счётчика иногда делают его отрицательным, если в конце вычислений важна только разница между началом и концом.00.К{x} 01.0 02.FВx 03.К[x] 04.ПП 05.07 06.<-> 07.… 20.+ 21.В/О
Что здесь происходит? Сначала подготавливается на будущее дробная часть, затем заталкивается ноль и оставляется целая часть. Далее вызывается часть кода (часть кода текущей подпрограммы), которая делает обработку над целой частью, а в конце делает сложение (с нулем, в данном случае). Затем целая и дробная часть меняются местами и код повторяется, причем в конце сложение уже делает объединение, а затем возврат. Где экономия? Если бы мы вызывали обрабатываемую часть по очереди, то нам всё равно потребовалось бы разделять на целую и дробную часть, обрабатывать по очереди, а затем объединять сложением. Для этого потребовались бы всё те же команды, кроме 0. Но при это пришлось бы делать дважды ПП, а это две команды. Заменив на одну команду 0 и вызвав свой "хвост", мы сэкономили одну команду.Для тех, кто не хочет идти по ссылке (или высматривать мелкий шрифт скана) опишем игру. Это трехмерный лабиринт (3 этажа размером 7×4), где каждая ячейка на этаже может быть или свободна, или занята – "стена". Цель игры – находясь в некоторой начальной точке лабиринта пройти весь (выход в левой нижней точке на третьем этаже – влево) и собрать максимальное количество сокровищ. Для понимания адресации вот план 1-го этажа из публикации в журнале:
1 | 2 | 3 | 4 | 5 | 6 | 7 | |
---|---|---|---|---|---|---|---|
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 | Fx² | x→Пe | 0 | x→П0 | КСЧ | F10ˣ | FВx | Fxʸ | К∨ |
60 | | Кx→П0 | П→x0 | Кx≥08 |
Положение игрока запоминается в Rb (как бы куда хотим пойти). Начальное количество динамита берем 6, а не 4, т.к. два придётся потратить на удаление возможной стенки в месте игрока – мы же ещё не знаем лабиринт. 36 (6²) – как начальное количество еды. А вот с регистром R0 проводим хитрые манипуляции. Благодаря особенностям косвенной адресации при первом обращении ноль превратится в -99999999, что соответствует регистру R3, затем в -99999998 – R2 и так пока не дойдет до R0, где -99999996 перепишется новым сгенерированным значением (уже положительным!), это и отслеживаем в цикле. Цикл возвращается на адрес 55, который и записан в R8 (точнее -55, но на адресацию это не влияет, а почему минус объясню позже). Сами операнды для К∨ получаются из КСЧ через функции F10ˣ и Fxʸ, которые "размазывают" цифры мантиссы случайного числа на все разряды и делают его более случайным. Причем испытания показали, что выгодней сделать FВx и Fxʸ вместо повтора КСЧ и 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 |
---|---|---|
2 | 1 | 4 |
4 | 2 | 5 |
±5 | ±2.5 | Что-то больше нуля |
6 | 3 | 6 |
8 | 4 | 7 |
10 | 5 | 8 |
Вариант ±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 | - | Fx² | + | С/П |
Сохраняем полученную букву с дробной частью в Rb и оставляем его в стеке для информации игроку. Потом не только извлекаем старое значение ресурса, но и заодно отсекаем дробную часть от буквы (при косвенной адресации), чтобы потом преобразовать её в нужную добавку. Тут нужно пояснить, что именно происходит для вычислении "добавки", в зависимости от значения регистра X (C, D или E). При вычитании срабатывают правила шестнадцатеричной арифметики.
X | После "− 1" | После x² |
---|---|---|
C | 1 | 1 (сокровище) |
D | 2 | 4 (динамит) |
E | 3 | 9 (еды) |
Код борьбы с разбойником полностью взят из оригинала. Главное, чтобы переключатель Р-ГРД-Г был в положении Р. Окончательное значение ресурса сохраняется.
# | | 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 | Fx² | x→Пe | 0 | x→П0 | КСЧ | F10ˣ | FВx | Fxʸ | К∨ |
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 | - | Fx² | + | С/П | Fsin |
A0 | | 1 | + | × | К[x] | Кx→Пb |
Начальные значения констант (которые не меняются между играми):
Вот последовательность для ввода констант:
5
5
/-/
x→П8
4
4
7
3
В↑
8
0
8
К∨
К{x}
ВП
7
/-/
x→П9
1
0
x→П5
2
x→П4
F1/x
x→П7
2
2
КИНВ
К{x}
ВП
1
К[x]
ВП
2
/-/
x→П6
Положение переключателя Р-ГРД-Г должно быть в Р. Перед первой игрой (или каждой, если не хотите аккумулировать сокровища) регистр Rc нужно обнулить.
И напомню порядок начала игры: задаётся начальное положение игрока на плане, например 1.0000001 – можно быстро получить как П→x9 F10ˣ (кто-то можета подумать, что автор даже это предусмотрел, но нет, это, но только это, случайно так совпало). Затем БП 48 С/П
Как не странно, даже такую "плотную" программу можно ещё улучшить. Например, если потребовать в начале каждой игры после ввода начального положения нажатия В↑ перед C/П. Тогда начало можно переместить на адрес 44, где уже есть команда x→Пb, и убрать её с адреса 48. При этом, кстати, будет реализован редкий трюк, связанный с исполнением одних и тех же команд, но в разных адресных ветках вычисления (и с разными целями и результатом). А потом, например, использовать сэкономленную команду для начального обнуления количества сокровищ, например x→Пс по адресу 54.
Код | Вид | Название | Комментарий |
---|---|---|---|
00…09 | 0…9 | Ввод числа |
Ввод цифр идёт даже через границу
С/П. Т.е. если в начале программы
идёт цифра(ы), а перед её запуском тоже ввод, то он продолжиться.
То же касается и разделителя ., он может
как заканчивать ввод в режиме вычислений, так и начинать в
программе – будет воспринят как разделитель целой и дробной
части.
Второе нажатие . для разделения разрядов игнорируется. Если предыдущая команда была не ввод цифры или разделителя, а так же не команда В↑, то предварительно осуществляется сдвиг стека под новое число. |
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 | Feˣ | Возведение в степень числа 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 | Fx² | Возведение в квадрат | Результат проверяется при X→X2, т.е. можно использовать для получения сверхчисел. |
23 | F1/x | Обратная величина | Ошибка деления на ноль возникает безусловно, остальное - при X→X2. |
24 | Fxʸ | Возведение в степень. |
Ошибка, если 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 В↑ ÷ ВП Fx² Cx <-> 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!
8
8
8
В↑
7
7
K∨
K{x}
ВП
2
x→П7
Cx
Теперь по этому "темному" адресу вставим команду вызова подпрограммы
по R7:
KБП7
FПРГ
KПП7
FАВТ
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АВТ
получим то, что хотели.
Не забудьте в конце почистить стек возврата (как указано в примечание для
команды В/О)!