JAVA ΕΝΟΤΗΤΑ 7 – FLOATING-POINT AND OPERATOR PRECEDENCE


 

ΕΙΣΑΓΩΓΗ

Στο σημερινό δωρεάν μάθημα Java θα αναλύσουμε τα floating-point data types, τι πρέπει να προσέξουμε όταν κάνουμε αριθμητικές πράξεις με floating-point αριθμούς, 

αλλά κυρίως πως μπορούμε εμείς να ορίσουμε την σειρά εκτέλεσης των αριθμητικών πράξεων για να λάβουμε το σωστό αποτέλεσμα.

FLOATING-POINT TYPES

Εκτός από τους ακέραιους αριθμούς, μπορούμε να ορίσουμε μια Java μεταβλητή να δέχεται και δεκαδικούς αριθμούς. Ο τρόπος να αναθέσουμε δεκαδικούς αριθμούς σε μια μεταβλητή είναι να την ορίσουμε σαν float ή σαν double. Η μόνη διαφορά ανάμεσα σε αυτά τα δύο data types είναι το εύρος των αριθμών που μπορούν να καλύψουν αφού ένας float αριθμός περιγράφεται από 32 bits ενώ ένας double από 64 bits.

Στο προηγούμενο δωρεάν μάθημα Java είχαμε αναφέρει ότι η default συμπεριφορά της Java με τους integral αριθμούς είναι να προσπαθήσει να τους μετατρέψει όλους σε int εκτός και αν βάλουμε το L στο τέλος του αριθμού και δηλώσουμε το data type να είναι long.

Στους floating-point αριθμούς η default συμπεριφορά είναι το double data type. Αν θέλουμε να ορίσουμε έναν αριθμό να είναι float, εκτός από τη δήλωση float data type μπροστά από το όνομα της μεταβλητής, θα πρέπει να προσθέσουμε και ένα F στο τέλος του αριθμού για να αναγνωριστεί σαν float.

Γενικότερα το double data type είναι εκείνο που χρησιμοποιούμε συχνότερα αφού έτσι και αλλιώς όλα τα μαθηματικά libraries της Java χρησιμοποιούν και επιστρέφουν double αριθμούς. Ας δούμε ένα απλό παράδειγμα πριν προχωρήσουμε σε κάποια συγκεκριμένα σημεία όταν προγραμματίζουμε με double μεταβλητές που πρέπει να προσέξουμε ιδιαίτερα.

App.java

package com.example;

public class App {

    public static void main(String[] args) {
        double original_price = 93.00;
        double discount = original_price * 0.23;
        double sale_price = original_price - discount;
        System.out.println("The sale price is " + sale_price);
    }
}

Output

The sale price is 71.61


Το παραπάνω πρόγραμμα το έχουμε ξαναδεί όταν είχαμε αναλύσει τα integral data types. Τώρα όμως, με την χρήση του double, κερδίζουμε σε ακρίβεια όσο αφορά τα ψηφία μετά την υποδιαστολή, κάτι που δεν είχαμε με τους ακέραιους. Όπως θυμάστε λοιπόν από την εξήγηση που δώσαμε στην προηγούμενη ενότητα, ο συγκεκριμένος κώδικας ορίζει την αρχική τιμή ενός προϊόντος. Αφού υπολογίσουμε την έκπτωση, την αφαιρούμε από την αρχική τιμή για δείχνουμε στο χρήστη την τελική τιμή του προϊόντος.


 

Στο συγκεκριμένο πρόγραμμα δεν έχει τίποτα το ιδιαίτερο να προσέξουμε γιατί οι αριθμητικές πράξεις ήταν απλές. Ας δούμε τώρα ένα άλλο πρόγραμμα στο οποίο, αν και πάλι η λογική είναι απλή, μπορεί να λάβετε λάθος αποτέλεσμα αν δεν γνωρίζετε πως ακριβώς λειτουργεί η Java. Το συγκεκριμένο πρόγραμμα ορίζει τα αποτελέσματα από τρία διαφορετικά τεστ και βρίσκει το μέσο όρο.

App.java

package com.example;

public class App {

    public static void main(String[] args) {
        int test1 = 89;
        int test2 = 88;
        int test3 = 95;

        double average = (test1 + test2 + test3) / 3;
        System.out.println("The average score is " + average);
    }
}

Output

