-3
jmp start

  mess1 db 'Enter 1st number: $'
  mess2 db 0a,0d, 'Enter 2nd number: $'
  nextline db 0a,0d, '$'


start:

  mov ax, 03
  int 10h

  mov dx, offset mess1
  call printstring
  call input
  sub al, 30h
  push ax

  mov dx, offset mess2
  call printstring
  call input
  mov bl, al
  mov dx, offset nextline
  call printstring
  sub cl, 30h
  sub bl, 30h
  pop ax

  mov ah, 0
  aad
  div bl

  mov dl, al
  add dl, 30h


  push ax
  call printchar

  mov dl, '.'
  call printchar
  mov ah, 0
  pop ax
  mov dl, ah
  add dl, 30h
  call printchar
  int 20h


input:
  mov ah, 1
  int 21h
  ret

printstring:
  mov ah, 9
  int 21h
  ret

printchar:
  mov ah, 2
  int 21h
  ret

It is functional when the remainder is 0, but the remainder will only display wrong numbers when the equation has a remainder.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198

1 Answers1

2

The remainder is the numerator for a fraction. For example, "5/3 = 1 with a remainder of 2" (because "5 - (1*3) = 2"), so the true result would be "1 + 2/3" (or "quotient + remainder/divisor"). Note that many fractions are recurring decimals, and "1 + 2/3" would be the number 2.666666666...

The easiest way to display this is to display it as a fraction (e.g. print the remainder, the '/' sign and the divisor).

To print it as a decimal; you can repeatedly multiply "remainder/divisor" by 10 and extract the integer part. E.g. "2*10 / 3 = 6 with a remainder of 2" so you can print '6' and do it again with the new remainder, which for this example will be the same, so you'll end up printing '6' repeatedly (until you stop for some other reason - e.g. because you limited it to 3 digits). This works better for other fractions, like "1/8"; where you'd do "1*10/8 = 1 with a remainder of 2; 2*10/8 = 2 with a remainder of 4; 4*10/8 = 5 with a remainder of 0" to get the digits 1, 2 then 5.

The other problem is that this will truncate towards zero and humans prefer "round to nearest" - e.g. for "5/3" you'd end up printing "5.666" and humans would want "5.667" instead. To fix that, if you're doing 3 digits after the decimal point, you'd want to add 0.0005 to the original remainder at the beginning, and to do that with fractions it ends up being cross multiplication - e.g. "remainder/divisor + 1/2000
= (remainder*2000) / (divisor*2000) + (divisor*1) / (divisor*2000)
= (remainder*2000 + divisor*1) / (divisor*2000)
= (remainder*2000 + divisor) / (divisor*2000)".

In other words; if you do "remainder = remainder*2000 + divisor" and "divisor = divisor*2000" before converting the fraction to three decimal digits you'll get a correctly rounded result. Note: In some cases this rounding can change the integer part - e.g. the value 3.99987654321 should be displayed as "4.000" but if you're not careful you'll display "3.000" instead. Fortunately you won't have to worry about this for your case (as the divisor is always a number from 1 to 9).

In assembly; this might look like (NASM syntax, untested):

; ax = number1 = [0,9]; bx = number2 = [1,9]

    div bl                 ;ah = remainder, al = quotient

    ;Do integer part

    mov dl,al              ;dl = quotient
    call printchar         ;Print the quotient

    ;Prepare for fractional part

    shr ax,8               ;ax = remainder (from 0 to 8)
    mov cx,2000
    mul cx                 ;dx:ax = ax = remainder*2000 (from 0 to 16000)
    add ax,bx              ;ax = remainder*2000+divisor (from 1 to 16009)
    push ax                ; (1)
    mov ax,bx              ;ax = divisor (from 1 to 9)
    mul cx                 ;dx:ax = ax = divisor*2000 (from 2000 to 18000)
    mov bx,ax              ;bx = divisor*2000 (from 2000 to 18000)

    mov dl,'.'
    call printchar         ;Print the decimal point

    pop ax                 ; (1)
    mov cx,10
    ;Do the first fractional digit

    mul cx                 ;dx:ax = (remainder*2000+divisor) * 10 (from 10 to 160090)
    div bx                 ;ax = (remainder*2000+divisor) / (divisor*2000); dx = next_remainder (from 0 to 17999)
    push dx
    mov dl,al
    call printchar         ;Print the first factional digit
    pop ax                 ;ax = next_remainder (from 0 to 17999)

    ;Do the second fractional digit

    mul cx                 ;dx:ax = next_remainder * 10 (from 0 to 179990)
    div bx                 ;ax = (next_remainder*10) / (divisor*2000); dx = next remainder
    push dx
    mov dl,al
    call printchar         ;Print the second factional digit
    pop ax                 ;ax = next_remainder (from 0 to 17999)

    ;Do the last fractional digit

    mul cx                 ;dx:ax = next_remainder * 10 (from 0 to 179990)
    div bx                 ;ax = (next_remainder*10) / (divisor*2000); dx = next remainder
    mov dl,al
    call printchar         ;Print the third factional digit

Note that it's important to keep track of variable ranges (e.g. as I have done in the comments above) to provide an assurance that there's no overflow bugs. For example, an integer value from from 10 to 180000 will not fit in 16 bits (and will not fit in a 16-bit register like AX), and we're merely lucky that the instruction set makes it easy to use a pair of registers ("DX:AX" or "highest 16 bits in DX and lowest 16 bits in AX") for those cases.

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
Brendan
  • 35,656
  • 2
  • 39
  • 66
  • "... an integer value from from 10 to 180000 will not fit in 16 bits" Apart from using "from" twice, this is still somewhat unclear. – Sep Roland Mar 02 '23 at 17:18
  • The code was displaying 8/3 as 2.666, instead of the desired 2.667. I have corrected the answer regarding a mistake with the cross multiplication. I have amended the code too, but you might want to verify it... (I also reorganised the PUSH/POP because the OP's *printchar* would have destroyed AX otherwise.) – Sep Roland Mar 02 '23 at 17:24