שפת C למתחילים: ביטים, חלק ב'

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

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

טבלת אמת לשני תנאים שחייבים להתקיים
ושיהיו מפיצריה נורמלית, לא מפיצה-סלמונלה-הכל-בשקל

ועכשיו את אותה הטבלה רק בצורה פורמלית יותר, עם 1 ו-0 במקום "כן" ו"לא", בהתאמה:

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

טור Y בטבלה הוא למעשה "פונקציה לוגית" של A ו-B: מוגדר לו ערך עבור כל צירוף אפשרי של ערכי A ושל ערכי B, וכולם לוגיים/בינאריים: אמת ושקר, אחד ואפס או איך שתרצו לקרוא להם. ספציפית במקרה זה, מדובר באחת הפונקציות הלוגיות הבסיסיות, שנקראת AND. בשפת C אנחנו מסמנים אופרטור של AND לוגי בצמד התווים "&&". הנה קטע קוד להמחשה…

boolean imComing, closeBy, pizzaAvailable;

// ...

imComing = closeBy && pizzaAvailable;

הגדרנו שלושה משתנים בוליאניים (שמקבלים ערכי true או false, אם כי מאחורי הקלעים מדובר בבייטים רגילים לגמרי), וחישוב אחד שייתן תוצאות זהות לאלו שבטבלת האמת – כמובן בהתאם לערכים שניתן למשתנים closeBy ו-pizzaAvailable במהלך הרצת התוכנית.

