Project Euler și AC

Pentru că marți am examen practic la Arhi­tec­tura Cal­cu­la­toarelor, am decis să mă pregătesc și eu un picuț. Seara la 10. Cu ce să ma pregătesc mai bine decât cu probleme de la Project Euler? Acesta este un site unde se găsesc o mulțime de probleme, mai mult de al­go­rit­mică, care pot fi rezolvate în orice limbaj de programare. Am făcut primele 3 în PHP mai demult, iar acum o să refac primele 2 în Assembly (cu anumite limitări), și o să încerc să mă explic cât mai bine.

Problema 1: Find the sum of all the multiples of 3 or 5 below 1000.

assume cs:code, ds:data
data segment
    suma dd 0; suma sub forma de numar
    suma_caract db 10 DUP(?) ; suma sub forma de sir de caractere
    citire db 'Dati numarul pana la care sa se mearga $'
    maxNrLength db 10 ; lungimea maxima a numarului
    nrLength db ? ; lungimea numarului dat de la tastatura
    numar_caract db 10 DUP(?) ; numarul dat de la tastatura sub forma de sir de caractere
    numar dw 0 ; numarul dat de la tastatura sub forma de numar
    sfarsit db 'Suma multiplilor de 3 si 5 este $'
    zece dw 10 ; pentru impartire
    trei dw 3
    cinci dw 5
data ends

code segment

getNumar proc ;transforma sirul de caractere in numar
    ; punem in cl lungimea sirului de caractere care reprezinta numarul
    mov cl,nrLength
    mov ch,0

    ; punem in si offsetul sirului de caractere si setam direction flag la 0
    mov si,offset numar_caract
    cld

    parcurge:
        ; citim urmatorul caracter si scadem 48 din el pentru a obtine cifra sub forma de numar,  inmultim numarul cu 10 si apoi adunam cifra noua
        lodsb
        sub al,48
        mov bl,10
        mov dl, al
        mov ax, numar
        mul bl
        mov dh,0
        add ax,dx
        mov numar, ax
    loop parcurge
    ret
getNumar endp

getCarac proc; transforma numarul in sir de carac
    mov di, offset suma_caract + 10
    std
    mov al, '$'
    stosb
    mov bx, word ptr suma + 2; mutam prima parte a numarului in bx pentru a-l transforma
    cmp bx,0
    je mutapartea2 ; daca prima parte e diferita de 0 trecem la a o transforma in sir de caractere. daca e 0 trecem la a transforma partea a 2a in sir de caractere
    impartire:
        mov ax,bx
        mov dx,0
        div zece
        mov bx,ax
        add dx,48
        mov al,dl
        stosb
        cmp bx,0
    jne impartire
    mutapartea2:; aceasi chestie doar ca pentru a 2a parte a sirului de caractere
        mov bx, word ptr suma
    impartire2:
        mov ax,bx
        mov dx,0
        div zece
        mov bx,ax
        add dx,48
        mov al,dl
        stosb
        cmp bx,0
    jne impartire2
    ret
getCarac endp
start:
    ; mutam adresa segmentului data in ds si es
    mov ax, data
    mov ds, ax
    mov es, ax
    ; afisam mesajul pentru citire
    mov ah,09h
    mov dx, offset citire
    int 21h

    ; citim numarul maxim
    mov ah,0Ah
    mov dx, offset maxNrLength
    int 21h

    call getNumar

    ;mutam in cx numar - 3 pentru a indica de cate ori vrem sa se repete operatiile
    mov cx, numar
    sub cx,3
    ; incepem cautare de la 3
    mov bx,3
    aduna:
        ; mutam numarul la care am ajuns in ax si il impartim la 3, respectiv 5 si comparam restul din dx cu 0.
        mov ax,bx
        mov dx,0
        div trei
        cmp dx,0
        je multiplu
        mov ax,bx
        mov dx,0
        div cinci
        cmp dx,0
        je multiplu
        jne comun
        multiplu: ; daca vreunul din resturi e 0 inseamna ca numarul e multiplu de 3 sau 5 si il adunam la prima parte a sumei, iar la a doua adunam eventualul transport (atentie la little endian)
            add word ptr suma,bx
            adc word ptr suma + 2, 0
        comun:
            ; crestem numarul cu 1
            inc bx
    loop aduna

    call getCarac

    ; afisam mesajul de sfarsit
    mov ah,09h
    mov dx, offset sfarsit
    int 21h
    ; afisam suma
    mov ah, 09h
    mov dx, offset suma_caract
    int 21h

    ; terminam programul si returnam 0
    mov ax,4C00h
    int 21h
