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

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

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


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

Для начала опишем список команд, которые результат своего выполнения записывают напрямую в X2, а только потом копируют его в X. Назовём такие команды X2-влияющие. Безусловно это делают следующие команды:

Команда С/П выполняют копирование только при остановке. Но не при запуске программы.

Выполняют копирование (в данном случае X → X2) только при НЕ переходе на адрес следующие команды:

Для условных операторов это означает, что копирование X→X2 производится, когда условие выполняется. Для циклов – когда цикл завершается.

Остальные операторы НЕ копируют результат в X2. А это, например, означает, что регистр X во время таких операций может содержать сверхчисло. При этом, конечно, нужно учитывать, чтобы команда сам по себе не делала такую проверку на переполнение. Например, F10x делает проверку параметра перед выполнением, а Fx2 – нет.

Для пояснения работы условных операторов рассмотрим такую программу:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 5 0 F10x Fx2 Fx=0 07 Cx С/П

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

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

Работая в режиме вычислений кажется, что ненормализованные числа, у которых есть ведущие нули, образующиеся, например, при косвенной адресации, автоматически нормализуется при выполнении любой следующей команды. Так вот, в программном режиме это не так. Фактически, когда команда является X2-влияющей, то после выполнения X2 копируется в X, и в этот момент X нормализуются, заодно проверяется на переполнение. А не X2-влияющие команды так не делают: не выполняют нормализацию, не проверяют переполнения, не копируют X в X2. Именно поэтому они работают немного быстрее. Приблизительное время выполнения команд см. в приложении.

В режиме вычислений можно считать все команды X2-влияющие. Это значит, что если вы извлекли ненормализованное число, то в регистре X2, т. е. на экране, оно с ведущими нулями, а вот в регистре X уже без них! Для доказательства возьмём простенькую программу:

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

Если ввести 001, то в X2 будет 001, а в X уже 1. И после нажатия С/П именно эта нормализованная единица подставится в итог: 101. . Почему вообще так, рассмотрим в других разделах подробнее.

Несмотря на это, регистр X2 при остановке не нормализуется. Вообще говоря, он специально вообще никогда не нормализуется. Вот пример, при условии, что переключатель Р-ГРД-Г не в положении Р.

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Cx Fsin-1 x→П1 С/П

Тут ненормализованное число 00 получается не X2-влияющей командой, чтобы в X оставаться ненормализованным. Сохраняется и остаётся как в R1, так и в X2 (на экране) в ненормализованном виде, потому что оператор С/П при остановке копирует его в X2. Но регистр X уже будет нормализован. Например, нейтральное FАВТ скопирует X в X2 и вы увидим простой ноль. Чуть удлиним программу:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Cx Fsin-1 x→П1 Fπ С/П

Теперь на экране мы увидим π. Но в Y и в X1 останется ненормализованное 00. Чтобы увидеть его, можно ещё чуть удлинить программу:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Cx Fsin-1 x→П1 Fπ FВx ВП С/П

Тут у ненормализованного 00 после извлечения из X1, с помощью ВП первая цифра заменится на 1 и получится 10. Получается, что нормализуется только регистр X.

На всякий случай ещё раз напомним, как действуют X2-влияющие команды. Они результат своего выполнения, возможно ненормализованный, копируют в X2. А затем X2 копируется в X, но уже с нормализацией. Для примера возьмём В↑. Она сначала сдвигает стек, копирую X в Y, возможно ненормализованное. Затем результат, который в данном случае X или Y, копирует в X2, тоже, возможно, ненормализованное, а затем из X2 в X, но уже с нормализацией. Пример, при условии что Р-ГРД-Г не в положении Р:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Cx Fsin-1 В↑ x→П1 <-> x→П2 С/П

Ненормализованное 00. копируется в Y, а позднее в R2, а вот полученный X в R1. Результат можно проверить по содержимому регистров по окончании: R1 = 0, R2 = 00.

