Reeks 6 :   Registermachines
1 Delen
2 Machtsverheffing
3 Machtsverheffing (recursief)
4 Fibonacci
5 Gelukkige getallen
6 Fietsvakantie
7 Oude oefeningen
7.1 Factorial
7.2 Programma’s combineren
7.2.1 Display One
7.2.2 Display Zero
7.2.3 Divide
7.2.4 Modulo
7.2.5 Combineer
8.9

Reeks 6 : Registermachines

Deze oefeningenreeks behandelt het beschrijven van computationele processen aan de hand van registermachineprogrammaʼs (hoofdstuk 5.1 van Structure and Interpretation of Computer Programs).

Registermachines manipuleren een beperkt aantal geheugenplaatsen of registers. Alhoewel de resulterende procesbeschrijvingen op traditionele machinetaal voor een microprocessor lijken, hebben we in de hoorcolleges in weze telkens een op maat gemaakte microprocessor ontworpen. Het bestand "icp_5_regsim.scm" bevat deze registermachinesimulator. We hebben ook een "grafische registermachinesimulator" voor jullie ontwikkeld.

De registermachine waarvoor wij deze les programma’s zullen schrijven heeft vier registers '(cont v1 v2 res) en ondersteunt volgende operatoren: + * - = < >.

Indien je geen gebruik maakt van Dodona, dan kan het handig zijn om de volgende (assemble-and-run p) functie te gebruiken. Hier is p een list van instructies (en labels). Wanneer het programma stopt, wordt de inhoud van het res register weergegeven.

