bird Δημοσ. 24 Νοεμβρίου 2013 Δημοσ. 24 Νοεμβρίου 2013 @bird η συνάρτηση round αν είναι α.β με β>=5 κάνει κανονικά τη δουλειά της. Το θέμα είναι πως θα αποκλίσω το γεγονός ένας αριθμός 1.49999 δεν αποθηκευτεί στον υπολογιστή κάπως 1.50000001... και ένας αριθμός που είναι 1.50001 δεν αποθηκετεί 1.4999999. Με το σφάλμα που βάζω εγώ μπορεί ένας αριθμός να είναι όντως 1.480000 και εγώ να τον κάνω στρογγυλοποίηση προς τα πάνω προσθέτοντας το σφάλμα που βάζω. Το ίδιο μπορεί να συμβεί και με σφάλμα 0.01 και 0.00001 Σίγουρα αυτό είναι ένα θέμα και για αυτό σου είπα να βάλεις έναν πολύ μικρό αριθμό... Από την άλλη μπορείς να κάνεις αυτό που σου είπα στην αρχή, δλδ να πολλαπλασιάσεις με μία δεκάδα παραπάνω και μετα να διαιρέσεις με το 10. Εκεί την κάνει σωστά από μόνο του την στρογγυλοποίηση... Ωστόσο αν θές να κάνεις τη λύση με την προσθεση ενος μικρου αριθμου, λογικά υπάρχει κάπου το σφάλμα που μπορεί να υπάρχει στην αποθηκευση ενος αριθμού και ισως αν ψαξεις να το βρεις ώστε να προσθέτεις αυτό για να είσαι καλυμένος.
migf1 Δημοσ. 24 Νοεμβρίου 2013 Δημοσ. 24 Νοεμβρίου 2013 Δεν έχω διαβάσει τους κώδικες του νήματος, αλλά μήπως το πρόβλημα λύνεται με τη χρήση είτε της ceil() είτε της floor(), ανάλογα με το αν μιλάτε για αρνητικές ή για θετικές τιμές (αλλά και οι δυο συναρτήσεις προϋποθέτουν C99 compiler).
zarzonis Δημοσ. 24 Νοεμβρίου 2013 Μέλος Δημοσ. 24 Νοεμβρίου 2013 Σίγουρα αυτό είναι ένα θέμα και για αυτό σου είπα να βάλεις έναν πολύ μικρό αριθμό... Από την άλλη μπορείς να κάνεις αυτό που σου είπα στην αρχή, δλδ να πολλαπλασιάσεις με μία δεκάδα παραπάνω και μετα να διαιρέσεις με το 10. Εκεί την κάνει σωστά από μόνο του την στρογγυλοποίηση... Ωστόσο αν θές να κάνεις τη λύση με την προσθεση ενος μικρου αριθμου, λογικά υπάρχει κάπου το σφάλμα που μπορεί να υπάρχει στην αποθηκευση ενος αριθμού και ισως αν ψαξεις να το βρεις ώστε να προσθέτεις αυτό για να είσαι καλυμένος. Μπορείς να μου εξηγήσεις λίγο αυτό με την παραπάνω δεκάδα που λες; Πες ότι έχουμε τον αριθμό 12.4712233 ευρώ. Εγώ πολλαπλασιάζω με 100 και γίνεται 1247.2233 λεπτά. Μετά κάνω round αυτό. και γίνεται 1247 λεπτά. Αν πολλαπλασιάσω με 1000 θα γίνει 12472.233 και μετά διαιρώντας με 10 θα βγάλει 1247. Θα κόψει το δεκαδικό δηλαδή. Αν είναι 2.1749999 ευρώ, θα πολλαπλασιάσω με 1000, θα γίνει 2174,999. Θα διαιρέσω μετά με 10 και θα γίνει 217. Εγώ όμως θέλω αυτό να γίνει 218 γιατί το .4999999 από πίσω, προέκυψε από λάθος αποθήκευση από το .5000000 Δεν έχω διαβάσει τους κώδικες του νήματος, αλλά μήπως το πρόβλημα λύνεται με τη χρήση είτε της ceil() είτε της floor(), ανάλογα με το αν μιλάτε για αρνητικές ή για θετικές τιμές (αλλά και οι δυο συναρτήσεις προϋποθέτουν C99 compiler). Σε συνδιασμό αυτών των δύο μπορεί και να λυθεί, αλλά λέει στην εκφώνηση να χρησιμοποιήσουμε ή την round ή την lround ή δικό μας τρόπο.
bird Δημοσ. 24 Νοεμβρίου 2013 Δημοσ. 24 Νοεμβρίου 2013 Αυτό που σου είπα με τη δεκάδα, δούλεψε για το παράδειγμα που μας έδωσες κι εξηγώ.... όταν κάνεις test1 = (double)50/60*2.25; ==> test1 = 2.17500000000 //(όταν το τυπώνω αυτό βγάζει) // μετά στο test *= 100; ==> test1 = 217.499999999999970 //(εδώ χαλάει το πράγμα) οπότε μετά η round σου το κάνει 217 Αυτό που έκανα και μου δούλεψε ήταν: test1 = (double)50/60*2.25; ==> test1 = 2.17500000000 //(Το ίδιο μέχρι εδώ) // μετά στο test *= 1000; ==> test1 = 2175.0000000000000 //(Κρατάει σωστά το δεκαδικό κομάτι χωρίς να το χαλάσει) //και μετά κάνω ενα test1 /= 10; ==> test1 = 217.50000000000 //(ξανά σωστά (μη ρωτάς γιατί δεν ξέρω) οπότε μετα η round δουλευει σωστά... Εν τω μεταξύ αν κάνει σταδιακά επί δέκα κάθε φορά δεν το χαλάει (τουλάχιστον σε μένα) δηλαδή: test1 = (double)58/60*2.25; test1 *= 10; test1 *= 10; rounded = round(test1); βγάζει πάλι σωστό αποτέλεσμα (τώρα το είδα αυτό!) Στην ουσία τον χαλάει ο πολλαπλασιασμός με το 100
zarzonis Δημοσ. 24 Νοεμβρίου 2013 Μέλος Δημοσ. 24 Νοεμβρίου 2013 Αν είναι δυνατόν δηλαδή. Όντως έτσι όπως λες δουλεύει. Θα βάλω και άλλους αριθμούς και θα τους τυπώσω έτσι να δω τί παίζει. Τί προβλήματα είναι αυτά; Όπως και να έχει, σε ευχαριστώ πολύ για την βοήθειά σου. Θα σε ενημερώσω αν βρω άκρη. EDIT: Προς το παρών έβαλα σαν σφάλμα το 0.01 και δείχνει να δουλεύει χωρίς πρόβλημα. Μέχρι να αποδειχθεί το αντίθετο πάντα.
migf1 Δημοσ. 24 Νοεμβρίου 2013 Δημοσ. 24 Νοεμβρίου 2013 Οκ, μόλις διάβασα το νήμα προσεκτικά. Είναι το infamous θέμα στρογγυλοποίησης με τους υπολογιστές. Αυτό με την αποδεκτή απόκλιση που έκανες φίλε zarzonis είναι μια χαρά για τα δεδομένα της άσκησης σου (μέχρι 2 δεκαδικά δεν είπες πως σε ενδιαφέρει Φτιάξε ένα macro ή μια δικιά σου σου συνάρτηση round... #define DELTA 0.01 // και με 0.1 δεν νομίζω να έχεις πρόβλημα αφού παίζεις με κέρματα #define ROUND( d ) ( (d) > 0.0 ? round( (d)+DELTA ) : round( (d)-DELTA ) ) και λογικά θα είσαι... τζετ
zarzonis Δημοσ. 24 Νοεμβρίου 2013 Μέλος Δημοσ. 24 Νοεμβρίου 2013 Μια τελευταία ερώτηση. Επειδή αυτό με την απόκλιση που έβαλα δουλεύει και μάλλον αυτό θα αφήσω, πώς ακριβώς θα το δικαιολογήσω; Δηλαδή τί να γράψω σαν σχόλιο από δίπλα;
migf1 Δημοσ. 24 Νοεμβρίου 2013 Δημοσ. 24 Νοεμβρίου 2013 Γράψε ένα link που να δείχνει π.χ. εδώ: http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
migf1 Δημοσ. 24 Νοεμβρίου 2013 Δημοσ. 24 Νοεμβρίου 2013 Παρακαλώ, αλλά σου έδωσα το outdated link (αυτό είχα bookmarked ). Το κείμενο όμως έχει links προς τη νέα του σελίδα.
zarzonis Δημοσ. 24 Νοεμβρίου 2013 Μέλος Δημοσ. 24 Νοεμβρίου 2013 Μιλάμε το άρθρο τα σπάει. Απίστευτα κατατοπιστικό και σίγουρα έμαθα πράγματα που τα προηγούμενα χρόνια τα είχα αγνοήσει. Κάθομαι το διαβάζω σιγά σιγά όλο και φυσικά έγινε bookmarked.
defacer Δημοσ. 25 Νοεμβρίου 2013 Δημοσ. 25 Νοεμβρίου 2013 EDIT: bird με πρόλαβες. Δεν ξέρω αν βοηθάει, αλλά τα νούμερα που υπολογίζω, έχουν να κάνουν με υπολογισμό κόστους σε cents. Δηλαδή το 217 είναι 217 cents που προέκυψε από ένα συγκεκριμένο υπολογισμό. Οπότε θέλω να γίνεται 127 cents ή 128 cents ανάλογα. Γενικά αν κάνεις υπολογισμούς για λεφτά με IEEE floating point θα έχεις πρόβλημα όσο κι αν χτυπιέσαι. Γι' αυτό το λόγο πάντα σ' αυτές τις περιπτώσεις ο κόσμος δουλεύει με βιβλιοθήκες που παρέχουν είτε decimal representation (δηλαδή τις εγγυήσεις περι σημαντικών ψηφίων τις παρέχουν στο δεκαδικό, όχι στο δυαδικό) είτε arbitrary precision ("άπειρη" ακρίβεια). Αν δε μπορείς να κάνεις τίποτα από τα δύο, η άλλη λύση είναι να δουλέυεις μόνο με ακέραιους, π.χ. σ' αυτή την περίπτωση να το πας ως printf("%d.%d\n", 10000 * 58 * 225 / 60 / 10000, 10000 * 58 * 225 / 60 % 10000); όπου η σημασία του 10000 είναι ότι θέλεις 4 σημαντικά ψηφία (αυτό πρέπει να το ξέρεις από πριν). Πάντως κι αυτή η προσέγγιση έχει μια σειρά από άλλα πιθανά προβλήματα οπότε και πάλι δεν είναι πανάκεια. Στην προκειμένη νομίζω πως απλά η πρακτική λύση είναι να βρεις κάποιο τρικ όπως αυτά που αναφέρθηκαν ήδη που να σε καλύπτει για το συγκεκριμένο πρόβλημα, δε χρειάζεται να καείς για τη γενική περίπτωση. To round(a+0.5) ειναι παντα ίδιο με round(a) , εκτος ότι στρογγυλοποιει προς τα πανω αν α=x.5. Nitpick: όχι πάντα όταν μιλάμε για IEEE floating point, γιατί το a + 0.5 μπορεί μην είναι αναπαραστήσιμο σαν float. Στα διαστήματα όπου ο μικρότερος αριθμός που είναι αναπαραστήσιμος σαν float και ταυτόχρονα μεγαλύτερος από το a είναι το a + 1 (δηλαδή μιλώντας για IEEE 754 single precision aka "float" στις τιμές ανάμεσα στο 2^23 και 2^24) τότε εαν το a είναι περιττός λόγω του round half to even γίνεται αυτό: int main(void) { float f = (1 << 23) + 1; // smallest odd integer > 2^23 printf("%f -> %f\n", f, round(f + .5)); return 0; } Output: 8388609.000000 -> 8388610.000000 Παρεμπιπτόντως όταν μιλάμε για υπολογιστές και προγράμματα τότε δεν έχει απαραίτητα σημασία του πώς μάθαμε εμείς τη στρογγυλοποίηση στο σχολείο. Το round half to even (συναντάται και ως banker's rounding) είναι επίσης πάρα μα πάρα πολύ διαδεδομένο γιατί έχει καλύτερα στατιστικά χαρακτηριστικά.
zarzonis Δημοσ. 25 Νοεμβρίου 2013 Μέλος Δημοσ. 25 Νοεμβρίου 2013 Γενικά αν κάνεις υπολογισμούς για λεφτά με IEEE floating point θα έχεις πρόβλημα όσο κι αν χτυπιέσαι. Γι' αυτό το λόγο πάντα σ' αυτές τις περιπτώσεις ο κόσμος δουλεύει με βιβλιοθήκες που παρέχουν είτε decimal representation (δηλαδή τις εγγυήσεις περι σημαντικών ψηφίων τις παρέχουν στο δεκαδικό, όχι στο δυαδικό) είτε arbitrary precision ("άπειρη" ακρίβεια). Αν δε μπορείς να κάνεις τίποτα από τα δύο, η άλλη λύση είναι να δουλέυεις μόνο με ακέραιους, π.χ. σ' αυτή την περίπτωση να το πας ως printf("%d.%d\n", 10000 * 58 * 225 / 60 / 10000, 10000 * 58 * 225 / 60 % 10000); όπου η σημασία του 10000 είναι ότι θέλεις 4 σημαντικά ψηφία (αυτό πρέπει να το ξέρεις από πριν). Πάντως κι αυτή η προσέγγιση έχει μια σειρά από άλλα πιθανά προβλήματα οπότε και πάλι δεν είναι πανάκεια. Στην προκειμένη νομίζω πως απλά η πρακτική λύση είναι να βρεις κάποιο τρικ όπως αυτά που αναφέρθηκαν ήδη που να σε καλύπτει για το συγκεκριμένο πρόβλημα, δε χρειάζεται να καείς για τη γενική περίπτωση. Nitpick: όχι πάντα όταν μιλάμε για IEEE floating point, γιατί το a + 0.5 μπορεί μην είναι αναπαραστήσιμο σαν float. Στα διαστήματα όπου ο μικρότερος αριθμός που είναι αναπαραστήσιμος σαν float και ταυτόχρονα μεγαλύτερος από το a είναι το a + 1 (δηλαδή μιλώντας για IEEE 754 single precision aka "float" στις τιμές ανάμεσα στο 2^23 και 2^24) τότε εαν το a είναι περιττός λόγω του round half to even γίνεται αυτό: int main(void) { float f = (1 << 23) + 1; // smallest odd integer > 2^23 printf("%f -> %f\n", f, round(f + .5)); return 0; } Output: 8388609.000000 -> 8388610.000000 Παρεμπιπτόντως όταν μιλάμε για υπολογιστές και προγράμματα τότε δεν έχει απαραίτητα σημασία του πώς μάθαμε εμείς τη στρογγυλοποίηση στο σχολείο. Το round half to even (συναντάται και ως banker's rounding) είναι επίσης πάρα μα πάρα πολύ διαδεδομένο γιατί έχει καλύτερα στατιστικά χαρακτηριστικά. Αυτά να πω την αλήθεια δεν ήξερα. Αλλά και να τα ήξερα, δεν θα μπορούσα να τα χρησιμοποιήσω με τίποτα για την συγκεκριμένη άσκηση. Πρώτο έτος είμαι. Δεν έχουμε μάθει ούτε τα βασικά καλά καλά. Ούτε δικιά μου συνάρτηση δεν μπορούσα να χρησιμοποιήσω. Όπως και να έχει, με το τρίκ αυτό που έκανα δούλεψε και την έστειλα την άσκηση. Αν ήταν δικό μου πρόγραμμα θα το είχα ψάξει πολύ περισσότερο, αλλά δυστυχώς είναι για τη σχολή.
Προτεινόμενες αναρτήσεις
Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε
Πρέπει να είστε μέλος για να αφήσετε σχόλιο
Δημιουργία λογαριασμού
Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!
Δημιουργία νέου λογαριασμούΣύνδεση
Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.
Συνδεθείτε τώρα