Аналогично П→xR, при ненормализованном значение в R, скопирует его в таком виде только в X2 (на экран), но в X уже будет нормализованное. Проверяется аналогичным образом.


X2-восстанавливающие

Некоторые команды игнорируют регистр X и для вычислений используют напрямую X2, т. е. делают как бы обратное копирование X2→X. Именно их мы и рассмотрим.

А причиной нестандартного поведения этих команд является то, что подпрограммы ввода числа в ПМК, в том числе его порядка, работают напрямую с регистром X2, иногда нестандартно. А только потом, как X2-влияющие, копируют его обратно в X. Именно поэтому, они, фактически, игнорируют текущее значение в X.

Чтобы выделить эти команды, назовём их X2-восстанавливающими. Они модифицируют X2, возможно нестандартным способом, а потом копируют X2→X.

Ещё. Т. к. эти команды также X2-влияющие, то число в X всегда нормализованное, даже если X2 не нормализованное.

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

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

Тут в результате будет 100, потому что X2 не нормализуются и цифры поочерёдно добавляются в него, а финальный ВП исправит первую цифру на 1.


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

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

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  П→x9 П→x1 К К{x} Fx≠0 77
 70 |  . К x→П9

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

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

После восстановления ввод цифр не является продолжением ввода числа. Они воспринимаются как новый ввод.

Вот интересный пример. Пусть в программе есть последовательность 1 . 2 . 3. Тогда при прямом выполнении будет 1.23. Если идёт переход откуда-то на первую точку, то будет восстановление X2 и затем ввод 2.3. Если идёт переход на вторую точку, то будет восстановление X2 и ввод 3. Вот фрагмент, на котором это видно:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Fπ × Fx≥0 07 Fx≠0 09 1 . 2 .
 10 |  3 С/П

Передадим на вход 10, получим 1.23, а RY = 10 × π. Передадим на вход −10, получим 2.3, а RY = −10 (умножение пропало, как и число π). Передадим на вход 0, получим 3, а RY = 0 (умножение тоже пропало, как и число π).


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

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

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

Прежде чем привести общий алгоритм восстановления после ВП введём несколько понятий.

Источник первого разряда
Или коротко источник. Он обозначает, откуда ПМК будет брать первый разряд числа при восстановлении после ВП.
Флаг нулевой мантиссы
Или коротко ZF. Он показывает, что содержимое X2 нулевое (точнее только мантисса), и требуется модификация первого разряда.

Теперь можно описать общий алгоритм восстановления после ВП. Сначала анализируется флаг ZF. Если он установлен, то первый разряд X2, если он нулевой, заменяется на 1. Как не странно, есть редкий случай, когда он не нулевой, даже при установленном ZF. Затем к нему (первому разряду) прибавляется первый разряд источника (сложение шестнадцатеричное, по модулю 16). Если же ZF не установлен, то первый разряд источника просто копируется в первый разряд X2. Интересно, что в варианте ZF складывается, а без него – копируется. И только потом новое X2 копируется в X. Кстати, после выполнения флаг ZF пересчитывается.

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

А вот источники разные для разных последовательностей команд перед ВП. И это мы детально рассмотрим ниже.


Восстановление X2 с отбрасыванием первой цифры

Это выполняется после последовательности операторов x→ПRВП, где R – любой регистр памяти. При этом текущее содержимое регистра X теряется, без изменения стека. Вместо x→ПR может быть и команда Кx→ПR.

Пример. Пусть нам нужно обработать ввод выбора пользователя для перемещения в некоем трёхмерном лабиринте. Обычно используются клавиши 2,4,6,8,±5, что соответствует направлению движения. Рассмотрим такую последовательность, в предположении, что выбор пользователя хранится в регистре R9 и значение 0 имеет ещё какой-то дополнительный смысл:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  П→x9 2 ÷ Fx≠0 77 Fπ + x→П9 ВП Fx=0
 10 |  55 КП→x9

