News: 11 March 2016 - Forum Rules
Current Moderators - DarkSol, KingMike, MathOnNapkins, Azkadellia, Danke

### Author Topic: 16-bit Math Problems (on an 8-bit system)  (Read 1575 times)

#### Jiggers

• Jr. Member
• Posts: 78
##### 16-bit Math Problems (on an 8-bit system)
« on: May 12, 2018, 04:10:48 pm »
I have been having trouble with high and low bytes of 16-bit numbers. Once in a while I almost understand things, but then it flies out the window the next.

I think, possibly, the part that I don't understand is the FLOW of what's needed. When do I start with the low byte and add the carry into the high byte? When do I start with the high byte?

Here's my current problem:

I want to make a healing spell that transfers HP from one character to another - damaging the healer, healing the patient.

The original code, which I understand perfectly, is:
Code: [Select]
`LDA hp_recovery     ; healing potency    CLC    ADC ch_curhp, X             ; add healing potency to low byte of HP    STA ch_curhp, X    LDA ch_curhp+1, X           ; add 0 to high byte of HP (to catch carry from low byte)    ADC #0    STA ch_curhp+1, X`
Originally I was just going to have "hp_recovery" subtract from the healer's HP after. The issue is, what if the patient is almost healthy? The healer's potency would damage them far more than they healed the patient. I need to cap the damage done to the healer if they overheal the patient.

So first I thought, maybe...
LDA ch_maxhp+1, X
SEC
SBC ch_curhp+1, X
STA MMC5_tmp+1
LDA ch_maxhp, X
SEC
SBC ch_curhp, X
STA MMC5_tmp

And that gets the amount of healing needed, stored in MMC5_tmp and +1. Then I could compare it to "hp_recovery" and do something to cut off the overheal amount before healing even begins...

But where to begin? High byte or low byte? "hp_recovery" is only one byte, so would I need to use #0 in the second comparison again, so it only compares the carry?

________

Edit: Slugged away at it a little more. Maybe made progress?

Code: [Select]
`MenuRecoverHP_Magic:        LDA ch_maxhp, X    SEC    SBC ch_curhp, X    STY MMC5_tmp    LDA ch_maxhp+1, X        SEC    SBC ch_curhp+1, X         STA MMC5_tmp+1    BEQ :+              ; if high bytes are 0, then Z is set. Z is clear if there is high byte HP to heal       CPY hp_recovery  ; compare the amount of lost low byte HP to the amount to be healed                        ; C is clear if there is overheal. C and Z is set if its exactly equal.       BCS @NoOverheal            STY hp_recovery  ; cap hp_recovery at what will fill up the low byte.             JMP @NoOverheal  ; and now there's no more overheal, so jump to healing!        : ; jump here if there is healing to be done in the high bytes...    ;; now I'm not sure what to do    ;; Heal the low bytes and then do this all over again with the carry, somehow?             @NoOverheal:    LDA hp_recovery             ; healing potency    CLC    ADC ch_curhp, X             ; add healing potency to low byte of HP    STA ch_curhp, X    LDA ch_curhp+1, X           ; add 0 to high byte of HP (to catch carry from low byte)    ADC #0    STA ch_curhp+1, X    CMP ch_maxhp+1, X           ; then compare against max HP to make sure we didn't go over    BEQ _MenuRecoverHP_CheckLow ; if high byte of cur = high byte of max, we need to check the low byte    BCS _MenuRecoverHP_OverMax  ; if high byte of cur > high byte of max... we went over`
« Last Edit: May 12, 2018, 07:49:11 pm by Jiggers »

#### Squall_FF8

• Full Member
• Posts: 198
##### Re: 16-bit Math Problems (on an 8-bit system)
« Reply #1 on: May 12, 2018, 07:58:31 pm »
Quote
But where to begin? High byte or low byte? "hp_recovery" is only one byte, so would I need to use #0 in the second comparison again, so it only compares the carry?
When you do 16 bits math with 8 bit arithmetic, the order is:
- Prepare the Carry Flag
- low byte operation
- high byte operation
* when one of the 16 bit operand is < 256, assume 0 as high byte. Example \$20(8bit) = \$0020(16bit)
* you set the Carry Flag only once - at the beginning. It is call "Carry" for a reason - each operation carry the extra bit of each operation.
* It is similar if you need more bits - 24, 32,...

