Learn C Programming

lesson ৪ / ৮ · Operator আর type কনভার্শন

Module ৪ · Operator আর type কনভার্শন

Bitwise operator: এক বিট করে কাজ

Freeপড়া

এই 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।

ExpressionBitমানকীভাবে পড়বে
12 & 1010008যেখানে দুইটারই 1 ছিল শুধু সেই ঘরগুলো
12 | 10111014যেকোনো একটার 1 ছিল এমন প্রতিটা ঘর
12 ^ 1001106যেখানে ওরা মেলেনি শুধু সেই ঘরগুলো
12 << 11100024সব এক ঘর উপরে সরল, তাই দ্বিগুণ
12 >> 2113দুই ঘর নিচে সরল, তাই 4 দিয়ে ভাগ
~1232টা 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, এক bit-র একটা mask, আর and-র ফল Mask যে ঘরগুলো দাগায় সেগুলো রাখে, বাকি সব শূন্য করে bit 7 6 5 4 3 2 1 0 leds 0 1 0 1 1 0 1 0 1u << 3 0 0 0 0 1 0 0 0 ফল 0 0 0 0 1 0 0 0 ফলটা 8, 1 না। সোজা হ্যাঁ-না পেতে ওটাকে আবার 3 ঘর নিচে নামাও।

একটা 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-র বাড়তি বাক্সটাই ঠিক উত্তর।

Example 1: সবচেয়ে ছোট bit পরীক্ষা

একটা সংখ্যা পড়ো, একটা 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-রই দ্বিতীয় বানান।

Run in Compiler
Example 2: এক সংখ্যায় তিনটা setting

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।

Run in Compiler
Example 3: এক সংখ্যায় ভরা একটা রং

আসল জিনিসটা। প্রতিটা 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 ঢুকল, একটা সংখ্যা বেরোল, আর তিনটাই অক্ষত ফিরে এলো।

Run in Compiler

এটা কোথায় কাজে লাগে

  • 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।

মাথা খাটাও

Zara প্রায় একরকম দুইটা লাইন লেখে, আর তার একটাই সে সত্যিকারের program-এ রাখবে।

unsigned int a = 1u << 31;
int b = 1 << 31;

একটা পুরোপুরি নির্ধারিত আর অন্যটা undefined behaviour, আর Playground দুইটার কোনোটা নিয়েই কিছু বলে না। কোনটা কোনটা বলো, আর type-র ঠিক কোন বৈশিষ্ট্য এটা ঠিক করে তার নাম বলো। তারপর কঠিন অংশটা: 1u << 32-ও undefined, আবার অন্য একটা কারণে, আর সেই কারণের সাথে চিহ্নের কোনো সম্পর্ক নেই।

প্রতিটা type-র bit গোনো আর জিজ্ঞেস করো 1-টা কোথায় গিয়ে পড়ত। কঠিন অংশের জন্য জিজ্ঞেস করো 32 bit-র একটা মানে আসলে কয়টা shift-র ঘর আছে।

অনুশীলন ১সহজ

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 রূপটা আগেই ভুল হয়ে যেত।

Run in Compiler
অনুশীলন ২মাঝারি

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 কিছুই প্রমাণ করে না।

Run in Compiler
অনুশীলন ৩মাঝারি

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 Compiler
অনুশীলন ৪কঠিন

Amara পুরনো একটা 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 বয়ে আনবে যা তুমি চাওনি।

Run in Compiler

সচরাচর যে প্রশ্নগুলো আসে

  • এই 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 দেবে