0

I am trying to write a program that can determine if a number is perfect or not.
The program reports the number 6 to be perfect, but for the number 28, which is also a perfect number, it tells that it is not perfect.
Can you help?

.data 
    msg1 db 13,10," Enter a number:$"
    perf db 13,10,"The number is perfect number $"
    nperf db 13,10,"The number is not perfect number $"
    num dw ?
    x dw 0
.const

.code


 ppp proc _x$:word

        uses bx,dx,ax,cx
        mov bx,0
        mov ax,_x$
        mov cx,ax
        dec cx  
        cmp ax,1        
        je perfecto     
           next:
           cwd
           div cx
           cmp dx,0
           je sum 
           dec cx
           jmp next
                       
            sum:
            add bx,cx
            dec cx
            cmp cx,0
            jnz next
            mov ax,_x$
            cmp ax,bx
            je perfecto
            call puts,offset nperf
             jmp done
             perfecto:
            call puts,offset perf

    
       
       done:
        ret
 
      ppp endp

    .startup
        call puts,offset msg1
        call endl
        call getint
        mov num,ax
        call ppp,num
        finish2:
    .exit
end
Sep Roland
  • 33,889
  • 7
  • 43
  • 76
  • When you [single-step with a debugger](https://idownvotedbecau.se/nodebugging/), what do you see in registers? Which compare / branch goes the wrong way? Is 28 even ending up as an integer in a register correctly in the first place? Also comment your code with the basics of your algorithm, like that you're keeping the sum in BX. – Peter Cordes Apr 20 '22 at 02:43
  • One bug that wouldn't affect an input of `28` is that before unsigned `div`, you should zero-extend AX into DX:AX with `xor dx,dx`, not sign-extend with `cwd`. – Peter Cordes Apr 20 '22 at 02:46
  • Hint: `0 / 25` has `0` remainder, but your code shouldn't be doing that division in the first place. `6` is a special case because all the divisors are prime factors so it works to divide them out and keep that quotient. – Peter Cordes Apr 20 '22 at 02:50

1 Answers1

1
cmp ax,1        
je perfecto

Why do you consider 1 to be a perfect number? The wikipedia article https://en.wikipedia.org/wiki/Perfect_number uses the following definition:

In number theory, a perfect number is a positive integer that is equal to the sum of its positive divisors, excluding the number itself. For instance, 6 has divisors 1, 2 and 3 (excluding itself), and 1 + 2 + 3 = 6, so 6 is a perfect number.

The number 1 has but one divisor (1) and that's the one that the definition wants you to exclude. What remains is nothing and that is clearly not equal to the number 1.
Moreover that Wikipedia article also mentions that there probably are no odd perfect numbers.


Why the code fails

The idea behind your code is to divide the inputted number N by all of the numbers in the range [N-1, 1]. Upon finding a zero remainder, you add the current divisor to your sum variable BX. This is not optimal, but it can work. However, because you neglect to reload the original number, you are not dividing the inputted number but rather the quotient that you got from the previous division. It seemingly works for the number 6 because its 3 divisors follow each other closely and are the final 3 iterations of the loop.

            AX   DX
 6 /  5  -> Q=1  R=1
 1 /  4  -> Q=0  R=1
 0 /  3  -> Q=0  R=0  -> BX=0+3=3
 0 /  2  -> Q=0  R=0  -> BX=3+2=5
 0 /  1  -> Q=0  R=0  -> BX=5+1=6   OK

Now consider 28:

            AX   DX
28 / 27  -> Q=1  R=1
 1 / 26  -> Q=0  R=1
 0 / 25  -> Q=0  R=0  -> BX=0+25=25
 0 / 24  -> Q=0  R=0  -> BX=25+24=49
 0 / 23  -> Q=0  R=0  -> BX=49+23=72
 0 / 22  -> Q=0  R=0  -> BX=72+22=94
 0 / 21  -> Q=0  R=0  -> BX=94+21=115   ???
...

A solution

  • Better start dividing at half the inputted number. Larger divisors will never produce a zero remainder.
  • Better no do that final division by 1. It will always produce a zero remainder. Just inc bx in the end. Or even better initialize the running sum at 1 instead of 0.
  • Better write the division loop with a single conditional branch instruction instead of a conditional exit branch and an unconditional repeat branch.
  mov  bx, 1       ; Running sum
  mov  cx, _x$     ; Inputted number
  shr  cx, 1       ; First divisor is N/2
  jz   NotPerfect  ; N=0 or N=1
next:
  mov  ax, _x$     ; RELOADING NUMBER
  xor  dx, dx
  div  cx
  dec  cx          ; (*)
  test dx, dx
  jnz  next

  add  bx, cx
  inc  bx          ; (*) Because CX got already decremented
  cmp  cx, 1
  ja   next
            AX   DX
28 / 14  -> Q=2  R=0  -> BX=1+(13+1)=15
28 / 13  -> Q=2  R=2
28 / 12  -> Q=2  R=4
28 / 11  -> Q=2  R=6
28 / 10  -> Q=2  R=8
28 /  9  -> Q=2  R=10
28 /  8  -> Q=3  R=4
28 /  7  -> Q=4  R=0  -> BX=15+(6+1)=22
28 /  6  -> Q=4  R=4
28 /  5  -> Q=5  R=3
28 /  4  -> Q=7  R=0  -> BX=22+(3+1)=26
28 /  3  -> Q=9  R=1
28 /  2  -> Q=14 R=0  -> BX=26+(1+1)=28  OK
Sep Roland
  • 33,889
  • 7
  • 43
  • 76