3

I need to write in asm 8086 a program like b=a/6 but without the DIV instruction. I know how to do it with SAR but only 2,4,8,16...

mov ax,a
sar ax,1 ;//div a by 2
mov b,ax

my question is how can I do it to div by 6?

Peter Cordes
  • 245,674
  • 35
  • 423
  • 606
  • you can use subtraction and count how many times it take to get to zero, eg. 30/6=5 and 30-6-6-6-6-6=0 so for 30 you must 5 times subtract 6 to get to zero – Antonio Bakula May 30 '20 at 15:12
  • For a fixed (compile-time-constant) divisor, there's a fixed-point trick using multiply to do exact integer division: [Why does GCC use multiplication by a strange number in implementing integer division?](https://stackoverflow.com/q/41183935). Also, do you want signed or unsigned division? `div` is unsigned, `sar` is signed (and rounds differently than `idiv` for negative numbers) – Peter Cordes May 30 '20 at 20:18

4 Answers4

3

The approach given an another answer is simple brute force loop, and can take a while for large values of a. This is a version that uses larger chunks (working it like a long division problem) specifically coded to divide a signed number by 6:

; signed divide by 6
    mov ax,a
    mov cx,1000h  ; initial count of how many divisors into ax to check for
    mov bx,6000h  ; value of "divisor * cx"
    xor dx,dx     ; result
top:
    cmp ax,bx
    jl skip
    ; we can fit "cx" copies of the divisor into ax, so tally them
    add dx,cx
    sub ax,bx
    ; optionally can have a "jz done" here to break out of the loop
skip:
    shr bx,1
    shr cx,1
    jnz top

    ; copy result into ax
    mov ax,dx

If you need to divide something other than 6, the initial cx and bx values need to be adjusted. cx is the power-of-two multiple of the divisor that leaves bit 14 set (since bit 15 is the sign bit; for an unsigned divide you'd want to have bit 15 set instead). bx is that power of 2. If there are limits on the initial value for a you can adjust the initial cx and bx values, but have to be careful because you'll get an incorrect answer if you make them too small.

1201ProgramAlarm
  • 30,320
  • 7
  • 40
  • 49
1

you can use subtraction and count how many times it take to get to zero, eg. 30/6=5 and 30-6-6-6-6-6=0 so for 30 you must 5 times subtract 6 to get to zero

Something like that:

mov cx,0
mov ax, dividend

divloop:
  cmp ax, 0
  jle done   
  sub ax, divisor
  inc cx
  jmp divloop

done:
  ;result is in cx
Antonio Bakula
  • 19,349
  • 5
  • 73
  • 97
1

Since you've used the sar instruction in your example, I assume you need signed division by 6.

The following code uses multiplication by the reciprocal constant. It rounds the result down to −∞:

mov ax,0x2AAB   ; round(0x10000/6)
imul A          ; DX:AX contains the signed 32-bit result of the multiplication
mov B,dx        ; take only the upper 16 bits

If you need the result rounded towards 0, increase negative results by one:

      mov  ax,0x2AAB
      imul A
      test dx,dx  ; set FLAGS according to DX
      jns  skip
      inc  dx      ; increase DX if it was negative
skip: mov  B,dx

Finally, if you need unsigned division by 6, you need a more precise constant and a shift:

mov ax,0xAAAB  ; round(0x40000/6)
mul A          ; DX:AX contains the unsigned 32-bit result of the multiplication
shr dx,1
shr dx,1       ; shift DX right by 2 (8086 can't do "shr dx,2")
mov B,dx
Peter Cordes
  • 245,674
  • 35
  • 423
  • 606
Řrřola
  • 5,022
  • 1
  • 13
  • 10
  • 1
    Use `test dx,dx` to set FLAGS according to DX, [it's more efficient than `or`](https://stackoverflow.com/questions/33721204/test-whether-a-register-is-zero-with-cmp-reg-0-vs-or-reg-reg/33724806#33724806) on some CPUs, and not worse on 8086. On modern CPUs, compilers will often make branchless code that conditionally adds 1 with something like `mov ax, dx` / `sar ax,15` / `sub dx, ax` to either subtract 0 or -1, but that would be much slower on actual ancient CPUs. – Peter Cordes Oct 07 '20 at 09:52
  • These magic constants are what GCC uses for `short`: https://godbolt.org/z/4baG7K. Interesting that for signed division it works out to just taking the high half without further shifting; in other cases, shifts are necessary to handle large dividends: [Divide by 10 using bit shifts?](https://stackoverflow.com/a/5558614) for a 32-bit example of that bug, where it's only exact for small-enough dividends. Also [Why does division by 3 require a rightshift (and other oddities) on x86?](https://stackoverflow.com/q/63417818). – Peter Cordes Oct 07 '20 at 10:05
  • 1
    To increase a negative number by one, you can also use the carry flag: `cmp dh,0x80` / `sbb dx,-1`. – Řrřola Oct 07 '20 at 15:38
0

From grade school

x/6 = x * 1/6;

Look at what happens when you let the C compiler do it

unsigned short fun ( unsigned short x )
{
    return(x/6);
}

32 bit x86

0000000000000000 <fun>:
   0:   0f b7 c7                movzwl %di,%eax
   3:   69 c0 ab aa 00 00       imul   $0xaaab,%eax,%eax
   9:   c1 e8 12                shr    $0x12,%eax
   c:   c3                      retq 

32 bit arm

00000000 <fun>:
   0:   e59f3008    ldr r3, [pc, #8]    ; 10 <fun+0x10>
   4:   e0802093    umull   r2, r0, r3, r0
   8:   e1a00120    lsr r0, r0, #2
   c:   e12fff1e    bx  lr
  10:   aaaaaaab

same story. Translate that to 8086.

so 6 = 3 * 2, so we really need to divide by 3. then adjust

unsigned short fun ( unsigned short x )
{
    return(x/3);
}

00000000 <fun>:
   0:   e59f3008    ldr r3, [pc, #8]    ; 10 <fun+0x10>
   4:   e0802093    umull   r2, r0, r3, r0
   8:   e1a000a0    lsr r0, r0, #1
   c:   e12fff1e    bx  lr
  10:   aaaaaaab

one less bit of shift. One of the shifts is to increase precision the other is because there is a divide by 2 in there.

You can do the subtraction loop of course. Otherwise it is long division which is actually pretty easy to code.

old_timer
  • 62,459
  • 8
  • 79
  • 150