כמו שבוודאי ניתן לראות, השתמשת במשתנה מסוג double כדי לאחסן את סכום הכסף, ועם משתנה זה אתה מבצע מספר חישובים.
אני לא איכנס כאן לפרטי פרטים על אודות מימושו של הטיפוס double בשפת C. אני רק אזכיר כי מדובר במשתנה המיועד למספרים ממשיים "ארוכים" (בדיוק כפול), שמחולק ל-3 שדות, האחד מורה על הסימן (+/-), השני על המספר עצמו, והשלישי על החזקה שיש להכפיל בה את המספר (ומה שקובע את מיקום ה"נקודה הצפה"), וזאת כמובן ע"פ התקנים הסטנדרטים (IEEE).אוקי, אז מה זה קשור בעצם?
מה שקורה בתכנית שלך, זה חישובים, לא מסובכים במיוחד, המבוצעים על המשתנה money (?) מטיפוס double. בכל חישוב, אתה מוריד מ-money ערך מסויים שנגזר ע"פ חלוקת המטבעות בארצנו.
בגלל המספר הלא-קטן של חישובים שאתה מבצע על המשתנה, ובגלל העובדה שהחישובים מבוצעים בצורת שרשרת (החישוב האחרון מותנה גם בחישובים שלפניו), כלומר הפלט שלך לא נגזר מן המשתנה money המקורי (מכיוון שאתה משנה אותו בכל חישוב), אלא נגזר ממשתנה money שבוצעו עליו חישובים כאלו ואחרים, אתה מקבל, בדיוק האחרון שלך (שתי ספרות אחרי הנקודה במספור עשרוני), כביכול שגיאה.
אגב, אם תעקוב אחר המשתנה money עצמו, ותדפיס את ערכו יחד עם כל הדפסת ערכו של coins, תגלה כי ערכו של money "בסדר גמור", ולא תוכל להבין מהי השגיאה.
אני מניח שזה עדיין לא ברור, אז בוא נעבור לדוגמה קלה.
נניח שאנחנו עובדים במערכת שבנוייה לפי בסיס 10, ושהשדה ששומר את המספר עצמו ב-double שומר עד 4 ספרות.
אם נרצה לייצג את המספר 19000 (מספר בן 5 ספרות), נייצג אותו כך שהמספר עצמו הוא 1900 (שוב, מגבלה של 4 ספרות), והחזקה היא 1 (כלומר נכפול ב-10 בחזקת 1). 1900 כפול 10 יוצא לנו 19000, כפי שרצינו.
ומה יקרה אם נרצה לייצג את המספר 19001? בגלל מגבלת 4 הספרות, ניקח את הספרות המשמעותיות ביותר (מצד שמאל), כלומר 1900, ונכפול בחזקה של 10 בחזקת 1. מה קיבלנו? 19000!!! כלומר איבדנו דיוק.
זהו לא בדיוק הייצוג של double, אך זוהי דוגמה קרובה מאוד, שממחישה לך על אודות תופעת איבוד הדיוק. תופעה זו קרתה אצלך בתכנית, בעיקר בגלל המרות מרובות שהיו לך בתכנית (בעיקר עם int וכו').
אם כך, למה כשעוקבים אחרי money מתקבלת תוצאה הגיונית?
מכיוון שהפונקציה printf מעגלת את הספרה האחרונה במספר. למעשה, בדוגמה שנתת, ערכו של money בחישוב האחרון הינו 0.0399999999 וכן הלאה, וכידוע לך, לאחר הכפלה ב-100 (לפי 100 אגורות בשקל), אתה מקבל 3.999999 ולאחר המרה ל-int, אתה מקבל 3, ולא 4.
אתה תמיד צריך לזכור שהמחשב עובד בבסיס 2, ולא בבסיס 10, וזה מה שגורם לאיבוד הדיוק בתכנית שלך אשר משתמשת בחישובים פשוטים של כפולות ב-10 או ב-5 (אשר כביכול רק מזיזים את הנקודה, או מחלקים ב-2).
אז מה לעשות?
1) אתה יכול להשתמש ב-float. המימוש שלו יותר פשוט, ומלווה בפחות אפשרויות לשגיאות מסוג זה, למרות ש-double יותר מדוייק.
2) כדי לא לאבד דיוק (בחישובים קצת יותר מסובכים), יש להשתמש במשתנה המקורי בכל חישוב, ולא לפעול בצורת חישובי שרשרת שגורמים לאיבוד-דיוק.