Что здесь происходит? На шаге два мы получим одно из чисел 1, 2, 3, 4, ±2.5 или 0. Условным оператором мы не только отсекаем вариант с нулём, но делаем X→X2. Далее к полученному числу сразу прибавляем π и сохраняем в R9 для дальнейшей косвенной адресации: пусть в R4…R7 хранятся коэффициенты умножения для выполнения движения. Обращаю внимание, что тут использованы не X2-влияющие команды. После команды ВП мы восстановим в X то значение, что было после деления на шаге 02, только без первой цифры, т. е. ноль для 1…4, или ±0.5 для ±5 как хвост 2.5. И использовать это для последующего ветвления программы: умножение на коэффициент деления, а для ±0.5 можно будет взять знак числа и т. д. Без использования ВП потребовалось бы использовать или дополнительный регистр, или дополнительные команды по манипуляции со стеком.

Дополнительные условия. Если содержимое X2 равно нулю, то будет восстановлена единица, точнее единица будет на месте первой цифры нулевого значения, т. е. если X2 было ненормализованным нулём, то результат будет уже не 1. Пример ниже выдаст в результате 10000000. :

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Cx x→П9 КП→x9 П→x9 x→П9 ВП С/П

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

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Fπ /-/ Fπ × x→П9 ВП С/П

на экране будет -9.1415926 , потому что команда по адресу 01 X2-влияющая, а в R9 будет −π2.

С учётом знаний по знакоцифрам приведём более точное правило: эта последовательность при восстановлении X2 первую цифру результата меняет на значение равное знакоцифре числа в регистре X минус 1. А если восстанавливаемое X2 равно нулю, то ещё и увеличивает первую цифру результата на 1. Тогда получается:

  1. X2 не ноль. Число X больше нуля, знакоцифра X равна единице, значит первая цифра X2 при восстановлении заменяется на ноль, т. е. удаляется. За исключением случая, когда X2 было ненормализованным, т. е. первая цифра и так была нулём.
  2. X2 не ноль. Число X меньше нуля, с минусом, знакоцифра X равна 10 (A), значит первая цифра X2 при восстановлении заменяется на 9.
  3. Число X2 равно нулю. В этом случае так же первая цифра заменяется на знакоцифру числа X минус один, но потом увеличивается на 1. Итого первой цифрой будет единица для X ⩾ 0, или цифра А = 10, для X < 0. Если X2 не было нормализовано, то остальные цифры восстановятся как было.

Для последнего правила приведём пример:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 1 x→П0 /-/ КП→x0 ←→ x→П9 ВП С/П

Здесь на шаге 04 происходит не только уменьшение R0 до нуля, но и извлечение этого ненормализованного нуля с помощью X2-влияющей команды. Команда по адресу 05 подгоняет в регистр X отрицательное значение −1 и при восстановлении получится -0000000. , где знакоцифра A, как минус, сначала уменьшается на один, а потом снова увеличивается и копируется в первый разряд. Это не минус ноль. Для наглядности можно ещё нажать /-/, получится --0000000. , которое численно равно −1.0|+08, что легко проверить, сложив с нулём.
--0000000. само по себе тоже интересно. Если провести его косвенное увеличение через R4…R6, то как уже указано в разделе по косвенной адресации, для этих регистров сначала пройдёт нормализация числа с переносом старшего разряда, здесь единицы, поскольку A = 10, в знакоцифру, которая была минусом, т. е. число 10. А ещё плюс один сделает уже 11, что снова вызовет перенос лишней единицы, но уже в никуда, оставив только 1 на месте знакоцифры, т. е. просто пусто, потому что неотрицательное число. А мантисса останется их одних нулей, значит она увеличится на один, что приведёт к содержимому R4…R6 в виде 00000001. . Для R0…R3 нормализации не будет, но при уменьшении −1.0|+08 превратиться в −99999999. Для R7…Re ничего не произойдёт.

