Mòdul 2

Pràctica 5: Aritmètica i fraccions (I)  
Tornar presentació tema
Pràctica 1 Pràctica 2 Pràctica 3 Pràctica 4 Pràctica 4 Pràctica 6  
     
 

En aquesta pràctica i la següent es tracta de fer servir tot el que ja hauràs après fins ara (i potser una mica més) per tal d'elaborar alguna cosa prou complexa: un projecte amb tots els ets i uts!

 
     
  Aritmètica de fraccions de nombres enters  
     
 

Et proposem, doncs, la construcció d'un conjunt de classes i mètodes que tinguin com a finalitat l'automatització del càlcul amb fraccions de nombres enters: allò que, en la majoria de calculadores habituals a les aules de secundària, no pots fer si no és que dónes els resultats com a nombres decimals:

 
     
 
 
     
  El projecte serà, doncs, un conjunt de classes (amb els seus mètodes, constructors,...) que automatitzarà els càlculs amb fraccions i et permetrà trobar els resultats en forma de fracció simplificada.  
   
 
 
 
     
  Obre el . Tria l'opció de menú Projecte|Nou Projecte i comença, doncs, per crear un projecte que convé que guardis en la carpeta de treball (en les pràctiques anteriors t'hem aconsellat ../Bluej/projectes). Dona a aquest projecte el nom d'Aritmetica i, entre aquesta pràctica i la següent, l'ompliràs amb les classes que ha de contenir. Veuràs que el programa ja t'ha preparat la infrastructura pel teu projecte però que encara no hi tens cap classe.  
     
  Una classe plena de mètodes: NombresEnters  
     
 

La primera classe que necessites serà, només, una mena de contenidor de mètodes per treballar amb nombres enters, més enllà de les quatre operacions bàsiques, i no la instanciarem mai (podríem fer-ho, però no en treuríem cap avantatge). Serà com una mena de "biblioteca de mètodes" que podrem cridar des d'altres classes quan ens convingui. En conseqüència, els mètodes d'aquesta classe han d'estar disponibles sense que se n'hagi creat cap objecte!

  • Perquè això sigui així, els mètodes els declararem com a static.

Això fa que una classe amb mètodes static sigui quelcom més que un simple esquema, que un simple esquelet, perque ja conté elements que són funcionals!

 
     
  • Un mètode static vol dir, exactament, que ja està disponible a la classe, no està lligat amb els objectes d'aquesta classe

  • Per cridar un mètode static des d'una altra classe, cal posar com a prefix del nom del mètode el nom de la classe que el conté:

    La_meva_classe.el_meu_metode(<... paràmetres...>);

    i aquesta serà la manera bàsica com farem servir els mètodes static, com ja anireu veient en el transcurs d'aquestes pràctiques.
 
     

Ara, doncs, en l'entorn del clica a Nova Classe, deixa l'opció per defecte per crear una classe i li poses el nom NombresEnters.

Aquesta classe serà la que es tracta de fer i ha de tenir aquest codi:

 
     
 

/**
 * La classe NombresEnters conté alguns mètodes static de càlcul amb
 * nombres enters.
 *
 * @author Carles Romero
 * @version 2004/01/26
 */

public class NombresEnters {

    /**
     * El valor absolut d'un nombre enter.
     * @param a un nombre enter
     * @return el valor absolut del nombre a
     */

    public static int valorAbsolut (int a) {
        int valor=a;
            if (valor<0) {
                // si a és negatiu, canviar-li el signe
                valor=-valor;
            }
        return valor;
    }

    /**
     * El màxim comú divisor positiu de dos enters.
     * @param a b dos nombres enters
     * @return el màxim comú divisor positiu de a i b
     */

    public static int Mcd (int a,int b) {
        int divisor=valorAbsolut(a);
        int residu=valorAbsolut(b);
            while (residu!=0) {
                int dividend=divisor;
                divisor=residu;
                residu=dividend%divisor;
            }
        return divisor;
    }

    /**
     * El mínim comú múltiple positiu de dos enters.
     * @param a b dos nombres enters
     * @return el mínim comú múltiple positiu de a i b
     */

    public static int Mcm (int a,int b) {
        return valorAbsolut(a*b/Mcd(a,b));
    }

}

 
     
 