BTW you have a mistake in your second code: - remove the 2nd SEC

#### Jiggers

• Jr. Member
• Posts: 78
##### Re: 16-bit Math Problems (on an 8-bit system)
« Reply #2 on: May 13, 2018, 02:48:25 pm »
Thanks for catching that! Also...

Quote
when one of the 16 bit operand is < 256, assume 0 as high byte. Example \$20(8bit) = \$0020(16bit)
< is "less than", right? I have a huge problem with those signs. I have a little chart to look at to know which is which and the moment I look away I lose track of what it means. Part of why doing comparison math stuff is so difficult for me... Starting out last year, every BNE, BEQ, BCC, BCS, was a huge nightmare taking half an hour until I somehow got it clicking (sometimes it still doesn't.) Taking numbers out and using words ("branch if not equal") helped a lot in really understanding what I was doing with them. And yet, less than/more than signs are still my nemesis.

I think I have it right so far, based on the order you gave me... But I don't know what kind of operation to perform now.

I need to... fill the low byte of HP with the heal potency...? Orrr, subtract the low byte FROM the heal potency, then compare the leftover heal potency with the high byte, and cap heal potency if THAT goes over...? Edit: I really don't know. I guess this is a logic flow problem. The math will be EASY compared to figuring out what I'm supposed to do.

Oh, here's another thing I was wondering about, but its a 24-bit number:

Code: [Select]
`SumBattleRewardEXP:;; Y = EXP    LDA #\$00    STA battlereward            ; reset reward to zero    STA battlereward+1    STA battlereward+2        LDX #\$09                    ; loop 9 times, once for each enemy slot      @Loop:      CLC      LDA btl_enemystats, Y     ; sum low byte      ADC battlereward      STA battlereward            LDA btl_enemystats+1, Y   ; high byte      ADC battlereward+1      STA battlereward+1            LDA battlereward+2        ; carry into 3rd byte - This is used for GP but not for Exp      ADC #\$00      STA battlereward+2            TYA                       ; it assumes C is clear here, since sum cannot be over \$FFFFFF      ADC #\$14                  ; add \$14 to source index to move to next enemy (\$14 bytes per enemy)      TAY            DEX      BNE @Loop            LDA ExpGainOption      BEQ HalveReward          ; If ExpGain is 0 (Low), halve the reward          CMP #2          BEQ DoubleReward     ; If ExpGain is 2 (High), 1.5x the reward                               ;; Otherwise, ExpGain is 1, normal, so do nothing.    RTS    HalveReward:    LSR battlereward+1    ROR battlereward    LDA #0    STA battlereward+2    RTSDoubleReward:        ASL battlereward    ROL battlereward+1    LDA #0    STA battlereward+2          ; not sure what to do with a third byte for thisRTS`
Should it be
LSR battlereward+2
ROR battlereward+1
ROR battlereward   ?
« Last Edit: May 14, 2018, 03:00:20 pm by Jiggers »

#### Squall_FF8

• Full Member
• Posts: 198
##### Re: 16-bit Math Problems (on an 8-bit system)
« Reply #3 on: May 13, 2018, 08:37:52 pm »
Quote
Should it be ...
Here you have another multi-bite operation - shift (left or right)

1. Shift Right (divide by 2):
LSR Variable +0  ; you start with the leftmost (highest order) byte
ROR Variable +1
ROR Variable +2

2. Shift Left ( multiply by 2)
ASL Variable +2  ; you start with rightmost byte (lowest order)
ROL Variable +1
ROL Variable +0

* you don't need to prepare C. ASL & LSR use 0 on the place of empty bit
* you don't use 0 as padding. That was done when the Variable was given a value

BTW if you have done this alone, excellent work in setting a multi-byte value and multi-byte addition (3bytes or 24bits)
« Last Edit: May 13, 2018, 08:45:08 pm by Squall_FF8 »

#### Jiggers

• Jr. Member
• Posts: 78
##### Re: 16-bit Math Problems (on an 8-bit system)
« Reply #4 on: May 14, 2018, 04:54:30 pm »
Yay, I got that! I only did the LSR/ROR thing and the ExpGainOption bit alone, though. The rest is Disch's disassembly for FF1. Originally I was going to go for a .75% / 1.50% thing, but... that's a headache for another day.

I've re-worked my healing code. I think I need someone to check it out before I test it. Either I'm over-thinking the whole thing, or its fine and I'm not convinced it'll work for some reason.  I feel like I'm missing something either super obvious or super difficult that's over my head.

Okay, I've figured out the logic flow.

If HP is one byte:
Load max HP, subtract current HP = damage
Compare healing to damage...

If Healing is MORE THAN Damage:
Healing - Damage = Overheal Amount
Damage - Overheal Amount = healer damage

If Healing is LESS THAN Damage:
Healing = Healer damage

~ ~ ~

If HP is two bytes:
Load max HP, subtract current HP = damage (2 bytes)

* How to compare 1 byte of healing to 2 bytes of HP?

If Healing is MORE THAN Damage:
Healing - Damage = Overheal amount (2 bytes)
Damage - Overheal amount = healer damage (2 bytes)

If Healing is LESS THAN damage:
Healing = Healer damage (2 bytes)

So what's the best way to compare a 1 byte number against a 2 byte number?

I don't think I can simply convert the healing to a 2 byte number. The high byte would always be 0, since the max healing potency is 173 (\$AD). I'd always be comparing 0 to 0, 1, 2, or 3. I dunno, would that work?
« Last Edit: May 15, 2018, 07:29:59 pm by Jiggers »

#### Disch

• Hero Member
• Posts: 2625
• NES Junkie
##### Re: 16-bit Math Problems (on an 8-bit system)
« Reply #5 on: May 16, 2018, 12:03:37 pm »
Sorry... I'm jumping into this late and only read the most recent post:

It sounds like you are over-complicating this.

Quote
If HP is one byte:

But HP isn't one byte, so this is irrelevant.  Making a special case and treating HP under 256 differently from other cases just adds more code and makes things much more complicated.  Try to treat everything as uniformly as possible.

If you have a damage/healing calculation routine that works with 2-byte values, it will ALSO work with "1-byte" values that are really 2-bytes with the high byte set to zero.  There is absolutely no reason to code two separate routines.

Quote
I don't think I can simply convert the healing to a 2 byte number. The high byte would always be 0

You can and you should.  And highbyte=0 is exactly how you should do it.

EDIT:

To elaborate... this is how you'd typically compare 2 two-byte values ("P" and "Q")  (high byte is +1):
Code: [Select]
`LDA P+1         ; compare high bytes firstCMP Q+1BCC Q_is_larger ; (no need to examine low bytes if these branches take, we already know which is larger)BNE P_is_largerLDA P           ; compare low bytes nextCMP Q ; at this point, C,Z tell you the result: ; Z=set if P=Q ; C=clear if P<Q ; C=set if P>=Q`

If 'Q' is only one byte wide... you can start by doing the same thing, but use an immediate zero for the high byte:

Code: [Select]
`LDA P+1         ; compare high bytes firstCMP #0          ;  high byte of Q is zero  ; ... `
And if you're clever, you should be able to see how to optimize that, since CMP #0 will never clear C, and won't change Z as set by LDA:

Code: [Select]
`LDA P+1         ; high byteBNE P_is_largerLDA P           ; low byte (as above)CMP Q; ...`
Also simpler logic for this would be:

Code: [Select]
`amount_to_heal = strength_of_spell();if(amount_to_heal >= caster_hp)    amount_to_heal = caster_hp-1;      // don't kill yourselftemp = target_hp;                      // their pre-heal HP amounttarget_hp += amount_to_heal;if(target_hp > target_max_hp){    target_hp = target_max_hp;    amount_to_heal = target_hp - temp; // how much we actually healed}caster_hp -= amount_to_heal;`
« Last Edit: May 17, 2018, 03:02:44 pm by Disch »

#### Jiggers

• Jr. Member
• Posts: 78
##### Re: 16-bit Math Problems (on an 8-bit system)
« Reply #6 on: May 18, 2018, 05:42:32 pm »
No worries! Thanks for taking the time to help again.

Part of why I was over-thinking it... I originally wanted the empath class to be able to keep healing when their HP is 1; they'd simply stop taking damage at that point. I thought it would hinder the fun of the game if you had to stop and heal them before continuing to use magic (in the menu. In battle, that's the whole point!) Polled some friends, and they suggested to keep it "hard mode" for menu healing... So that simplified things a lot. Maybe.