Интересно, что можно сразу в X2 получить минус ноль и добиться сходного результата --. , но это уже из следующего раздела:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Cx /-/ Fπ Fx2 КНОП ВП С/П

Так же хочу напомнить, что команда ВП X2-восстанавливающая, а это значит, что если второй раз сделать x→ПRВП, то учитываться будет уже знак восстановленного значения X. В частности, для программы выше из 8 команд, если в конце ещё добавить x→П8ВП, то получим уже ноль, т. к. в неотрицательном -0000000. первая цифра заменится на ноль.

Вот интересный пример, использующий ненормализованность чисел в необычном контексте. Пусть вводом пользователя является некоторое двузначное число, как координаты, и вы последовательностью x→ПRВП не только сохраняете ввод, а также сразу отбрасываете десятки. Но часто в таких случаях однозначное число подразумевает нулевой десяток, и эта последовательность уберёт эту единственную цифру. Что же делать? Оказывается, ввод пользователя с ведущим нулём оставляет X2 ненормализованным, с тем самым нулём. Т. е. достаточно вместо числа 3, вводить 03 и всё снова заработает – будет отброшен незначащий ноль. Для примера приведём программу, которая входное положительное двузначное число разделит на десятки, которые будут в регистре Y, и единицы, которые будет в регистре X. Вводить нужно всегда две цифры, возможно с ведущим нулём.

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  x→П9 ВП П→x9 ←→ FВx С/П

Ещё рассмотрим для примера один фрагмент.

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Fπ Fπ Fx2 x→П1 ВП x→П2 x→П3 ВП
 10 |  С/П

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

Команда X Y Z X2 Пояснение
Fπ 3.1415926 1.23 1.23
Fπ 3.1415926 3.1415926 1.23 1.23
Fx2 9.869604 3.1415926 1.23 1.23
-6.7280114 1.23 1.23
x→П1 -6.7280114 1.23 1.23
ВП 9.23 1.23 9.23 Произошло восстановление X2 (1.23), но т. к. число в RX отрицательное, то первая цифра заменена на 9.
x→П2 9.23 1.23 9.23
-8 9.23
x→П3 -8 9.23
ВП 9.23 9.23 Восстановлено X2, с заменой 9 на 9.

Теперь передадим на вход 000.

Команда X Y Z X2 Пояснение
Fπ 3.1415926 000. 000.
Fπ 3.1415926 3.1415926 000. 000.
Fx2 9.869604 3.1415926 000. 000.
-6.7280114 000. 000.
x→П1 -6.7280114 000. 000.
ВП -00. 000. -00. Произошло восстановление X2 (000), причём ноль не просто заменился на 9, но и произошло его увеличение на 1. Получилась шестнадцатеричная цифра A.
x→П2 -00. 000. -00.
-1000. -00. Прошла нормализация шестнадцатеричной A до 10.
x→П3 -1000. -00.
ВП 900 900. Уже не -00. . ZF не установлен, поэтому 9 просто копируется в первый разряд.

В данном случае проявляется то, что во время выполнения ZF изменился, поэтому +1 уже не делается. Теперь приведём пример, где наоборот ZF устанавливается в процессе вычисления.

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  x→П1 ВП x→П2 ВП С/П

Для неё тоже приведём полный разбор. Сначала неинтересный случай – на вход число 24:

Команда X X2 Пояснение
x→П1 24. 24.
ВП 4 04 Прошла замена первого разряда на знакоцифру − 1 = 0
x→П2 4 04 В R2 сохранилось 4
ВП 4 04 Повтор того же. У 04 первая цифра меняется на ноль

Обратите внимание, что несмотря на то, что при восстановлении X сразу нормализуется, само X2 – нет. Теперь более интересный вариант. На вход число 20:

Команда X X2 Пояснение
x→П1 20. 20.
ВП 0 00 Вот тут флаг ZF пересчитался
x→П2 0 00 В R2 сохранился ноль
ВП 10 10 Из-за флага первая цифра ещё раз увеличилась

