עבור לתוכן

לולאת for פשוטה ב-C

Featured Replies

פורסם

נתקלתי בהתנהגות לא צפויה שהצלחתי למקד אותה למקרה הפשוט הבא:


#include <stdio.h>
main(){
double c,d=2;
for (c=0;c<4;c+=1) {
printf("%lf\n",c);
if (c==d) printf("hit\n"); }
}

כשהקפיצות ב-C הן של 1, הקוד עובד כמו שצריך - מדפיס את הספרות מ-0 ועד 3 בקפיצות של אחד, ומדפיס hit אחרי 2.

לעומת זאת כשאני הופך את הקפיצות של c ל-0.1, התוכנה מדפיסה כצפוי את המספרים מ-0 עד 3.9 בקפיצות של 0.1, אבל התנאי עצמו לא מתקיים, כלומר התוכנה לא מדפיסה hit אחרי 2.

מישהו יודע מה הבעיה?

תודה מראש.

פורסם

אני לא מאה אחוז בטוח אבל יש כל מיני בעיות עם מספרים שלמים וfloat/דאבלים, ה"2" מיוצג ע"י 1.99999999... ולכן לא מופיע ה"hit" אתה צריך לעשות את התנאי שיהיה אם c>d-0.05 ו c<d+0.05 אז יש סיכוי שזה יעבוד.

פורסם
  • מחבר

הפכתי את התנאי לכך ההפרש בין C ל-D בערך מוחלט יהיה קטן מ0.0001 וזה באמת פתר את הבעיה. תודה.

אפשרי לתקן אותה בלי להשתמש בתנאי הנ"ל? ואם לא, איך אני יכול לדעת בדיוק מה המספר האמיתי שמאוחסן בערך, ובאיזו ספרה אחרי הנקודה הוא שונה מהמספר שהתכוונתי אליו? כי בפלט המספר מופיע תקין (2.000000).

תודה שוב.

פורסם

דווקא אם תגדיר double c = 2 אז הוא ייוצג ע"י 2 (כי זה מספר שלם, ולמחשב אין בעיה לייצג אותו). הבעיה היא 0.1, שהמחשב לא באמת יודע לייצג (כי המחשב משתמש בייצוג בינארי ולא דצימלי), אז הוא מקרב אותו, ולכן כשאתה מוסיף שוב ושוב 0.1 יש שגיאה קטנה שהולכת וגדלה עם כל תוספת, ככה שכשתוסיף אותו 20 פעם השגיאה תהיה מספיק גדולה כדי שהמספר לא יהיה שווה בדיוק 2.0. כמו ש-Moon-Mage אמר, צריך להימנע מלהשוות double/float בדיוק מהסיבה הזו.

כי בפלט המספר מופיע תקין (2.000000).

זה כי בפלט המחשב מעגל קצת את המספר.

פורסם
  • מחבר

תודה.

תוכל לעזור לי לפתור את הבעיה המקורית בה נתקלתי? אני לא רואה דרך להמנע מהשוואת שני דאבלים.

הבעיה המקורית שניסיתי לפתור היתה פתרון נומרי למשוואה פיזיקלית, שמחייבת אותי להשתמש בזמן t שרץ מ-0 עד t_final בצעדים קטנים של dt, נאמר 0.0001 (כל הגדלים הרלוונטיים בתוכנה מתקבלים ע"י המשתמש וחייבים להיות מסוג דאבל).

כמו כן, בכל זמן הריצה, מ-t=0 עד t=t_final, מדי פעם (לאו דווקא כל dt, נאמר כל X זמן שהוא גם דאבל) התוכנה צריכה לכתוב פלט כלשהו.

כלומר, בכל ריצה של הלולאה (t שמתקדם בצעדי dt), אני בודק אם הזמן t מתחלק ב-X ללא שארית, כדי לדעת אם להדפיס את הפלט.

אני לא רואה אפשרות להמנע מהשוואת שני דאבלים. אני יכול לבדוק שההפרש בינהם בערך מוחלט מאד קטן במקום ממש להשוות בינהם, אבל זה נראה לי פתח לבעיות נוספות, למשל משום שאיני יודע עד כמה הדאבל באמת לא מדויק (ספרות אחרי הנקודה). האם ישנה דרך יותר סטנדרטית לטפל בבעיה?

פורסם

כל עוד אתה יודע מה גודל הקפיצה פשוט תעשה את אותו הדבר עם התנאי שזה קטן מX+0.5D כאשר D הוא גודל הקפיצה וגדול מX-0.5D

פורסם
  • מחבר

תודה

פורסם

מה ש-Moon-Mage הציע עובד טוב במקרה הזה כי אתה יודע שהשגיאה לא תיהיה עד כדי כך גדולה ולא דרוש לך דיוק גדול יותר.

אם יהיה לך מצב שתצתרך דיוק מוחלט אז תגדיר משתנה int t_int שיהיה הזמן שלך חלקי dt, ככה שהוא שלם, ותשתמש בו להשוואות. משהו כזה:



// if my dt = 0.0001, then t_int = t*10000. t is the actual time in seconds.
const int dt_inv = 10000; // (1/dt)
int t_int;
for (t_int = 0; t_int < N; t_int++) {
double t = (double)t_int / dt_inv; // edit: i previously wrote here t where i meant t_int
// ... do some calculation using t ...
// you can check when t_int is exactly 3 seconds because it's an int.
if (t_int == 3 * dt_inv) ...;
}

פורסם

הדבר הנפוץ ביותר הוא להשתמש באפסילון שמתאים:


#define EPSILON 0.0001
//code...
if (abs(d-c) < EPSILON)
{
//do something
}

פורסם
  • מחבר

דבר ראשון תודה רבה לשניכם

אני באמת מנסה בשעה האחרונה למצוא את האפסילון המתאים :) קצת בעייתי כשאני לא יודע בדיוק מה השגיאה הנוצרת בדאבל, וגם ה-dt לא ידוע מראש, מוזן ע"י המשתמש.

ניסיתי בכל מקרה לשחק עם זה עבור מקרה ספציפי, אבל עדיין יש התנהגות לא צפויה... נמשיך לנסות, תודה שוב.

פורסם

קח בחשבון שאין פיתרון נכון לבעיית ההשוואה של מספרים שמיוצגים ע"י נקודה צפה (float, double)

אתה צריך לחשוב על פיתרון שנכון למקרה שלך. אם ה-dt נקבע ע"י המשתמש, תנסה לחשוב איך אתה מתייחס אליו ותנסה להדפיס את התוצאות

של ההשוואה (חיסור) ולהעריך את הקירוב.

ארכיון

דיון זה הועבר לארכיון ולא ניתן להוסיף בו תגובות חדשות.

דיונים חדשים