The average score is 90.0


Το πρόγραμμα μας λειτουργεί κανονικά χωρίς η Java να παραπονιέται για κάποιο συντακτικό σφάλμα, όμως το αποτέλεσμα της διαίρεσης είναι λάθος. Το σωστό αποτέλεσμα είναι 90.666 και όχι 90.0. Τι έχει συμβεί εδώ? Για να καταλάβετε απόλυτα το πώς λάβαμε το συγκεκριμένο λάθος αποτέλεσμα θα πρέπει να αναλύσουμε την γραμμή που υπολογίζει το μέσο όρο βήμα-προς-βήμα.

(test1 + test2 + test3) / 3;

Για να βρούμε το μέσο όρο μιας ομάδα αριθμών, θα πρέπει πρώτα να τους προσθέσουμε. Σε αυτό το σημείο του κώδικα κάνουμε ακριβώς αυτό – προσθέτουμε τα τρία διαγωνίσματα. Τώρα η επόμενη ερώτηση σας θα είναι: γιατί ακριβώς χρειάζεται η παρένθεση?

Στη Java, οι αριθμητικές πράξεις εκτελούνται με σειρά προτεραιότητας. Multiplication ( * ), division ( / )  και remainder ( % ) εκτελούνται πρώτα. Εάν έχουμε πολλαπλούς ίδιους arithmetic operators, για παράδειγμα multiplication και division τότε οι πράξεις εκτελούνται από τα αριστερά στα δεξιά. Addition ( + ) και subtraction ( - ) operators εκτελούνται τελευταίοι. Ξανά, εάν έχουμε πολλαπλούς addition arithmetic operators και subtraction arithmetic operators στην ίδια δήλωση,  τότε οι πράξεις εκτελούνται από τα αριστερά προς τα δεξιά.

Αν τώρα θέλουμε να επηρεάσουμε εμείς την σειρά εκτέλεσης των πράξεων και όχι να αφήσουμε τον αυτόματο τρόπο της Java να αποφασίσει με βάση την προτεραιότητα των arithmetic operators, τότε πρέπει να βάλουμε μέσα σε παρενθέσεις ( ( ) ) εκείνες τις πράξεις που θέλουμε να εκτελεστούν πρώτες. Οι παρενθέσεις κατέχουν την κορυφή (έχουν την ανώτερη προτεραιότητα) ανάμεσα σε όλους τους arithmetic operators.

Γυρνώντας λοιπόν πίσω στο απλό παράδειγμα μας, βλέπουμε ότι υπάρχει η εκτέλεση μιας πρόσθεσης τριών μεταβλητών και μια διαίρεσης. Αν δεν τοποθετήσουμε τις τρεις μεταβλητές μέσα σε παρένθεση, τότε η Java θα λειτουργήσει με την σειρά προτεραιότητας των συμβόλων και θα πραγματοποιήσει την διαίρεση σαν πρώτο βήμα. Η διαίρεση, όπως φαντάζεστε θα είναι λάθος γιατί θα διαιρεθεί ο αριθμός τρία μόνο με την τιμή 95 της μεταβλητής test3. Μετά την διαίρεση θα ακολουθήσει η πρόσθεση και των υπόλοιπων μεταβλητών. Οπότε έχοντας τις τρεις μεταβλητές μέσα σε παρένθεση, επιβάλλουμε στη Java να εκτελέσει την πρόσθεση των μεταβλητών σαν πρώτο βήμα και μετά την διαίρεση.

Σε αυτό το σημείο η Java έχει προσθέσει τις μεταβλητές και το αποτέλεσμα είναι o int αριθμός 272. Αυτός ο αριθμός διαιρείται με τον int αριθμό 3. Η διαίρεση ανάμεσα σε δύο int αριθμούς δεν μπορεί να μας δώσει το ακριβές αποτέλεσμα. Οπότε το αποτέλεσμα είναι ο int αριθμός 90. 

double average =

Το αποτέλεσμα της πρόσθεσης θα ανατεθεί σαν τιμή στην double μεταβλητή average. Όμως ένας int αριθμός (32-bits) είναι μικρότερος από έναν double (64-bits). Σε αυτό λοιπόν το σημείο η Java πραγματοποιεί αυτόματο casting μετατρέποντας τον int αριθμό 90 σε double αριθμό 90.0 για να συμφωνούν τα data types. Αφού γίνει το casting, πραγματοποιείται και και η ανάθεση του αριθμού στη μεταβλητή average.