Как итог для данного раздела. Последовательность x→ПR ВП придерживается общего алгоритма восстановления после ВП, но в качестве источника первого разряда выступает цифра, равная (знакоцифра − 1).


Восстановление X2 с сохранением первой цифры числа в X

Как правило это наиболее интересная последовательность. Она позволяет нестандартно сочетать два числа.

Рассмотрим последовательность КНОПВП. При этом КНОП взята как более нейтральная, могут быть и другие не X2-влияющие команды. Назовём это первой командой. Тут важно учитывать:

  1. В качестве образца первой цифры берётся содержимое X до (!) начала данной последовательности.
  2. Если вместо КНОП стоит другая не X2-влияющая команда, то она выполнится, но результат её выполнения, т. е. содержимое X, будет потерян. При этом стек будет иметь состояние как после выполнения команды.
  3. Если первая команда – это переход на другой адрес, то она выполнится и последовательность с ВП будет нарушена. Но если ВП расположена по адресу, куда идёт переход, то сработает. Для косвенных переходов сложнее – см. ниже.
  4. Если первая команда генерит ошибку, то снова последовательность с ВП будет нарушена.
  5. Если первая команда это x→ПR, то будет выполнено по правилу, описанному разделом выше, с отбрасыванием первой цифры.

Пример:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Fπ КИНВ КЗН FВx ←→ КНОП ВП С/П

В результате получим 1.ELE-6Г9 . Тут дробная часть понятна – это инверсия числа π которое сохраняется X2-влияющей командой по адресу 03. А интересна тут цифра 1 на первом месте, которая появилась в стеке по команде КЗН. Именно она подставляется вместо восьмерки при восстановлении.

Интересно, что первой цифрой может быть и шестнадцатеричная, тем самым можно получить то, что в режиме вычислений трудно сделать. Например,

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 9 F1/x 5 × КИНВ /-/ К{x} КНОП ВП
 10 |  /-/ 9 9 С/П

Будет 10 (!) минусов. --.--------99. Тут X2-влияющая команда по адресу 06, добавляя минус, запоминает в X2 число −8.AAAAAAA. Потом оператор дробной части продвигает на первый разряд тоже цифру A. Последовательность с ВП успешно объединяет X2 с этой первой цифрой A, а порядок −99 в конце дописывается для красоты. Хочу снова повторить, что даже восстанавливая, команда ВП сохраняет контекст своего исполнения, т. е. после неё ожидается ввод порядка. Это отличается, например, от команды ., которая после восстановления не подразумевает ввод дробной части числа.

Ещё пример. Пусть нужна подпрограмма, которая из первой цифры, целой части дробного числа в регистре X делает букву. Например, 1→E, 2→D, 3→C, 4→A. В режиме вычислений для этого подошла бы последовательность:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 1 0 + КИНВ К{x} ВП 1 К[x] С/П

В реальной подпрограмме первый КНОП не нужен, а вместо С/П должно стоять В/О, но в таком виде это можно сразу ввести и попробовать. С учётом специфики выполнения команды ВП в программном режиме нужно ещё скопировать X→X2 после команды К{x}, т. е.

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 1 0 + КИНВ К{x} В↑ ВП 1 К[x]
 10 |  С/П

Это +1 команда, к тому же стек будет испорчен. Рассмотрим

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 1 F10x + КИНВ К{x} КНОП ВП С/П

Здесь, благодаря восстановлению единицы с шага 01 на шаге 07, останется только одна шестнадцатеричная цифра. Так недокументированная последовательность сделала подпрограмму короче на два шага.

Нужно понимать, что если число в X2 ненормализованное, то всё равно заменяется только первая цифра. Воспользуемся знаниями косвенной адресации:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 4 x→П7 КБП7 П→x7 КНОП ВП С/П

В данном случае 4 после косвенного перехода становится 00000004, а значит замена первой цифры приведёт к 40000004. , в чем легко убедиться, запустив программу.

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