So a few hours of fiddling with things later... This compiles without error!

Code: [Select]
`MenuRecoverHP:    LDY ch_ailments, X          ; get out of battle ailments for this character    CPY #\$01    BEQ _MenuRecoverHP_Exit     ; if dead... skip to exit (can't recover HP when dead)        CPY #\$02    BEQ _MenuRecoverHP_Exit     ; if stone... skip to exitMenuRecoverHP_Abs:    LDA hp_recovery    STA tmp                     ; back up HP to recover by stuffing it in tmp    BEQ _MenuRecoverHP_OverMax  ; JIGS - if hp_recovery is 0 (set by using a max potion), max HP            JSR LongCall    .WORD UseItemCoffee    .BYTE \$0F    JMP MenuRecoverHPDoHeal    ;; Coffee heals %25 of max HPMenuRecoverHP_Magic:        LDA #1    STA EmpathHealing    LDY menu_caster    LDA ch_curhp+1, Y    BNE @CasterHPIsMore     ; High byte of caster health is 1, 2, or 3; therefore, the low byte                            ; will be able to handle up to 255 of healing        LDA ch_curhp, Y    CMP hp_recovery    ;BCC @HP_RecoveryIsMore ; no need to branch... gonna do it anyway    BNE @CasterHPIsMore        @HP_RecoveryIsMore:    ;LDA ch_curhp+, Y        ; this would be 0, though... so pointless to do, right?    ;STA hp_recovery+1       ; since this is currently always 0    LDA ch_curhp, Y    SEC    SBC #1        STA hp_recovery         ; subtract 1, so healer doesn't die    BNE MenuRecoverHPDoHeal     JMP CureFamily_CantUse  ; cancel healing if its 0, don't waste MP!        @CasterHPIsMore:    LDA ch_curhp, X    STA MMC5_tmp    LDA ch_curhp+1, X    STA MMC5_tmp+1    MenuRecoverHPDoHeal:    LDA ch_curhp, X    CLC    ADC hp_recovery             ; add healing potency to low byte of HP    STA ch_curhp, X    LDA ch_curhp+1, X           ; add 0 to high byte of HP (to catch carry from low byte)    ADC #0    STA ch_curhp+1, X    CMP ch_maxhp+1, X           ; then compare against max HP to make sure we didn't go over    BEQ _MenuRecoverHP_CheckLow ; if high byte of cur = high byte of max, we need to check the low byte    BCS _MenuRecoverHP_OverMax  ; if high byte of cur > high byte of max... we went over                                ; otherwise.. we're done...  _MenuRecoverHP_Done:    LDA EmpathHealing    BEQ @Done       LDA ch_curhp, Y    SEC    SBC hp_recovery    STA ch_curhp, Y    LDA ch_curhp+1, Y    SBC hp_recovery+1    STA ch_curhp+1, Y     @Done:    LDA #0    STA EmpathHealing    JSR PlayHealSFX        LDA tmp                     ; restore the HP recovery ammount in A, before exiting                                ; (for tent party healing)  _MenuRecoverHP_Exit:    RTS  _MenuRecoverHP_CheckLow:    LDA ch_curhp, X             ; check low byte of HP against low byte of max    CMP ch_maxhp, X    BCS _MenuRecoverHP_OverMax  ; if cur >= max, we're over the max    BCC _MenuRecoverHP_Done     ;  otherwise we're not, so we're done (always branches)  _MenuRecoverHP_OverMax:    LDA ch_maxhp, X             ; if over max, just replace cur HP with maximum HP.    STA ch_curhp, X    LDA ch_maxhp+1, X    STA ch_curhp+1, X        SEC                         ; nonsense if using a potion, but never used in that case    SBC MMC5_tmp+1    STA hp_recovery+1    LDA ch_curhp, X    SBC MMC5_tmp    STA hp_recovery    JMP _MenuRecoverHP_Done     ; and then jump to done`
I've yet to bugtest it though.