האופרטור הלוגי הבסיסי השני נקרא OR, וב-C הוא מסומן בשני קווים אנכיים – "||" (להקלדה: Shift+המקש שנמצא בדרך כלל מעל ה-Enter, משותף עם "\"). הפעולה של || מזכירה מאד את השימוש היומיומי שלנו במילה "או": מספיק שאחד מהתנאים יתקיים, והתוצאה תהיה true. אם שני התנאים יתקיימו זה גם סבבה, אבל לא הכרחי. לדוגמה: אם תדליק את המנורה או תפתח את התריס יהיה אור בחדר (אם שתי הפעולות יתבצעו, גם אז התוצאה תהיה אור בחדר). הנה טבלת האמת של OR:

טבלת אמת לפונקציה הלוגית OR
הפונקציה הלוגית OR

האופרטור הלוגי השלישי הוא NOT, שמסומן בסימן קריאה ("!"). את הסימן הזה כותבים לפני משתנה או ביטוי, והוא הופך אותו מ-true ל-false או להיפך.

הזכרנו בעבר שבשפת C, המספר אפס הוא שווה ערך ל-false ואילו המספר אחד שווה ערך ל-true. זה לא היה מדויק במאה אחוזים. כשמתייחסים ל-true כמספר, למשל בביטוי byte a = true, הוא אמנם הופך ל-1. אך בכיוון ההפוך, כשמתייחסים למספר כאל boolean כמו בביטוי boolean b = 148, כל ערך שאינו 0 ייחשב true, ואך ורק 0 ייחשב false.

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

byte b;

b = true;       // b is TRUE (1)
b = b + 1;      // b is 2 (TRUE)
b = !b;         // NOT operation: b is FALSE
b = !!b;        // NOT NOT: b is FALSE
b = b && true; // FALSE AND TRUE => FALSE
b = b || true;  // ?

b = 5;             // Non-zero => TRUE
b = !(b - 5);      // ?
b = (b - 2) || !b; // ?
b = 1 - b;         // ?
b = 1 - b;         // ?
b = !((178 && b) || !(true && false)); // ?!?!

אם עשיתם את התרגיל הזה עד הסוף, והוא באמת לא קשה כמו שאולי נדמה במבט ראשון, אמורה להיות לכם כעת תחושה של המשחק בשפת C בין "אמת" ו"שקר" לבין 0 ולא-0. הגמישות הזו שימושית מאד, גם לחישובים וגם לכתיבת קוד קצר ונקי. סתם לדוגמה, האופרטור "==" (שני סימני "שווה" ברצף) משווה בין מה שמימין לו למה שמשמאל, ומחזיר לקוד true אם הם שווים או false אם הם שונים. כשנרצה לבדוק אם byte כלשהו שווה לאפס, לא צריך לכתוב

if (a == 0) /* do something */ ;

מספיק לכתוב

if (!a) /* do something */ ;

פעולות על ביטים

אולי שמתם לב שקודם, בכל פעם שהצגתי אופרטור לוגי נוסף, הדגשתי את המילה לוגי. זה לא היה רק בשביל היופי – הסיבה היא שיש אופרטורים נפרדים שעושים פעולות דומות, אבל לא על true או false אלא על ביטים ממש. פעולות כאלה נקראות Bitwise, וכאן נכנס לתמונה הנושא של הייצוג הבינארי שעליו דיברנו בפוסט הקודם.

המספר העשרוני 9 נראה, בבסיס בינארי, ככה:  1001. המספר 5 הוא 0101 (לצורך הפשטות השתמשתי בארבעה ביטים בלבד). מה נקבל אם נעשה פעולת AND על הביטים האלה, כל זוג בנפרד?

Bitwise AND
פעולת AND על ביטים במספרים

האפסים והאחדות שבתמונה הם כמעט בדיוק טבלת האמת שהבאתי בהתחלה, מסובבת בתשעים מעלות. כל זוג ביטים אנכי עובר חישוב AND, והתוצאה "מצטברת" למטה למספר חדש – ממש כמו פעולות החשבון שעשינו בבית הספר היסודי. התוצאה במקרה זה היא, כמובן, המספר 1 – רק שימו לב שהאופרטור ל-Bitwise AND הוא "&" יחיד ולא "&&" לוגי.

מה ההבדל? ניקח את המספרים 3 (בבינארי 0011) ו-12 (בבינארי 1100). שניהם שונים מאפס כך ששניהם בעצם true, ואם נכתוב 12 && 3 נקבל true או 1. לעומת זאת, אם נכתוב 12 & 3, יתבצע חישוב על כל זוג ביטים בנפרד ונקבל את התוצאה הסופית 0!

יש לנו, איך לא, גם Bitwise OR – פעולת OR לביטים, שהאופרטור שלה הוא "|". כמה זה 5 | 9?  וכמה זה 12 | 3? אם הגעתם לתשובות 13 ו-15, מזל טוב – קלטתם את העניין!

הפעולה הבאה בתור היא Bitwise NOT. אותה מסמנים באמצעות האופרטור "~" שמופיע לפני המספר, המשתנה או הביטוי. לדוגמה, אם נזכור ש-5 זה 0101 בינארי, אז ~5 זה 1010 – כלומר 10 עשרוני. אבל אם תנסו את זה בארדואינו, עליכם לזכור שבכל בייט יש 8 ביטים, לא ארבעה, ולכן התוצאה תהיה דווקא 250!

ויש לנו גם בונוס, פעולת Bitwise XOR שאין לה מקבילה לוגית ב-C. מה זה XOR? קיצור של eXclusive OR, כלומר "או חזק". נניח שיהודי שומר מצוות אדוק אומר "אם יהיו פיצות או יהיו המבורגרים אני אבוא". מה יקרה אם יהיו גם פיצות וגם המבורגרים? בשר וחלב ביחד, הוא לא יגיע… או במילים אחרות, רק אם שני ביטים שונים זה מזה, התוצאה של XOR תהיה 1. אם הם זהים זה לזה, ולא משנה אם שניהם 1 או 0, התוצאה של XOR תהיה 0. את פעולת XOR אנחנו מבצעים בשפת C בעזרת האופרטור "^".

נסכם את כל מה שלמדנו בינתיים בטבלה אחת:

אופרטורים לוגיים ו-Bitwise
עם הטבלה הזו לעולם לא תתבלבלו!

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

להרשמה
הודע לי על
9 Comments
מהכי חדשה
מהכי ישנה לפי הצבעות
Inline Feedbacks
הראה את כל התגובות

מעולה !!
ההסבר הכי ברור שיכול להיות.
תודה רבה 🙂

תודה.
בניגוד לקוראיך האחרים, אני לחלוטין לומד ממך מאפס, דברים שתיכנתתי מתוך התבוננות
או שפשוט כתבתי בצורה לא אלגנטית. זו אולי הסיבה לכך שיש שלב שבו פתאום אני מאבד אותך, והוא השלב שבו אתה הולך מ:
if (a == 0) /* do something */ ;
ל:
if (!a) /* do something */ ;

אני לא מצליח לצערי להבין את ההגיון פה ואשמח אם תרחיב.

סבבה, תודה! עכשיו אני יכול להמשיך למטה 🙂

הסבר מעולה!
תודה רבה 🙂

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

כל הכבוד על הסבלנות! אני מזמן הייתי מפנה אנשים לאיזה ספר C אונליין 🙂