(define (assemble-and-run p)
  (define m (make-machine
              '(cont v1 v2 res)
              ops
              p))
  (start m)
  (display "\nFinal Result:\n")
  (display (get-register-contents m 'res)))

Elk registermachineprogramma bestaat uit een lijst van labels en instructies. Ter herinnering, de instructies die je kunt gebruiken zijn de volgende:
(goto (label/reg _))
(test (op _) (reg/const _) (reg/const _))
(branch (label _))
(assign reg (op _) (reg/const _) (reg/const _))
(assign reg (label/const/reg _))
(save reg)
(restore reg)
(perform (op _) (reg/const _))

Het volgende stukje code kan je op weg helpen. Het toont de implementatie en oproep van een procedure die altijd 3 terug geeft.

Scheme code:

(define (return-3 a b)
  3)
(return-3 15 4)

Equivalente registermachine:

> (assemble-and-run '(start
                      (assign v1 (const 15))
                      (assign v2 (const 4))
                      (assign cont (label stop))
                      (goto (label start-return-3))
  
                      start-return-3
                      (assign res (const 3))
                      (goto (reg cont))
  
                      stop))

Final Result:

3

Voor het vertalen van Scheme-procedures naar registermachineprogramma’s gebruiken we enkele conventies die jullie goed van pas zullen komen.

1 Delen

Zoals je hierboven ziet, ondersteunt onze registermachine geen deling (/). Ontwerp een registermachine die het toelaat om de gehele deling van twee getallen uit te voeren. Laat je hierbij inspireren door volgende Scheme-procedure:
(define (start-divide v1 v2)
  (define (divide-loop res v1 v2)
    (if (< v1 v2)
        res
        (divide-loop (+ res 1) (- v1 v2) v2)))
  (divide-loop 0 v1 v2))

2 Machtsverheffing

Ontwerp een registermachine die het toelaat om een geheel getal tot een gehele macht te verheffen. Laat je hierbij inspireren door volgende Scheme-procedure:
(define (expt b n)
  (define (expt-iter counter product)
    (if (= counter 0)
        product
        (expt-iter (- counter 1) (* b product))))
  (expt-iter n 1))

3 Machtsverheffing (recursief)

Ontwerp opnieuw een registermachine die de machtsverheffing implementeert. Doe dit deze keer door middel van een recursief process. Laat je hierbij inspireren door volgende Scheme-procedure:
(define (expt b n)
  (if (= n 0)
      1
      (* b
         (expt b (- n 1)))))

4 Fibonacci

Ontwerp een registermachine die fibonacci implementeert. Laat je hierbij inspireren door volgende boomrecursieve Scheme-procedure:
(define (fib n)
  (if (< n 2)
      n
      (+ (fib (- n 1))
         (fib (- n 2)))))

Maak in deze oefening geen gebruik van het v2 register.

Herinner welke waarden de fibonacci functie kan aannemen:

n

 

(fib n)

0

 

0

1

 

1

2

 

1

3

 

2

4

 

3

5

 

5

6

 

8

7

 

13

8

 

21

9

 

34

10

 

55

11

 

89

5 Gelukkige getallen

Deze oefening was een examenvraag in de tweede zit van het academiejaar 2018-2019.

Morgen, 27 augustus, is de 239ste dag van het jaar 2019. Hopelijk zijn jullie dan allemaal geslaagd voor dit vak. Een gevoel van gelukzaligheid zal zich meester van jullie maken. Dat is geen toeval, want 239 is een gelukkig getal.

Testen of een getal een gelukkig getal is, doe je door de individuele cijfers te kwadrateren en op te tellen. In het geval van 239 geeft dit 2² + 3² + 9² = 94. Dit proces blijf je herhalen. Indien je uiteindelijk bij 1 uitkomt, dan was het originele getal een gelukkig getal. Voor 239 krijg je dus: 239 → 94 → 97 → 130 → 10 → 1. Indien dit proces niet tot een 1 leidt, dan komt het in een loop terecht waar het niet meer uitgeraakt.

Beschouw de volgende Scheme code die #t teruggeeft indien het gegeven getal een gelukkig getal is. Indien het geen gelukkig getal is, blijft de code lopen.

(define (is-gelukkig n)
  (if (= n 1)
      #t
      (is-gelukkig (kwadrateer-cijfers n))))
(define (kwadrateer-cijfers n)
  (define (kwadrateer-cijfers-loop x y)
    (if (< x 10)
        (+ y (* x x))
        (kwadrateer-cijfers-loop (verwijder-laatste-cijfer x)
                                 (+ y
                                    (* (laatste-cijfer x)
                                       (laatste-cijfer x))))))
  (kwadrateer-cijfers-loop n 0))

Je wordt gevraagd de twee gegeven Scheme functies om te zetten naar twee subroutines in een registermachine. De registermachine waarop jouw code uitgevoerd zal worden, ondersteunt +, *, =, <, laatste-cijfer, en verwijder-laatste-cijfer als primitieve operaties. Die twee laatste doen wat hun naam aanduidt. De registermachine beschikt over drie registers: v1, res, en cont. De subroutines worden verwacht de richtlijnen uit de WPOs te volgen i.v.m. het gebruik van labels en het gebruik van deze registers.

Bespreek ten slotte kort (maximum drie zinnen) hoe jouw registermachine zich gedraagt indien het gegeven getal geen gelukkig getal is. Zoals eerder vermeld zal dit tot een loop leiden. Blijft deze loop oneindig lang duren? Zal de registermachine crashen door een tekort aan geheugen? Waarom wel of waarom niet?

Om volledig te zijn, hier kan je dus niet assemble-and-run gebruiken. Gebruik in plaats daarvan de volgende machine.

(let* ((laatste-cijfer (lambda (x) (modulo x 10)))
       (verwijder-laatste-cijfer (lambda (x) (floor (/ x 10))))
       (geluk-machine
        (make-machine
         '(cont res v1)
         `((+ ,+) (* ,*) (= ,=) (< ,<)
           (laatste-cijfer ,laatste-cijfer)
           (verwijder-laatste-cijfer ,verwijder-laatste-cijfer))
 
         '(start
           (assign v1 (const 239))
           (assign cont (label stop))
           (goto (label is-gelukkig))
 
           is-gelukkig
           ... Jouw code hier
 
           kwadrateer-cijfers
           ... Jouw code hier
 
           stop))))
  (display "Gelukkig getal? ")
  (start geluk-machine)
  (display (get-register-contents geluk-machine 'res))
  (newline))

6 Fietsvakantie

Deze oefening was een examenvraag in de eerste zit van het academiejaar 2020-2021.

Voor komende zomer zou je graag een fietsvakantie plannen. Daarvoor zou je de algemene hellingsgraad van de tocht willen kennen. De hellingsgraad is de hoogte gedeeld door de lengte, neergeschreven in procent. Ga je bijvoorbeeld 52 meter omhoog over 1000 meter, dan is de hellingsgraad 52 / 1000 = 0.052 = 5.2%.

Je zoekt en vindt enkele gegevens online, maar helaas zijn deze opgedeeld in "segmenten" van een beklimming. De gegevens van de Lukmanierpas in de Zwitserse Alpen zien er bijvoorbeeld als volgt uit:

(define hoogtes ( 228   52   72   43   64   53   67   72  194))
(define lengtes (3000 1000 3000 1000 1000 1000 1000 2000 2800))

Ter verduidelijking stelt volgende afbeelding het eerste en het voorlaatste segment voor:

Om de hellingsgraad van de hele klim uit te rekenen, moet je de hoogtes optellen en dit getal delen door de som van alle lengtes:

(define som_hoogtes (+ 228 52 72 43 64 53 67 72 194))
; 845
(define som_lengtes (+ 3000 1000 3000 1000 1000 1000 1000 2000 2800))
; 15800
(define hellingsgraad_lukmanierpas (/ 845 15800))
; = 5.35%

De volgende Scheme code voert deze berekening uit:
(define (hellingsgraad hoogtes lengtes)
  (* 100
     (/ (som hoogtes)
        (som lengtes))))
 
(define (som lst)
  (if (null? lst)
      0.0
      (+ (car lst)
         (som (cdr lst)))))

Je kan de Scheme code als volgt gebruiken:
> (hellingsgraad hoogtes lengtes)
5.348101265822785

Jou wordt nu gevraagd om de hellingsgraad en som functies om te zetten naar twee subroutines in registermachinecode. Het iteratieve of recursieve karakter van de functies moet natuurlijk behouden worden. De registermachine waarop jouw subroutine wordt uitgevoerd, ondersteunt null?, car, cdr, +, *, en / als operaties. De registermachine beschikt over vier registers: cont, v1, v2 en res. Alle subroutines in deze registermachine volgen hetzelfde “contract”: input wordt verwacht in v1 en v2, output belandt in res en de inhoud van cont wordt gebruikt om uit de subroutine te springen wanneer deze klaar is. We verwachten hetzelfde van jouw code. Samengevat, jouw code moet hieronder ingevoegd kunnen worden en het gepaste resultaat in res achterlaten:

(let ((helling-machine
       (make-machine
         '(cont v1 v2 res)
         `((null? ,null?) (car ,car) (cdr ,cdr) (+ ,+) (* ,*) (/ ,/))
         '(start
          (assign v1 (const ( 228   52   72   43   64   53   67   72  194)))
          (assign v2 (const (3000 1000 3000 1000 1000 1000 1000 2000 2800)))
          (assign cont (label stop))
          (goto (label hellingsgraad))
 
          ; <jouw code hier>
 
          stop))))
  (start helling-machine)
  (display (get-register-contents helling-machine 'res)))

7 Oude oefeningen

Deze oefeningen maken geen deel meer uit van de WPOs. Indien je je verveelt, mag je ze natuurlijk ook maken.

7.1 Factorial

Ontwerp een registermachine die het toelaat om de faculteit van een geheel getal te berekenen. Laat je hierbij inspireren door volgende Scheme-procedure:
(define (factorial n)
  (define (iter product counter)
    (if (< n counter)
        product
        (iter (* counter product) (+ counter 1))))
  (iter 1 1))

Enkele voorbeelden:

n

 

(factorial n)

3

 

6

5

 

120

11

 

39916800

7.2 Programma’s combineren

Ontwerp een registermachine die het getal in register v1 in binaire vorm op het scherm toont. Doe dit door eerst afzonderlijk enkele kleine registermachines te ontwerpen. Als je iets wilt weergeven op het scherm gebruik dan de operatie perform met display, bijvoorbeeld als volgt:

(perform (op display) (const 0))

7.2.1 Display One

Ontwerp een registermachine die enkel een 1 weergeeft.

7.2.2 Display Zero

Ontwerp een registermachine die enkel een 0 weergeeft.

7.2.3 Divide

Ontwerp een registermachine die de gehele deling uitvoert (zie eerste oefening).

7.2.4 Modulo

Ontwerp een registermachine die de rest na deling berekent (zie eerste oefening).

7.2.5 Combineer

Ontwerp nu een registermachine die het getal in register v1 in binaire vorm op het scherm toont. Maak hierbij gebruik van (de) stukjes registermachine die je net maakte, en laat je inspireren door de volgende procedure:

(define (display-binary n)
  (if (= n 0)
      (display-zero)
      (if (= n 1)
          (display-one)
          (begin
            (display-binary (divide n 2))
            (if (= (modulo n 2) 0)
                (display-zero)
                (display-one))))))