code ends
end start

Calculul se face corect pentru 1000 (rezultatul este 233168), problema este la afisarea rezul­tat­u­lui pentru valori ale lui n mai mari de 500. De la acea valoare încolo suma nu se mai poate reprezenta pe 16 biți, iar cum la afișare facem împărțire la 10, apare o problemă la împărțirea unui număr pe 32 biți la 10. Și nu am chef să im­ple­mentez algoritmul lui Fourier pentru împărțire în Assembly, așa că testați în Turbo Debugger.

Problem 2: By con­sid­er­ing the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms. Problemă care se vede din start: trebuie să merg până la 4.000.000, care încape pe 32 de biți. Așa că o să reducem un pic domeniul până la 65000 :D. Dar algoritmul e la fel.

assume cs:code, ds:data
data segment
    suma dd 0; suma sub forma de numar
    suma_caract db 10 DUP(?) ; suma sub forma de sir de caractere
    citire db 'Dati numarul pana la care sa se mearga $'
    maxNrLength db 10 ; lungimea maxima a numarului
    nrLength db ? ; lungimea numarului dat de la tastatura
    numar_caract db 10 DUP(?) ; numarul dat de la tastatura sub forma de sir de caractere
    numar dw 0 ; numarul dat de la tastatura sub forma de numar
    sfarsit db 'Suma termenilor pari din seria Fibonacci este $'
    zece dw 10 ; pentru impartire
    f1 dw 1 ; termeni fibonacci
    f2 dw 2
data ends

code segment

getNumar proc ;transforma sirul de caractere in numar
    ; punem in cl lungimea sirului de caractere care reprezinta numarul
    mov cl,nrLength
    mov ch,0

    ; punem in si offsetul sirului de caractere si setam direction flag la 0
    mov si,offset numar_caract
    cld

    parcurge:
        ; citim urmatorul caracter si scadem 48 din el pentru a obtine cifra sub forma de numar,  inmultim numarul cu 10 si apoi adunam cifra noua
        lodsb
        sub al,48
        mov bl,10
        mov dl, al
        mov ax, numar
        mul bl
        mov dh,0
        add ax,dx
        mov numar, ax
    loop parcurge
    ret
getNumar endp

getCarac proc; transforma numarul in sir de carac
    mov di, offset suma_caract + 10
    std
    mov al, '$'
    stosb
    mov bx, word ptr suma + 2; mutam prima parte a numarului in bx pentru a-l transforma
    cmp bx,0
    je mutapartea2 ; daca prima parte e diferita de 0 trecem la a o transforma in sir de caractere. daca e 0 trecem la a transforma partea a 2a in sir de caractere
    impartire:
        mov ax,bx
        mov dx,0
        div zece
        mov bx,ax
        add dx,48
        mov al,dl
        stosb
        cmp bx,0
    jne impartire
    mutapartea2:
        mov bx, word ptr suma
    impartire2:
        mov ax,bx
        mov dx,0
        div zece
        mov bx,ax
        add dx,48
        mov al,dl
        stosb
        cmp bx,0
    jne impartire2
    ret
getCarac endp
start:
    ; mutam adresa segmentului data in ds si es
    mov ax, data
    mov ds, ax
    mov es, ax
    ; afisam mesajul pentru citire
    mov ah,09h
    mov dx, offset citire
    int 21h

    ; citim numarul maxim
    mov ah,0Ah
    mov dx, offset maxNrLength
    int 21h

    call getNumar

    repeta:
        mov ax, f1 ; comparam numarul cu 1 prin AND
        test ax,1
        je urmator
        ; daca e 0 inseamna ca e par si il adunam la suma, avand grija la little endianess
        mov ax, f1
        add word ptr suma, ax
        adc word ptr suma + 2, 0
        urmator: ; generam urmatorul termen fibonacci, salvand in ax f2, adunand la f2 f1 (prin cx), si mutand in f1 ax (egal cu f2)
        mov ax, f2
        mov cx, f1
        add f2, cx
        mov f1, ax
        cmp ax, numar ; comparam f1 cu numarul si daca e mai mic repetam
        jb repeta

    call getCarac
    ;afisam mesajul de sfarsit
    mov ah,09h
    mov dx, offset sfarsit
    int 21h
    ; afisam suma
    mov ah, 09h
    mov dx, offset suma_caract
    int 21h

    ; terminam programul si returnam 0
    mov ax,4C00h
    int 21h
code ends
end start

Sper că pe cineva au ajutat aceste două probleme. Și acum să merg să învăț un set de chestii inutile: cum lucrez cu fișiere în ASM pe 16 biți și DOS.