Если X2 нулевое, то тут как бы проявляется упомянутый ранее анализ ZF. Фактически, в этом случае, первая цифра числа X увеличивается на 1. Вот фрагмент:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 0 ←→ КНОП ВП С/П

Если на вход этой программе дать 5, то выдаст 6. А если 9, то выдаст… A (!), потом аналогично B, C, D, E, F. Впрочем, последнее лучше тут же заменить на 0 и прочистить стек – пустышки коварны. Во всяком случае, если на вход передать F, то программа однозначно будет перекручена во что-то неузнаваемое. Ещё пример:

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

На вход -9. , на выходе первая цифра от обратной величины -1. .

Как итог для данного раздела. Последовательность КНОП ВП также придерживается общего алгоритма восстановления после ВП, но в качестве источника первого разряда выступает число в X до начала этой последовательности. Можно чуть расширить пример ранее:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 0 <-> КНОП ВП Fπ ВП С/П

Как и ранее, если на вход передать 9, то первый ВП восстановит 9 + 1 = A (9 из X вместо нуля). Второй ВП из регистра X2 уже иcпользует цифру A, но только её и восстановит вместо нуля, заменив π, но уже без +1.


Использование ВП как сложение

Редкий случай, скорее для информации только. Последовательность КНОПВПВП при нулевом значении X2, сначала, как и описывалось ранее, увеличит X на единицу, а второе ВП уже на величину цифры, которая была до увеличения. Добавление ещё команд ВП уже ничего не меняют. Пример:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 0 ←→ КНОП ВП ВП С/П

На вход 6, на выходе D (6 + 7 = 13). На вход D, на выходе B (13 + 14 = 27 = 16 + 11).

Здесь также проявляется результат общего алгоритма восстановления после ВП. Первый раз, как указано разделом выше, число восстанавливается как X + (0 + 1). Но второе ВП, так получается, всё ещё использует тот же источник, что и первое ВП. Например, если вместо КНОП вставить Fπ, то всё равно как источник будет использован первый разряд нашего ввода, а не π.

Но запаздывание срабатывает и для флага ZF. ПМК всё ещё считает его установленным. Но первая ВП меняет первый разряд в X2 на X + 1. Второе ВП, в соответствии с общим алгоритмом, складывает его с тем, что видит (X). Дополнительного увеличения не происходит, потому что первый разряд уже не ноль. Вот и получается X + X + 1.

Как подведение итогов сравним несколько вариантов с учётом того, что мы рассмотрели ранее. Возьмём ненормализованные числа, чтобы видеть изменения только первого разряда, которые и будем восстанавливать. 00000073. в R7 ( 73x→П7КП→x7) и 00000000. в R0 ( 1x→П0КП→x0) и рассмотрим следующие последовательности.

1. Восстановление 00000073, первая цифра берётся из регистра X:
 # |  00 01 02 03 04 05 06 07 08 09
 00 |  П→x7 ←→ КНОП ВП С/П

2. Восстановление 00000000, первая цифра берётся из регистра X:
 # |  00 01 02 03 04 05 06 07 08 09
 00 |  П→x0 ←→ КНОП ВП С/П

3. Восстановление 00000000, первая цифра берётся из регистра X и ещё сложение при X2 = 0:
 # |  00 01 02 03 04 05 06 07 08 09
 00 |  П→x0 ←→ КНОП ВП ВП С/П

4. Восстановление 00000073, первая цифра – знакоцифра − 1:
 # |  00 01 02 03 04 05 06 07 08 09
 00 |  П→x7 ←→ x→П9 ВП С/П

5. Восстановление 00000000, первая цифра – знакоцифра − 1:
 # |  00 01 02 03 04 05 06 07 08 09
 00 |  П→x0 ←→ x→П9 ВП С/П

6. Восстановление 00000000, первая цифра – знакоцифра − 1» и ещё «сложение при X2 = 0:
 # |  00 01 02 03 04 05 06 07 08 09
 00 |  П→x0 ←→ x→П9 ВП ВП С/П