After figuring out how to edit .sav files, re-designing another menu screen, and re-organizing spell data for menu casting... The caster's HP loops back to max instead of capping at 1. Gonna see if I can figure this out.

Edit agaiiiin:

While its hard to say which of the silly things that was going on was causing trouble, I am 95% certain this is it. It gave me a scare by having the healer's HP go to double digits from 999 at random... but after half an hour of debugging and trying to heal the healer, then someone else, then the healer, then someone else... in different patterns... it basically stopped doing that and started working???

Lookit my proud little messy baby!

Code: [Select]
`MenuRecoverHP_Magic:        LDA #1    STA EmpathHealing    TXA                     ; transfer patient character index to A    CMP menu_caster         ; compare with character index of caster    BEQ MenuRecoverHPDoHeal ; character is healing self; skip the nonsense        LDY menu_caster    LDA ch_curhp+1, Y    BNE @CasterHPIsMore     ; High byte of caster health is 1, 2, or 3; therefore, the low byte                            ; will be able to handle up to 255 of healing        LDA ch_curhp, Y    CMP hp_recovery    BCC @HP_RecoveryIsMore ; NEED TO BRANCH    BNE @CasterHPIsMore        @HP_RecoveryIsMore:    LDA ch_curhp+1, Y        ; this would be 0, though... so pointless to do, right?    STA hp_recovery+1       ; since this is currently always 0    LDA ch_curhp, Y    SEC    SBC #1        STA hp_recovery         ; subtract 1, so healer doesn't die    BEQ EmpathCantHeal        @CasterHPIsMore:    LDA ch_curhp, X    STA MMC5_tmp    LDA ch_curhp+1, X    STA MMC5_tmp+1    MenuRecoverHPDoHeal:    LDA ch_curhp, X    CLC    ADC hp_recovery             ; add healing potency to low byte of HP    STA ch_curhp, X    LDA ch_curhp+1, X           ; add 0 to high byte of HP (to catch carry from low byte)    ADC #0    STA ch_curhp+1, X    CMP ch_maxhp+1, X           ; then compare against max HP to make sure we didn't go over    BEQ _MenuRecoverHP_CheckLow ; if high byte of cur = high byte of max, we need to check the low byte    BCS _MenuRecoverHP_OverMax  ; if high byte of cur > high byte of max... we went over                                ; otherwise.. we're done...  _MenuRecoverHP_Done:    LDA EmpathHealing       ; if empath is not healing (healing with potion instead) skip this    BEQ @Done    TXA                     ; transfer patient character index to A    CMP menu_caster         ; compare with character index of caster    BEQ @DECMANA            ; if casting on self, skip this, but still decrease mana        LDA ch_curhp, Y    SEC    SBC hp_recovery    STA ch_curhp, Y    LDA ch_curhp+1, Y    SBC hp_recovery+1    STA ch_curhp+1, Y      @DECMANA:    LDX mp_required         ; put mp required index in X    DEC ch_magicdata, X     ; and subtract 1 MP from the proper level     @Done:    LDA #0    STA EmpathHealing    JSR PlayHealSFX        LDA tmp                     ; restore the HP recovery ammount in A, before exiting                                ; (for tent party healing)  _MenuRecoverHP_Exit:    RTS  _MenuRecoverHP_CheckLow:    LDA ch_curhp, X             ; check low byte of HP against low byte of max    CMP ch_maxhp, X    BCS _MenuRecoverHP_OverMax  ; if cur >= max, we're over the max    BCC _MenuRecoverHP_Done     ;  otherwise we're not, so we're done (always branches)  _MenuRecoverHP_OverMax:    LDA ch_maxhp, X             ; if over max, just replace cur HP with maximum HP.    STA ch_curhp, X    LDA ch_maxhp+1, X    STA ch_curhp+1, X        SEC                         ; nonsense if using a potion, but never used in that case    SBC MMC5_tmp+1    STA hp_recovery+1    LDA ch_curhp, X    SBC MMC5_tmp    STA hp_recovery    JMP _MenuRecoverHP_Done     ; and then jump to done     EmpathCantHeal:    LDA #33                 ; update this later    JSR DrawItemDescBox        ; different description text ID    JSR PlaySFX_Error    JSR MenuWaitForBtn_SFX  ; Then just wait for the player to press a button.  Then exit by re-entering magic menu    RTS`
« Last Edit: May 18, 2018, 11:27:18 pm by Jiggers »

#### Disch

• Hero Member
• Posts: 2625
• NES Junkie
##### Re: 16-bit Math Problems (on an 8-bit system)
« Reply #7 on: May 19, 2018, 02:20:11 am »
Looks good to me!  Nice work.