My given task is to find the mean of the sum of 3 inputs rounded down using LMC. To which I had the following code:
LDA zro
STO r
STO cnt
LDA t
STO abc
lp IN
STO a
div LDA a
SUB t
ADD r
BRP x
BRZ x
BR y
x STO a
LDA cnt
ADD one
STO cnt
LDA zro
STO r
BR div
y ADD t
STO r
LDA abc
SUB one
STO abc
BRZ out
BR lp
out LDA cnt
OUT
HLT
a DAT 000
t DAT 003
cnt DAT 000
one DAT 001
abc DAT 003
r DAT 000
zro DAT 000
which works for 99% of inputs 0-999, save for a few like:
4 4 2
Produces 2 instead of 3
596 165 2
Produces 253 instead of 254
148 574 2
Produces 240 instead of 241
For these inputs, the program produces a value one less than expected. This is because of a bug where the NEG flag doesn't reset until a new value is loaded.
For example with 4,4,2 during the last input loop where 2 is input (abc=1
), we have a=2
; 2 - 3 = (999 NEG) + 2 = (1 NEG)
which should've been just 1. But the NEG flag hasn't been reset leading the branching into the wrong line (y
instead of x
). Therefore cnt
is one less than expected.
My question then is how do you fix that (with minimal added instructions)?
Indeed, with LMC it is not good to perform an ADD
immediately after a SUB
because of the effect on the negative flag and on the accumulator. Not only could ADD
clear the flag or not (depending on the simulator), the accumulator value after a SUB
is not defined either, although most LMC simulators will use modular arithmetic (or will allow the accumulator to really have a negative value), this behaviour cannot be relied on.
The solution is to first check with BRP
whether the SUB
didn't overflow below zero, and then -- if it did -- load the accumulator again with the original value before the subtraction was done. It is the most reliable way to proceed (in terms of portability of the code).
Some other comments:
BRZ
has little sense when it follows immediately after a BRP
as the latter will branch also when the accumulator's value is zero.
The name out
for a label is ambiguous: some LMC simulators might interpret it as the instruction OUT
and will not be able to parse the program.
The names r
, t
, x
, y
, cnt
, ...etc are not very descriptive. I wouldn't abbreviate anything and just use longer labels: it will improve the readability of the code.
I would not add the remainder repeatedly to the current value during the division process (in x
), as in most cases the remainder will be 0. You could do this in the case where the last subtraction was performed (the y
case), and only then reset it to zero, and perform one more iteration of the division loop (as now the current value might be between 3 and 5).
If you swap the blocks for x
and y
you can save one BR
instruction.
So here is the modified code -- you can run it here:
#input: 4 4 2
LDA zero
STO remainder
STO average
LDA three
STO inputcount
getnext IN
STO value
divide LDA value
SUB three
BRP positive
LDA remainder # ignore the subtraction of 3
BRZ continue # branch if no remainder
ADD value # now we have no problem with NEG flag
STO value # remainder is added
LDA zero
STO remainder
BR divide # repeat one more time
positive STO value
LDA average
ADD one
STO average
BR divide
continue LDA value
STO remainder
LDA inputcount
SUB one
STO inputcount
BRZ output
BR getnext
output LDA average
OUT
HLT
value DAT 0
average DAT 0
inputcount DAT 3
remainder DAT 0
zero DAT 0
one DAT 1
three DAT 3
<script src="https://cdn.jsdelivr.net/gh/trincot/lmc@v0.816/lmc.js"></script>