Сводная таблица по указанным выше последовательностям для двух разных чисел в X:
При X = 52 При X = −52 Пояснение
1 50000073. 50000073. Первая цифра 5 берётся из регистра X. Знак не важен.
2 60000000. 60000000. Первая цифра 5 после восстановления увеличивается, т. к. восстанавливается ноль.
3 L0000000. L0000000. Т. к. ещё сложение, то к 5 прибавляется 6 и получается 11 – цифра B.
4 73. 90000073. Для неотрицательных знакоцифра − 1 равна нулю, первый ноль меняется на ноль, число не меняется. Для отрицательных знакоцифра − 1 равна 9.
5 10000000. -0000000. Т. к. восстанавливался ноль, то «знакоцифра − 1» ещё увеличивается.
6 10000000. 30000000. Тут тоже сложение, просто с другой последовательностью. Второе ВП использует тот же источник для первого разряда, т. е. знакоцифра − 1. Поэтому проводит сложение с этой цифрой и резервом + 1. Для неотрицательных 0 (источник) + 0 (резерв) + 1 = 1, а для отрицательных 9 (источник) + 10 = 19. А по модулю 16, т. к. только один разряд, равно 3.


ВП сразу после косвенного перехода

Т. е. в программе делается переход через команду безусловного КБПR, или условного перехода, например, Кx=0R. И сразу в месте, куда выполнился переход, идёт команда ВП. В этом случае так же идёт восстановление X2, но при этом первая цифра меняется на 7. Знак и порядок X2 сохраняются. Аналогично тому, что и ранее, если число в X2 не нормализовано, то всё равно идёт замена первой цифры, оставляя остальные как есть. В случае X2 = 0 при восстановлении первая цифра будет восьмёркой.

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

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 1 0 x→П8 Fx2 КБП8
 10 |  ВП С/П

После остановке на экране будет 70. , т. е. восстановлено X2 = 10, вместо X = 100, и первая цифра заменена на 7. Ещё пример.

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 4 x→П5 КП→x5 Fπ КБП5 ВП С/П

По уже указанными правилам будет 70000005. . Если заменить команду по адресу 05 на Кx=05, то ничего не изменится, т. к. условие не выполнится и будет переход. Но если заменить на Кx≠05, то уже условие выполнится, перехода не будет, и сработает старое правило, т. е. будет использована первая цифра числа в X, т. е. 30000005. .

Приведём пример для отличия нулевого X2. Пусть у нас R7 = 1, и есть короткая программа

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

Тогда если ввести 000. и нажать В/ОС/П, то получим на выходе 800. . А если вначале ввести 000.00123 , то получим 700.00123 .

Для не косвенных переходов работает правило восстановление X2 с сохранением первой цифры числа в X, при этом пустой оператор не нужен, т. к. сам переход будет им. Пример:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Fπ Fx2 Fx<0 04 ВП С/П

Если на вход программе дать 002. , то получим 902. (9 от π2), а если 000. , то получим -00. , где первый символ – это шестнадцатеричное A, т. к. 9 + 1 = 10.

Кстати, команды FLx тоже являются аналогом команд косвенного условного перехода, только по регистрам R0…R3. И для них это правило тоже работает. Например, введём программу

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Fπ В↑ ВП FL0 02 С/П

Если ввести 1 в R0 и выполнить программу, то всё будет как обычно и выдаст π, поскольку оно запоминается в X2. Но если в R0 ввести 2 и выполнить программу, то будет уже 7.1415926 , т. к. один раз FL0 успеет отбросить назад, как бы косвенный условный переход по регистру R0 на наше ВП, которое и заменит первую цифру на 7. Легко проверить, что если вставить по адресу 02 другую команду, например КНОП, сдвинув остальную часть программы, то фокус уже не получится, потому что переход уже будет не на ВП.

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


