-3

I have to write a program which is reading chars from a file, moving bits in every char and writing those changed chars to a new file in TASM.

I've written a program which is reading chars from a file and writing them to a new file, but I don't know how to move bits in a char.

For example, here would be our file with chars:

        ▼    ▼
!   // 00100001 
"   // 00100010
#   // 00100011
$   // 00100100

And this would be our new file with changed chars (2nd bit goes to 7th position):

        ▼    ▼
!   // 00100001 
b   // 01100010
c   // 01100011
$   // 00100100

How to move that bit in a char in TASM?

Edit:

This is not a duplicate question to How to change bits in a char in TASM? because I need to detect if the desired bit is 0 or 1, necessary to know what to use, OR or AND, then set the proper bit in 1 and the rest in 0, or the proper bit in 0 and the rest in 1, then execute the OR or AND. That's a bit different question.

Community
  • 1
  • 1
  • Use bitwise shifting and masking. – Jester Dec 07 '16 at 22:37
  • Possible duplicate of [How to change bits in a char in TASM?](http://stackoverflow.com/questions/41026387/how-to-change-bits-in-a-char-in-tasm) – zx485 Dec 07 '16 at 22:38
  • 1
    Since you already know how to use AND and OR, the only new part is [SHL](http://www.felixcloutier.com/x86/SAL:SAR:SHL:SHR.html). – Peter Cordes Dec 07 '16 at 23:00

2 Answers2

2

To do this, test the "source" bit by masking everything but the bit you want to test, and then branch conditionally based on whether the resulting value is zero or not. Then you can either set or clear the "destination" bit.

So, for example, assuming your char is in the AL register,

   TEST AL,2        ; TEST sets zero flag if the result of ANDing is zero,
   JZ BIT_IS_ZERO   ; so there is no need to do a separate CMP
   ; not zero, so fall through to BIT_IS_ONE, no need for a JMP

BIT_IS_ONE:
   OR AL,64         ; set bit 7; define constant make easier to understand
   JMP DONE

BIT_IS_ZERO:
   AND AL,191       ; mask off bit 7; ditto on defining constant for this
   ; fall through to DONE, no need for a JMP here

DONE:

You may need to use AND rather than TEST depending on the chip you are targeting. AND modifies the register to be tested, so you'll have to keep another copy and load it before setting/clearing the bits, but otherwise it is the same.

You could also use SHL, by using AND to clear all but the bit you care about, then use SHL to shift the remaining bit (doesn't matter whether it is 0 or 1) from bit 2 to bit 7, then clear that bit in the "destination" char (a separate copy) and finally OR the two together. The approach I provided above is probably simpler to understand and implement, but it does introduce branch instructions that are not necessary when using SHL, so it is less efficient than SHL.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Matt Jordan
  • 2,133
  • 9
  • 10
  • 1
    Why don't you write your bitmask constants in hex or binary like a normal person? Decimal is the least-easy way to read them and think about them. – Peter Cordes Dec 08 '16 at 04:40
1

The most straightforward literal rewrite of English sentence "copy 2nd bit into 7th bit" into 8086 Assembly I can think of:

    mov al,char  ; input value
    mov ah,al    ; copy of value for 2nd bit extraction
    and ah,0b00000010 ; keep only 2nd bit in copy
    ; ^ and ah,2 if your assembler doesn't support "0b"
    shl ah,5     ; move 2nd bit to position of 7th bit
    and al,0b10111111 ; clear 7th bit in char
    ; ^ and al,0BFh if your assembler doesn't support "0b"
    or  al,ah    ; merge "second bit" into cleared one at 7th

About bitmask constant calculation:

When you have some binary value like 1010 1001 = mask of 8 bits, when used by AND, it will keep original values everywhere where "1" are set in mask, and it will clear the bits where "0" is.

That should be easy to imagine, if you know which bit you want to manipulate by AND/OR/XOR/TEST instruction, like "obvious".

But you don't want to type such long binary number into assembler (plus if you use some obsolete one, it will not even compile it for you)... So write it instead in hexa 0A9h. It's just as "obvious" as binary one, just every 4 bits form single hexadecimal digit, so 1010 is 8+2 = 10 = Ah (write "0A" into editor, leading 0 to tell assembler that the "A" belongs to number, not to label) and 1001 is 8+1 = 9, write "9" into editor, then add "h" to mark it hexa value, done.

How Matt managed to get decimal 191 (= 0xBF = 0b10111111) - I don't know, probably he used calculator or he did 255-64 in head (full mask minus single bit). While I can write the BF hexadecimal form on fly just by imagining the bit pattern in head.


Also read this, the wiki article is IMO quite good: https://en.wikipedia.org/wiki/Bitwise_operation

Ped7g
  • 16,236
  • 3
  • 26
  • 63
  • 2
    I don't think the second version works. `and al,ah` will always clear the other bits of AL. Also, using both halves of the same register gives you a false dependency on AMD and Silvermont CPUs (i.e. anything other than Intel P6 and Sandybridge-family, since those are the only ones that rename partial registers separately). – Peter Cordes Dec 08 '16 at 04:38
  • @PeterCordes ... brain on holidays... deleted... about ah/al: I know, but if you would be after speed on the task (whole file), you would not process just 8 bits either, so I just didn't care, to keep it simple. As you noted, I still managed to shoot myself into foot, big time. :D Yeah, for once I did think I don't need to verify such simple code... – Ped7g Dec 08 '16 at 08:00