LUNDS TEKNISKA HÖGSKOLA Institutionen för datavetenskap EDA011/017 Programmeringsteknik 2016/2017 Exempel från föreläsning 7 Jag demonstrerade två olika sätt att implementera en klass för komplexa tal. Även om Java inte har inbyggda typer för komplexa tal (på samma sätt som int eller double) så kan vi skapa en sådan klass själva. Idén är att vi då kan uttrycka komplexa beräkningar så här: public class ComplexExample { public static void main(String[] args) { Complex a = new Complex(3, 4); Complex b = new Complex(7, 9); Complex c = a.plus(b); System.out.println(c.toString()); System.out.println(c.getAbs()); } } Här skapas summan c av talen a = 3 + 4i och b = 7 + 9i. Vi förväntar oss en utskrift i stil med 10.0+13.0i 16.401219466856727 Vi eftersträvar närmare bestämt följande specifikation: Complex /** Skapa ett komplext tal x + iy */ Complex(double x, double y); /** Hämta realdelen */ double getRe(); /** Hämta imaginärdelen */ double getIm(); /** Hämta absolutbeloppet */ double getAbs(); /** Hämta argumentet (vinkeln) */ double getArg(); /** Hämta ett nytt komplext tal som motsvarar summan av detta tal och other */ Complex plus(Complex other); /* Hämta en sträng enligt mönstret 1.0+2.5i */ String toString(); 1 2 Den första implementationen bygger på att vi använder real- och imaginärdel som attribut (re och im) och därefter beräknar absolutbelopp och argument vid behov. public class Complex { private double re; private double im; /** Skapa ett komplext tal x + iy */ public Complex(double x, double y) { this.re = x; this.im = y; } // 1 /** Hämta realdelen */ public double getRe() { return re; } /** Hämta imaginärdelen */ public double getIm() { return im; } /** Hämta absolutbeloppet */ public double getAbs() { return Math.sqrt(re * re + im * im); } /** Hämta argumentet (vinkeln) */ public double getArg() { return Math.atan(im / re); } // 2 /** Hämta ett nytt komplext tal som motsvarar summan av detta tal och other */ public Complex plus(Complex other) { return new Complex(this.getRe() + other.getRe(), // 3 this.getIm() + other.getIm()); } /* Hämta en sträng enligt mönstret 1.0+2.5i */ public String toString() { return this.getRe() + "+" + this.getIm() + "i"; } // 3 } Kommentarer: 1. Här skulle man lika gärna kunna skriva re = x . Att skriva this framför är inte nödvändigt eftersom det inte finns något annat som heter re här. I andra fall har vi ofta haft parametrar som heter samma sak som attributen, och då är this nödvändigt. 2. Som studenten D mycket riktigt påpekade fungerar arctan-beräkningen bara i första kvadranten. Det har emellertid inte så mycket med Java-klasser att göra som med föreläsarens pinsamt dimmiga minnen av polär form för komplexa tal. 3. Jag har skrivit this.getRe() istället för this.re. Det spelar ingen roll här, men i den andra implementationen är det praktiskt, som vi strax ska se. I den andra implementationen av samma klass valde jag istället att ha absolutbelopp och argument (vinkel) som attribut, och beräknar real- och imaginärdel vid behov. 3 public class Complex { private double r; private double phi; /** Skapa ett komplext tal x + iy */ public Complex(double x, double y) { this.r = Math.sqrt(x * x + y * y); this.phi = Math.atan(y / x); } /** Hämta realdelen */ public double getRe() { return r * Math.cos(phi); } /** Hämta imaginärdelen */ public double getIm() { return r * Math.sin(phi); } /** Hämta absolutbeloppet */ public double getAbs() { return r; } /** Hämta argumentet (vinkeln) */ public double getArg() { return phi; } /** Hämta ett nytt komplext tal som motsvarar summan av detta tal och other */ public Complex plus(Complex other) { return new Complex(this.getRe() + other.getRe(), this.getIm() + other.getIm()); } /* Hämta en sträng enligt mönstret 1.0+2.5i */ public String toString() { return this.getRe() + "+" + this.getIm() + "i"; } } Huvudprogrammet ovan fungerar likadant oavsett hur vi väljer våra attribut. Däremot blir det en liten, liten numerisk skillnad (2 · 10−15 ) när man kör det: 10.0+13.000000000000002i 16.401219466856727 Skillnaden beror på att värdet x + yi räknas om från rektangulär till polär form när vi skapar Complex-objekt i den andra lösningen, och därefter åter till rektangulär form i additionen. I summan hanteras c i polär form, och därefter räknas realdelen än en gång ut när toString anropar getIm. I den första lösningen var imaginärdelen ett attribut och någon konvertering var alltså inte nödvändig, men här ger den begränsade precisionen i double små avrundningsfel. Om man vill undvika sådana avrundningsfel för just imaginär- och realdelen, så är den första lösningen bättre här. Om man däremot ofta vill räkna ut absolutbelopp och argument är den andra något effektivare. Även om detta exempel är väldigt enkelt illustrerar det hur man ibland kan välja attribut beroende på vilka operationer som måste vara särskilt effektiva. Det viktiga med detta exempel är att illustrera att samma klass ofta kan implementeras på olika sätt, med olika attribut. Diskussionen om beräkningsprecision och effektivitet ovan är väldigt ytlig, och är mindre viktig här.