Команда . перед ВП сдвигает захват X глубже на начало

Обычно команда . восстанавливает в X значение X2, но ВП тоже пытается восстановить X2, но 1-ю цифру берёт ту, что было за 2 хода до нее в X. Рассмотрим

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 1 5 Fx2 Fx2 КНОП . ВП С/П

Результат 55, потому что 15.|+2 = 225, 225.|+2 = 50625 и выигрывает ВП, который восстановит X2 = 15, с первой цифрой 5. Если убрать второй КНОП по адресу 05, то результат будет 25, 2 от 225, т. е. второй Fx2 будет проигнорирован, как будто команда . отодвинула ВП вглубь. Если поменять КНОП и . местами, то будет 15, т. к. . восстановит 15 и уже с ним работает ВП, поставляя ту же единицу. Другой пример:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  КНОП 1 5 Fπ x→П9 КНОП . ВП С/П

Здесь . отодвигает до x→П9, т. е. восстановится 15 с цифрой 3, т. е. 35. Если убрать КНОП, то поведение будет как у x→П9ВП, т. е. 15 без первой цифры = 5.


ВП.

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

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

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

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

остановится по ошибке уже на команде ., т. е. следующим для исполнения будет адрес 06, и успеет возвести в квадрат только дважды, потому что первый квадрат, как не X2-влияющий будет отброшен по команде ВП. Например, на вход 2. Появляется ЕГГ0Г . И после КНОП на экране будет 16, как (2.|+2)|+2. А при нажатии FПРГ мы увидим 0- 22 22 06 – остановка на адресе 06.

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


ВП/-/

Само по себе сочетание не интересно, обычная смена знака при вводе порядка, но между ними могут стоять не X2-влияющие команды, тогда /-/ восстановит содержимое X2 после команды ВП. Пример:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  ВП 2 Fπ Fx2 Fx2 x→П9 /-/ С/П

Дадим на вход число −45, тогда в регистре Y будет -4500. , как результат возведения во вторую степень. В R9 97.409083 , как четвёртая степень π. А в регистре X восстановиться число, но степень успеет стать отрицательной. -4.5 -01, Обратите внимание на два момента:

  1. Ввод цифр после команды ВП являются X2-влияющими, и в данном контексте относятся только к вводу порядка, поэтому в X2 сохранится число с изменённым порядком, а не 2.
  2. Команда /-/ кроме восстановления выполнит ещё свою основную функцию – сменит знак порядка.

Если на вход этой программе дать 00000. , то команда ВП поставит на первом месте цифру 1, т. е. будет уже 10000 = 10.|+4, поэтому неудивительно, что после выполнения в X будет 100 = 10.|(4 − 2), а в регистре Y число 1000000 = 10.|(4 + 2).

Интересно, что если отставить только

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  ВП 2 x→П9 /-/ С/П

то промежуточное умножение на 100 сохраниться в R9, а в регистре X останется только результат деление на 100. Такой вот трюк.

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

Узнать содержимое X2 можно только с помощью ВП, потому что . по прежнему не годится, т. к. при таком сочетании остаётся контекст ввода порядка, где точка недопустима. Пример:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Cx ВП 2 Fπ КНОП Fπ КНОП ВП С/П

Здесь обычный случай. Полученная сначала сотня отодвинется не X2-влияющими командами в стеке до Z, а потом будет восстановлена, только уже с тройкой на первом месте: 300. . Изменим 4-ю команду:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Cx ВП 2 Fπ /-/ Fπ КНОП ВП С/П

Теперь, благодаря /-/ порядок не только инвертируется, но ещё и сохранится в X2. Поэтому результат будет 3. -02. Но если мы захотим восстановить X2 с помощью ., заменим её 7-ю команду:

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Cx ВП 2 Fπ /-/ Fπ КНОП . С/П

То ничего не выйдет. Программа выдаст ошибку, потому что . всё еще в контексте команды ВП, который /-/ не меняет.


Итог

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

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