Module ৪ · Operator আর type কনভার্শন
Bitwise operator: এক বিট করে কাজ
এই lesson-এ যা শিখবে
&,|,^আর~দিয়ে দুইটা সংখ্যাকে bit ধরে ধরে মেলাতে পারবে।<<আর>>দিয়ে bit সরাতে পারবে, আর বলতে পারবে তাতে মানটার কী হয়।- চারটা চেনা idiom দিয়ে একটা bit set, clear, toggle আর test করতে পারবে।
Kenji-র একটা board-এ আটটা LED, আর ওগুলো চালানোর জন্য একটা byte। Bit 0 প্রথম বাতি, bit 7 শেষটা।
ও আটটা variable চায় না। ও একটা সংখ্যা চায়, আর তার ভিতরে হাত ঢোকানোর একটা উপায় চায়।
এই lesson-এ যা আছে সবই সেটাই: এমন অঙ্ক যেটা একটা সংখ্যাকে পরিমাণ না ধরে সুইচের সারি ধরে।
ছয়টা operator, যারা মানের উপর না, bit-র উপর চলে
Module 2-র lesson 2 বলেছিল একটা integer আসলে bit-র একটা নকশা। এই operator-গুলো সেটা মেনে নেয়।
ছয়টা bitwise operator
a & b and দুই bit-ই 1 হলে bit হয় 1
a | b or অন্তত এক bit 1 হলে bit হয় 1
a ^ b xor দুই bit আলাদা হলে bit হয় 1
~a not প্রতিটা bit উল্টে যায়
a << n left প্রতিটা bit n ঘর উপরের দিকে সরে
a >> n right প্রতিটা bit n ঘর নিচের দিকে সরে
- এই track ওদের
unsignedমানের উপর চালায়। Signed মানে ছয়টার দুইটার এমন নিয়ম আছে যা তুমি এখনই চাও না। - ওরা প্রতিটা bit আলাদা করে দেখে। কোনো হাতে রাখা নেই, ধার নেওয়া নেই, round করা নেই।
&&&না, আর|||না। শেষ অংশটা এই কথায় ফিরে আসবে।
ছয়টাই দেখতে চার bit-ই যথেষ্ট। নাও 12, যেটা 1100, আর 10, যেটা 1010।
| Expression | Bit | মান | কীভাবে পড়বে |
|---|---|---|---|
12 & 10 | 1000 | 8 | যেখানে দুইটারই 1 ছিল শুধু সেই ঘরগুলো |
12 | 10 | 1110 | 14 | যেকোনো একটার 1 ছিল এমন প্রতিটা ঘর |
12 ^ 10 | 0110 | 6 | যেখানে ওরা মেলেনি শুধু সেই ঘরগুলো |
12 << 1 | 11000 | 24 | সব এক ঘর উপরে সরল, তাই দ্বিগুণ |
12 >> 2 | 11 | 3 | দুই ঘর নিচে সরল, তাই 4 দিয়ে ভাগ |
~12 | 32টা bit-ই উল্টানো | 4294967283 | একটা unsigned int 32 bit চওড়া, 4 না |
#include <stdio.h>
int main(void)
{
unsigned int a = 12u;
unsigned int b = 10u;
printf("a & b = %2u\n", a & b);
printf("a | b = %2u\n", a | b);
printf("a ^ b = %2u\n", a ^ b);
printf("a << 1 = %2u\n", a << 1);
printf("a >> 2 = %2u\n", a >> 2);
printf("~a = %u\n", ~a);
return 0;
}
a & b = 8
a | b = 14
a ^ b = 6
a << 1 = 24
a >> 2 = 3
~a = 4294967283
শেষ লাইনটাই একমাত্র চমক, আর চমকটা চওড়ার। ~12 32টা bit-ই উল্টায়, তুমি যে চারটা এঁকেছ শুধু সেগুলো না।
তাই ~a & 15u হলো 3, যেটা চার bit-র উত্তর। যখনই উল্টাবে, প্রায় সব সময় mask-ও লাগবে।
Mask মানে এমন একটা সংখ্যা যেটা তুমি ছাঁচ হিসেবে ব্যবহার করো
Mask এমন একটা মান যার 1 bit-গুলো দেখায় তুমি কোন ঘরগুলো নিয়ে ভাবছ। Mask-র সাথে & ওই ঘরগুলো রাখে আর বাকি সব শূন্য করে।
একটা bit নম্বর k-র mask হলো 1u << k। ওটা একটা 1, তার পরে kটা শূন্য।
একটা byte-কে binary-তে ছাপা মানে ওই mask আটবার ব্যবহার করা। Module 6 ওটা loop হিসেবে লিখবে; আজ ওটা আটটা expression।
#include <stdio.h>
int main(void)
{
unsigned int leds = 90u;
printf("%u%u%u%u%u%u%u%u\n",
(leds >> 7) & 1u, (leds >> 6) & 1u,
(leds >> 5) & 1u, (leds >> 4) & 1u,
(leds >> 3) & 1u, (leds >> 2) & 1u,
(leds >> 1) & 1u, leds & 1u);
return 0;
}
01011010
প্রতিটা expression যে bit-টা তুমি চাও সেটাকে একদম নিচে নামায়, তারপর শুধু ওই একটা bit রাখে।
তাই "bit k পড়ো" মানে সব সময় একই দুই ধাপ: নিচে নামাও, তারপর 1 দিয়ে mask করো।
চারটা idiom, আর ওরাই পুরো lesson
এই operator-গুলোর প্রতিটা বাস্তব ব্যবহার চারটা লাইনের একটা। চারটা শিখে ফেললে bit নিয়ে কাজ শেখা হয়ে গেল।
Set, clear, toggle, test
flags |= (1u << k); bit k-কে 1 করো
flags &= ~(1u << k); bit k-কে 0 করো
flags ^= (1u << k); bit k উল্টে দাও
(flags >> k) & 1u bit k পরীক্ষা করো, মান 1 বা 0
- প্রথম তিনটা variable-টা বদলায়। চতুর্থটা শুধু পড়ে।
- Clear-ই একমাত্র যেটায়
~লাগে: mask-টাকে এক ঘরে শূন্য আর বাকি সব ঘরে এক হতে হয়। - Test-টা আগে থেকেই 1 বা 0, তাই ওটা সরাসরি
%uদিয়ে ছাপে আর কোনো তুলনা লাগে না।
#include <stdio.h>
int main(void)
{
unsigned int flags = 0u;
flags |= (1u << 3);
printf("set bit 3 %u\n", flags);
flags |= (1u << 5);
printf("set bit 5 %u\n", flags);
flags &= ~(1u << 3);
printf("clear bit 3 %u\n", flags);
flags ^= (1u << 5);
printf("toggle bit 5 %u\n", flags);
printf("is bit 5 set? %u\n", (flags >> 5) & 1u);
return 0;
}
set bit 3 8
set bit 5 40
clear bit 3 32
toggle bit 5 0
is bit 5 set? 0
মানগুলোকে bit-র নকশা হিসেবে পড়লে অঙ্কটা মিলিয়ে যায়। 8 মানে এক বাতি জ্বলছে, 40 মানে দুই বাতি, 32 মানে আবার এক বাতি।
তাই একটা unsigned int-এ 32টা আলাদা হ্যাঁ-না উত্তর থাকে, আর প্রতিটার কাছে পৌঁছাতে এক লাইন লাগে।
Shift করা মানে গুণ করা, যতক্ষণ না ওটা আর তা থাকে
একটা unsigned মানে x << n 2-র n ঘাত দিয়ে গুণ করে, আর x >> n ওটা দিয়ে ভাগ করে, ভাগশেষ ফেলে দিয়ে।
তাই 2-র ঘাত লেখার সবচেয়ে সস্তা উপায় 1u << n। 1u << 10 হলো 1024।
দুইটা নিয়ম এটাকে সব জায়গায় নিরাপদ হতে দেয় না, আর ওরা ইচ্ছে করেই আলাদা দুইটা শব্দ ব্যবহার করে।
- ঋণাত্মক signed মানকে ডানে shift করা implementation defined। Compiler-কে লিখে রাখতে হয় ও কী করে, আর তারপর সেটাই করতে হয়। GCC চিহ্নটা ধরে রাখে, তাই ওখানে
-8 >> 1হলো -4। - একটা 1-কে চিহ্নের bit-এ বাঁয়ে shift করা undefined behaviour। 32 bit-র একটা
int-এ1 << 31-র কোনো নির্ধারিত মানেই নেই, আর এখানে তার কোনো output লেখা নেই।
মানটা unsigned হলে দুইটাই মিলিয়ে যায়। 1u << 31 সাধারণ, পুরোপুরি নির্ধারিত 2147483648।
#include <stdio.h>
int main(void)
{
printf("1u << 0 = %u\n", 1u << 0);
printf("1u << 10 = %u\n", 1u << 10);
printf("1u << 31 = %u\n", 1u << 31);
return 0;
}
1u << 0 = 1
1u << 10 = 1024
1u << 31 = 2147483648
Literal-র গায়ে ওই u-টাই পুরো নিরাপত্তা। এক অক্ষর খরচ, আর মাথা থেকে দুইটা নিয়ম বাদ।
তাই এই track প্রতিবার 1u << k লেখে, আর shift করে শুধু unsigned মান।
& মানে && না, আর XOR swap একটা ফাঁদ
5 & 1 হলো 1 আর 5 && 1-ও 1, আর এই জন্যই ভুলটা এত দিন টিকে থাকে।
ওরা মিলে যায় কাকতালীয়ভাবে। 4 & 1 হলো 0 আর 4 && 1 হলো 1, আর ওখানেই কাকতালটা শেষ।
& bit মেলায় আর সব সময় দুই পাশই চালায়। && উত্তর মেলায় আর ডান পাশ বাদ দিতে পারে। Lesson 2-র পাহারা দেওয়া ভাগটা ওই তফাতের উপরই দাঁড়ানো।
আরও একটা বিখ্যাত চালাকির নাম বলে তারপর বাদ দেওয়া দরকার। তৃতীয় কোনো variable ছাড়া, তিনটা ^= লাইন দিয়ে দুইটা সংখ্যা অদলবদল করা যায়।
ওটা সত্যি, আর এই track ওটা ব্যবহার করে না। আধুনিক যেকোনো processor-এ ওটা তৃতীয় একটা বাক্সের চেয়ে ধীর, ওটা পড়া যায় না, আর দুই পাশে একই variable থাকলে ওটা চুপচাপ শূন্য বানিয়ে দেয়। Module 2-র বাড়তি বাক্সটাই ঠিক উত্তর।
একটা সংখ্যা পড়ো, একটা bit নিয়ে একটা প্রশ্নের উত্তর দাও।
#include <stdio.h>
int main(void)
{
unsigned int n = 0;
scanf("%u", &n);
printf("%u\n", n & 1u);
return 0;
}
1
ওই output-টা 7 input-র জন্য। Bit 0-ই জোড়-বিজোড়ের bit, তাই এটা lesson 1-র % 2-রই দ্বিতীয় বানান।
Bold, italic আর underline একটা মানের bit 0, 1 আর 2 হিসেবে, তিনটা input থেকে বসানো।
#include <stdio.h>
int main(void)
{
unsigned int bold = 0;
unsigned int italic = 0;
unsigned int under = 0;
scanf("%u %u %u", &bold, &italic, &under);
unsigned int look = (bold << 0) | (italic << 1) | (under << 2);
printf("look %u\n", look);
printf("italic? %u\n", (look >> 1) & 1u);
return 0;
}
look 5
italic? 0
ওই output-টা 1 0 1 input-র জন্য। Bold আর underline চালু, তাই bit 0 আর 2 বসেছে, আর 1 যোগ 4 হলো 5।
আসল জিনিসটা। প্রতিটা image format আর প্রতিটা web রং ঠিক এটাই করে।
#include <stdio.h>
int main(void)
{
unsigned int r = 0;
unsigned int g = 0;
unsigned int b = 0;
scanf("%u %u %u", &r, &g, &b);
unsigned int packed = (r << 16) | (g << 8) | b;
printf("packed %u\n", packed);
printf("r %u\n", (packed >> 16) & 255u);
printf("g %u\n", (packed >> 8) & 255u);
printf("b %u\n", packed & 255u);
return 0;
}
packed 13140520
r 200
g 130
b 40
ওই output-টা 200 130 40 input-র জন্য। তিনটা byte ঢুকল, একটা সংখ্যা বেরোল, আর তিনটাই অক্ষত ফিরে এলো।
এটা কোথায় কাজে লাগে
- File-র অনুমতি।
chmod-এ তুমি যে0644লেখো ওটা নয়টা bit, মালিক, group আর বাকিদের জন্য তিনটা করে। Linux kernel ওগুলো ঠিক(mode >> k) & 1দিয়েই পরীক্ষা করে। - Network packet। একটা TCP header-এ SYN, ACK আর FIN flag থাকে এক byte-র আলাদা আলাদা bit হিসেবে। তোমার মেশিন যত packet পাঠায়, সবই
|দিয়ে গাঁথা আর&দিয়ে পড়া। - রং। একটা PNG-র বা canvas-র একটা pixel হলো 32 bit-র একটা সংখ্যা, যাতে লাল, সবুজ, নীল আর alpha ঠিক Example 3-র মতো ভরা থাকে।
- দাবার engine। Stockfish পুরো board-টা 64 bit-র একটা unsigned সংখ্যায় রাখে, প্রতি ঘরে এক bit। একটা নৌকা কোন কোন ঘরে আক্রমণ করে সেটা বের করা তখন কয়েকটা shift আর mask-ই।
যে ভুলগুলো সবাই করে
১. & আর &&-কে এক ভেবে নেওয়া।
unsigned int flags = 4u;
printf("%u %u\n", flags & 1u, flags && 1u);
কোনো command line-এই বার্তা নেই, আর ছাপে 0 1। প্রথমটা bit 0 নিয়ে প্রশ্ন করে; দ্বিতীয়টা জিজ্ঞেস করে flags শূন্য না কি না। 5-এ ওরা মেলে আর 4-এ মেলে না।
২. ভুলে যাওয়া যে ~ 32টা bit-ই উল্টায়।
unsigned int nibble = 12u;
printf("%u\n", ~nibble);
কোনো command line-এই বার্তা নেই, আর ছাপে 4294967283। তুমি যে চার bit-র উত্তরটা চেয়েছিলে সেটা ~nibble & 15u, মানে 3। উল্টানোর পরে প্রায় সব সময় একটা mask লাগে।
৩. Signed একটা 1-কে চিহ্নের bit-এ shift করা।
printf("%d\n", 1 << 31);
চেষ্টা করা প্রতিটা command line-এ নীরব, -Wall -Wextra -Wpedantic সহ। তবু এটা undefined behaviour, তাই এখানে কোনো output লেখা নেই। 1u << 31 লেখো আর প্রশ্নটাই থাকে না।
৪. Test idiom থেকে shift-টা বাদ দেওয়া।
unsigned int flags = 8u;
printf("%u\n", flags & (1u << 3));
কোনো command line-এই বার্তা নেই, আর ছাপে 8, 1 না। একটা if-র ভিতরে হ্যাঁ-না হিসেবে মানটা ঠিক, কিন্তু ছাপা উত্তর হিসেবে অকেজো। ওটা নিচে নামাও: (flags >> 3) & 1u।
Kenji জানতে চায় নির্দিষ্ট একটা বাতি জ্বলছে কি না।
Input. এক লাইনে দুইটা পূর্ণসংখ্যা: একটা unsigned মান n আর একটা bit নম্বর k।
Output. এক লাইনে 1, যদি n-র bit k বসানো থাকে, নয়তো 0।
Constraints. 0 <= n <= 4294967295, আর 0 <= k <= 31।
Sample. Input 90 3 দিলে 1। Input 90 2 দিলে 0।
#include <stdio.h>
int main(void)
{
unsigned int n = 0;
unsigned int k = 0;
scanf("%u %u", &n, &k);
/* Shift the bit down to the bottom, then mask with 1u. */
return 0;
}
এই module-এ গ্রেড হয় না। k-কে 31-এ পরীক্ষা করো, যেখানে এটার signed রূপটা আগেই ভুল হয়ে যেত।
Maria-র image tool একটা রং এক সংখ্যায় রাখে আর পরে ওটা আবার খুলতে হয়।
Input. এক লাইনে তিনটা পূর্ণসংখ্যা r, g আর b।
Output. দুই লাইন: প্রথমে packed মানটা, তারপর ওটা থেকে খোলা r, g আর b, মাঝে একটা করে space।
Constraints. 0 <= r, g, b <= 255।
Sample. Input 200 130 40 দিলে 13140520, তারপর 200 130 40।
#include <stdio.h>
int main(void)
{
unsigned int r = 0;
unsigned int g = 0;
unsigned int b = 0;
scanf("%u %u %u", &r, &g, &b);
/* Pack with two shifts and two ors. Unpack with shifts and 255u. */
return 0;
}
bit-pack নামে গ্রেড হয়। Input থেকে না, packed মান থেকেই খোলো, নয়তো program কিছুই প্রমাণ করে না।
David একটা sensor-র byte দেখছে আর জানতে চায় তার আটটা bit-র কয়টা বসানো।
Input. এক লাইনে একটা পূর্ণসংখ্যা b।
Output. এক লাইনে b-র কতগুলো bit 1 তার সংখ্যা।
Constraints. 0 <= b <= 255।
Sample. Input 90 দিলে 4। Input 255 দিলে 8।
#include <stdio.h>
int main(void)
{
unsigned int b = 0;
scanf("%u", &b);
/* Eight tests, added together. No loop until Module 6. */
return 0;
}
এই module-এ গ্রেড হয় না। প্রতিটা পরীক্ষার মান 1 বা 0, তাই যোগফলটাই গণনা, ঠিক Module 2-র lesson 4-র মতো।
Run in CompilerAmara পুরনো একটা format খুলছে, যেখানে প্রতিটা byte-র দুই অর্ধেক উল্টো হয়ে এসেছিল।
Input. এক লাইনে একটা পূর্ণসংখ্যা b।
Output. এক লাইনে b-র মান, যেখানে উপরের চার bit আর নিচের চার bit জায়গা বদল করেছে।
Constraints. 0 <= b <= 255।
Sample. Input 195 দিলে 60। Input 16 দিলে 1।
#include <stdio.h>
int main(void)
{
unsigned int b = 0;
scanf("%u", &b);
/* Two halves: one moves up four places, the other moves down four. */
return 0;
}
nibble-swap নামে গ্রেড হয়। সরানোর আগে প্রতিটা অর্ধেককে 15u দিয়ে mask করো, নয়তো ফলটা এমন bit বয়ে আনবে যা তুমি চাওনি।
সচরাচর যে প্রশ্নগুলো আসে
এই track
unsigned-র উপর জোর দেয় কেন?কারণ ছয়টার দুইটা operator-র signed মানে এমন নিয়ম আছে যা সহজে ভুল হয় আর সহজে চোখে পড়ে না।
unsignedদুইটা নিয়মই সরিয়ে দেয়।x << 1কিx * 2-র চেয়ে দ্রুত?আজকাল আর না। 2-র ঘাত দিয়ে গুণ করলে প্রতিটা compiler ওটাকে shift-এ বদলে নেয়। তুমি যেটা বলতে চাও সেটাই লেখো।
printfদিয়ে binary-তে ছাপব কীভাবে?C17-এ পারবে না; binary-র কোনো specifier নেই। C23
%bযোগ করে। তার আগ পর্যন্ত এই lesson-র আটটা expression, নয়তো Module 6-র একটা loop।^আর "ঘাত"-র মধ্যে তফাত কী?অন্য কিছু ভাষায় ওদের চিহ্ন এক, আর এটুকুই মিল। C-তে
^হলো exclusive or, আর lesson 1-র ভুল ৪ ঠিক সেখানেই কামড় দেয়।ঋণাত্মক সংখ্যা দিয়ে shift করা যায়?
না, ওটাও undefined, আর type-র চওড়া বা তার বেশি ঘর shift করাও তাই। 32 bit-র মানে shift-র সংখ্যা 0 থেকে 31-র মধ্যে রাখো।
মূল কথা
&,|,^আর~প্রতিটা bit আলাদা করে দেখে, কোনো হাতে রাখা ছাড়াই।- Mask দাগিয়ে দেয় তুমি কোন bit চাও, আর
1u << kহলো এক bit-র mask। - Set, clear, toggle আর test, এই চারটা লাইনেই প্রায় সব বাস্তব কাজ চলে।
<<আর>>unsignedমানে 2-র ঘাত দিয়ে গুণ আর ভাগ করে।- ঋণাত্মক মান ডানে shift করা implementation defined; 1-কে চিহ্নের bit-এ shift করা undefined।
&মানে&&না, আর XOR swap এমন চালাকি যা এই track ব্যবহার করে না।
এরপর জানবে, এক লাইনে কয়েকটা operator লিখলে তাদের ক্রম কে ঠিক করে।
lesson ৪ শেষ
শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।
পরেরটা: Precedence আর associativity: কখন bracket দেবে