Per escriure aquest codi a la teva classe NombresEnters has de fer doble clic a la caixa que el programa li ha preparat (o botó dret i Obrir Editor). Llavors has de modificar les propostes inicials que et fa el programa i incloure-hi el text precedent amb el benentès que...

  • ja saps quines són les línies de comentaris; no seria imprescindible escriure-les però t'aconsellem que ho facis per fer més entenedora la lectura posterior del codi.

  • també pots fer retallar i enganxar. Seleccionar i retallar d'aquesta pàgina web i enganxar a la finestra d'editor del codi del

Quan ja ho hagis fet és l'hora d'analitzar el codi amb una explicació dels mètodes que conté la classe.

 
     
  Valor Absolut    
       
  El primer mètode, public static int valorAbsolut (int a), no necessita cap explicació especial: si el nombre a és negatiu se li canvia el signe i, si no, se'l deixa tal com està.  
     
  Màxim comú divisor    
     
 

El segon mètode, public static int Mcd (int a,int b), calcula el màxim comú divisor de dos nombres mitjançant l'ús de l'algorisme d'Euclides. Molt breument: del fet que, si a i b són dos nombres enters i r és el residu de la divisió de a entre b, aleshores m.c.d.(a, b) = m.c.d.(b, r), i del fet que, en una divisió de nombres no negatius, el residu és sempre més petit que el divisor, simplement es tracta d'anar fent les divisions successives fins arribar a residu zero.

Aquest procediment assegura que, per trobar el màxim comú divisor de dos nombres enters, podem dividir l'un per l'altre; tot seguit el que era dividend passa a divisor i el residu obtingut passa a dividend i fem la nova divisió. Si repetim aquesta acció fins que el residu sigui 0, en aquest moment el darrer divisor emprat és el màxim comú divisor.