Τώρα που καταλάβαμε τι ακριβώς συμβαίνει και γιατί πήραμε λάθος αποτέλεσμα, η επόμενη ερώτηση είναι: πως μπορούμε να διορθώσουμε τον κώδικα μας?

Απάντηση είναι εύκολη, γιατί γνωρίζοντας την θεωρία (που μόλις αναλύσαμε παραπάνω) καταλάβαμε ότι πρέπει να επέμβουμε στον κώδικα πριν πραγματοποιηθεί η διαίρεση. Εδώ θα μπορούσατε να προσφέρετε δύο πιθανές λύσεις.

Η πρώτη λύση είναι να διαιρέσετε με τον double αριθμό 3.0 και όχι με τον int 3. Βλέποντας η Java ότι πρόκειται να εκτελέσει έναν int με έναν double αριθμό, αυτόματα θα μετατρέψει τον int σε double και μετά θα πραγματοποιήσει την διαίρεση.

 double average = (test1 + test2 + test3) / 3.0;

Χρησιμοποιώντας την ίδια ακριβώς λογική, θα μπορούσαμε να κάνουμε casting σε double το αποτέλεσμα της πρόσθεσης. Πριν πραγματοποιηθεί η διαίρεση η Java θα κάνει casting στον αριθμό 3 μετατρέποντας τον σε 3.0.

double average = (double) (test1 + test2 + test3) / 3;

Ας επιλέξουμε λοιπόν την δεύτερη προσέγγιση σαν λύση και σας την εφαρμόσουμε στον κώδικα μας για να δούμε αν τώρα παίρνουμε το επιθυμητό αποτέλεσμα. Επίσης ας προσθέσουμε κα το Double.MAX_VALUE όπως και το Double.ΜΙΝ_VALUE για να δούμε αντίστοιχα τον μεγαλύτερο και μικρότερο αριθμό που μπορεί να φτάσει ένας double αριθμός.

App.java

package com.example;

public class App {

    public static void main(String[] args) {
        int test1 = 89;
        int test2 = 88;
        int test3 = 95;

        double average = (double) (test1 + test2 + test3) / 3;
        System.out.println("The average score is " + average);

        double doubleMax = Double.MAX_VALUE;
        double doubleMin = Double.MIN_VALUE;
        System.out.println("The highest double value is " + doubleMax);
        System.out.println("The lowest double value is " + doubleMin);
    }
}

Output

The average score is 90.66666666666667

The highest double value is 1.7976931348623157E308

The lowest double value is 4.9E-324


Πριν κλείσουμε το σημερινό δωρεάν μάθημα Java, ας δούμε ένα από τα πιο γνωστά παραδείγματα που αποδεικνύει την χρησιμότητα των floating-point μεταβλητών που δεν είναι άλλο από την μετατροπή από Celcius βαθμούς σε Fahrenheit.

App.java

package com.example;

public class App {

    public static void main(String[] args) {
        final int BASE = 32;
        final double CONVERSION_FACTOR = 9.0 / 5.0;
        double fahrenheitTemp;
        int celsiusTemp = 24;
        fahrenheitTemp = celsiusTemp * CONVERSION_FACTOR + BASE;
        System.out.println("Celsius Temperature: " + celsiusTemp);
        System.out.println("Fahrenheit Equivalent: " + fahrenheitTemp);
    }
}

Output

Celsius Temperature: 24

Fahrenheit Equivalent: 75.2

Όπως παρατηρήσατε στη σημερινή ενότητα, η Java εκτελεί widening conversions αυτόματα γιατί νιώθει ασφάλεια ότι δεν θα χάσει σε ακρίβεια το τελικό αποτέλεσμα. Ο παρακάτω πίνακας, ίσως σας βοηθήσει έτσι ώστε να έχετε μια καλύτερη εικόνα κάτω από ποιες συνθήκες γίνεται αυτή η αυτόματη μετατροπή.



Μην ξεχάσετε να κάνετε ένα μικρό donation έτσι ώστε αυτό το blog να μεγαλώσει ακόμα πιο πολύ και να έχει περισσότερες δυνατότητες στην online παράδοση δωρεάν μαθημάτων.


full-width

Post a Comment

0 Comments