El mètode comença tot assegurant que els càlculs es faran amb nombres positius i els guarda a les variables divisor i residu.

 
     
 
    public static int Mcd (int a,int b) {
        int divisor=valorAbsolut(a);
        int residu=valorAbsolut(b);
 
     
  Després comença una estructura while(...){...}, que és el primer exemple de bucle (en anglès: "loop") que apareix en aquest curs. D'acord amb el fet que while vol dir mentre l'estructura condicional funciona així:  
     
 

    while (<condició>) { // La condició pot ser certa o falsa
        ... codi ...
        ... codi ... // Aquest codi s'executa només si la
        ... codi ... // condició és certa
        ... codi ...

    }     
            // Ara es torna a la primera línia de l'estructura
            // while(...){...} i es torna a examinar
            // si la condició és certa o falsa...


... més codi ...  // quan la condició ja és falsa, el programa
... més codi ...  // continua...
... més codi ...

 
     
 
  • Mentre la condició sigui certa, s'executará una i altra vegada el codi contingut entre les claus delimitadores { }. Quan la condició ja sigui falsa, se sortirà del bucle i es continuarà amb el codi que vingui a continuació.

  • Si la condició no és certa inicialment el programa no executa cap vegada les instruccions del bucle i passa a les sentències indicades ... més codi...

  • Si, després de l'anàlisi de l'exemple que et proposem, t'interessa veure altres exemples que poden portar-se a la pràctica amb un bucle de programació, mira aquí.

Vegem ara l'exemple que ens ocupa. El nostre bucle repeteix el cicle (les sentències del cos del bucle, que més avall examinarem) mentre residu sigui diferent de zero. Adoneu-vos de quin és per a Java el signe que permet comprovar si els valors de dues variables son diferents: és !=

 
 
            while (residu!=0) {
 
     
  Dels dos nombres, cal guardar el primer que era divisor, en una altra variable intermèdia int dividend:
 
 
                 int dividend=nombre;
 
     
  Tot seguit es fa que el segon dels nombres, que havíem designat com a residu, passi a ser el divisor:  
 
                divisor=residu;
 
     
  i calcula el nou residu amb l'operador mòdul, %. (a%b dóna el residu de la divisió entera de a per b) :  
 
                residu=dividend%divisor;
 
     
  Quan residu sigui zero, divisor (on hi tenim el darrer divisor emprat) ja serà el màxim comú divisor:  
 
                return divisor;
 
     
  Mínim comú múltiple    
     
 
  • El tercer mètode, public static int Mcm (int a,int b), aprofita el fet que, si a i b són dos nombres enters, aleshores a·b = mcd(a,b)·mcm(a,b)
 
 
        return valorAbsolut(a*b/Mcd(a,b));
 
     
  Funcionament dels mètodes    
     
 

Ara hem d'anar per verificar el funcionament d'aquests mètodes.

Compila la classe NombresEnters. Recorda que ho pots fer des de l'entorn del , botó dret del ratolí sobre la caixa de la classe i Compilar, o bé des de l'editor de codi desant primer els canvis i clicant a Compilar.

Sense crear cap objecte de la classe, comprova què fan aquests mètodes, cosa que et mostrem amb unes imatges.

 
     
 
  • public static int valorAbsolut (int a):
 
 
 
 
 
 
     
 
  • public static int Mcd (int a,int b):
 
 
 
 
 
 
 
     
 
  • public static int Mcm (int a,int b):
 
 
 
 
 
 
 
     
Mètodes recursius:  
     
  El mètode public static int Mcd (int a,int b) pel càlcul del màxim comú divisor dels nombres a i b es pot escriure d'una manera força més elegant que la d'abans:  
     

    /**
     * El màxim comú divisor positiu de dos enters.
     * Versió recursiva.
     * @param a b dos nombres enters
     * @return el màxim comú divisor positiu de a i b
     */

    public static int Mcd (int a,int b) {
            if (b==0) { // el segon nombre és zero?
                return valorAbsolut(a);
                  // Sí: el Mcd és el 1r nombre

            } else {
                return Mcd(b,a%b);
                  // b no és 0: calcula el Mcd del segon i

                  // del residu del 1r entre el 2n
            }
    }

 
     
  Com que m.c.d.(a, 0) = a, les línies de codi  
     
 
            if (b==0) { // el segon nombre és zero?
                return valorAbsolut(a);

                  // Sí: el Mcd és el 1r nombre
 
     
  es justifiquen per elles mateixes. D'altra banda, com que, si r és el residu de la divisió de a entre b, aleshores m.c.d.(a, b) = m.c.d.(b, r), i l'operació a%b dóna, precisament, el residu de la divisió de a entre b, les línies  
     
 
            } else {
                return Mcd(b,a%b);

                  // b no és 0: calcula el Mcd del segon i

                  // del residu del 1r entre el 2n
            }
 
     
  queden perfectament explicades. Finalment, com que cada residu a%b és, cada vegada, estrictament menor que l'anterior, és clar que, en algun moment, el càlcul arribarà a a%b==0 i el mètode acabarà.  
     
Observa que, en aquest mètode, la línia  
 
                return Mcd(b,a%b);
 
  fa una crida a ell mateix. Els mètodes que fan això es diuen mètodes recursius i solen ser més eficients que els que fan la mateixa feina, però no són recursius.  
     
Pràctica, pràctica!  
     
  Un primer exercici per a comprovar les teves habilitats en mètodes recursius és aquest:  
     
 

Escriu un mètode recursiu,
        public static int potencia (int a,int n),
que retorni a elevat a la potència n, és a dir, a multiplicat n vegades per sí mateix. Has d'aprofitar els fets que a0 = 1, i que an = a·an-1. Naturalment, el mètode entraria en un cicle infinit si n < 0. Això se soluciona amb un objecte Exception, però no t'ho expliquem fins a la propera pràctica.

 
     
  Si no te'n surts (ep! només si no te'n surts), o bé, vols comparar el teu codi amb el que nosaltres proposem, trobaràs la solució d'aquest exercici aquí.  
     
 

Encara un altre exercici. Escriu un mètode recursiu,
        public static int factorial(int n)
,
que retorni el factorial n! del nombre n, això és:
               n
! = n·(n - 1)·(n - 2)·...·3·2·1
.
Per exemple, 3! = 3·2·1 = 6 i 5! = 5·4·3·2·1 = 120.
Per definició, 0! = 1 i n! = n·((n - 1)!).

 
     
  La solució és aquí.  
     
  Comentari final:    
     
  Tot i que, com ja t'hem comentat, els mètodes recursius solen ser els més potents, en aquests dos exercicis que acabem de plantejar hi ha una característica que sovint hom demana als programes informàtics: la repetició d'un procediment (multiplicar a per sí mateix un munt de vegades; multiplicar uns nombres, el resultat per l'anterior, etc...).
  • En aquesta pràctica has vist un procediment del tipus bucle (while) que t'ofereix una altra manera d'enfocar els dos mètodes anteriors. A veure si te'n surts! També (ep! només si no te'n surts) pots consultar el codi de la potència i el factorial amb un while.

  • Ara bé, també és interessant que sàpigues que, més endavant, t'explicarem un altre procediment d'iteració