Module ২ · Variable আর data type
Integer type, তাদের range আর overflow
এই lesson-এ যা শিখবে
char,short,int,long,long longআর তাদের unsigned রূপের মধ্যে বেছে নিতে পারবে।- Type-র মাপ নিয়ে C standard কী কথা দেয় আর কী দেয় না, সেটা বলতে পারবে।
- সীমার মাথা ছাড়ালে কী হয় আগেই বলতে পারবে: unsigned ঘুরে যায়, signed অনির্ধারিত।
Kenji একটা step counter বানায়। দুই বছর চলে, তারপর এক ব্যবহারকারীর মোট সংখ্যা রাতারাতি ঋণাত্মক হয়ে যায়। ওই মানুষটা 2,147,483,647 পা হেঁটে ফেলেছিল।
কিছুই ভাঙেনি। বাক্সটা ভরে গিয়েছিল, আর ভরা বাক্স যা করে C তাই করেছে। এই lesson বাক্সের মাথাটা কোথায়, আর তার ওপারে কী আছে, সেটা নিয়ে।
Integer পরিবার
Integer মানে পূর্ণসংখ্যা, দশমিক বিন্দু ছাড়া। C তোমাকে পাঁচ মাপের বাক্স দেয়, প্রতিটার signed আর unsigned দুই রূপে।
| Type | Playground-এ মাপ | Playground-এ সীমা | Specifier |
|---|---|---|---|
signed char | 1 byte | -128 থেকে 127 | %d |
short | 2 byte | -32,768 থেকে 32,767 | %hd |
int | 4 byte | -2,147,483,648 থেকে 2,147,483,647 | %d |
long | 8 byte | প্রায় -9.2 থেকে 9.2 কুইন্টিলিয়ন | %ld |
long long | 8 byte | প্রায় -9.2 থেকে 9.2 কুইন্টিলিয়ন | %lld |
unsigned char | 1 byte | 0 থেকে 255 | %u |
unsigned short | 2 byte | 0 থেকে 65,535 | %hu |
unsigned int | 4 byte | 0 থেকে 4,294,967,295 | %u |
unsigned long long | 8 byte | 0 থেকে প্রায় 18.4 কুইন্টিলিয়ন | %llu |
ওই table-র শিরোনামটা আবার পড়ো। ওতে লেখা "Playground-এ", আর সেটা ভদ্রতা করে লেখা না।
Standard কী কথা দেয়, আর কী দেয় না
C standard সর্বনিম্ন সীমা ঠিক করে দেয়, নির্দিষ্ট মাপ না। ও এটুকুই কথা দেয়, এর বেশি না।
charসবসময় 1 byte, সংজ্ঞা অনুযায়ী। আর একটা byte অন্তত 8 bit।shortআরintঅন্তত -32,767 থেকে 32,767 পর্যন্ত পৌঁছায়।longঅন্তত প্রায় দুইশো কোটির এদিক-ওদিক পর্যন্ত পৌঁছায়।long longঅন্তত প্রায় 9.2 কুইন্টিলিয়নের এদিক-ওদিক পর্যন্ত পৌঁছায়।- ওই তালিকার প্রতিটা type আগেরটার চেয়ে অন্তত সমান চওড়া।
তাই তুমি যত desktop আর phone দেখবে, সবগুলোতেই int 4 byte। ওয়াশিং মেশিন আর গাড়ির dashboard-র ছোট chip-এ ওটা 2 byte, আর ওই chip-গুলোও C চালায়।
long-কেই সন্দেহ করতে হবে। একই processor-এ ওটা Linux আর macOS-এ 8 byte, আর Windows-এ 4 byte। আন্দাজ না করে কীভাবে জিজ্ঞেস করতে হয়, lesson 5 দেখাবে।
তাই সৎ অভ্যাসটা হলো: সাধারণ গোনার জন্য int, দুইশো কোটি ছাড়াতে পারে এমন হলে long long, আর কোনো মাপ কখনো মাথায় সত্য হিসেবে লিখে রেখো না।
Signed আর unsigned: একই bit, দুই রকম পড়া
প্রতিটা integer type-র দুইটা রূপ। Signed type একটা bit খরচ করে চিহ্নের জন্য, তাই তার অর্ধেক সীমা ঋণাত্মক। Unsigned type-এ ঋণাত্মক নেই, তাই ওই bit-টা মাপের কাজে লাগে আর মাথাটা দ্বিগুণ হয়।
#include <stdio.h>
int main(void)
{
int temperature = -12; /* signed by default */
unsigned int student_count = 350; /* never negative, so unsigned fits */
printf("temperature : %d\n", temperature);
printf("student count : %u\n", student_count);
return 0;
}
temperature : -12
student count : 350
সাধারণ int মানে signed int। অন্য পড়াটা চাইলে তুমি unsigned লেখো।
যে মানের ঋণাত্মক হওয়া অর্থহীন, তাতে unsigned ব্যবহার করো: মানুষের সংখ্যা, byte-র মাপ, তালিকার অবস্থান। তারপর ভরসা করার আগে পরের দুইটা অংশ পড়ে নাও।
সীমাগুলো একটা header-এ থাকে
সীমা মুখস্থ করতে হয় না। তুমি সেগুলো ছাপো, <limits.h> থেকে, যে মেশিনে সত্যিই চালাচ্ছ তার জন্য।
#include <stdio.h>
#include <limits.h>
int main(void)
{
printf("signed char : %d to %d\n", SCHAR_MIN, SCHAR_MAX);
printf("short : %d to %d\n", SHRT_MIN, SHRT_MAX);
printf("int : %d to %d\n", INT_MIN, INT_MAX);
printf("long long : %lld to %lld\n", LLONG_MIN, LLONG_MAX);
printf("unsigned int: 0 to %u\n", UINT_MAX);
return 0;
}
signed char : -128 to 127
short : -32768 to 32767
int : -2147483648 to 2147483647
long long : -9223372036854775808 to 9223372036854775807
unsigned int: 0 to 4294967295
long ওই তালিকায় নেই, ইচ্ছে করেই। Playground-এ ওর উত্তর 8 আর Windows মেশিনে 4, তাই ছাপা কোনো তালিকায় ওকে রাখলে সেটা সব জায়গায় সত্যি থাকতে পারে না।
তাই "কত বড়" প্রশ্নের উত্তর header, আর lesson 5 সেটাকে অভ্যাসে বদলায়।
Unsigned ঘুরে যায়, আর standard সেটা বলেও দেয়
Unsigned গণিত ঘুরে যাওয়ার জন্যই সংজ্ঞায়িত, গাড়ির odometer যেমন 999999 থেকে 000000-তে ফেরে। এটা নিয়ম, দুর্ঘটনা না।
#include <stdio.h>
int main(void)
{
unsigned int stock = 0;
printf("stock : %u\n", stock);
stock = stock - 1;
printf("stock - 1 : %u\n", stock);
unsigned int big = 4294967295u;
printf("big + 1 : %u\n", big + 1u);
return 0;
}
stock : 0
stock - 1 : 4294967295
big + 1 : 0
ওখানে কিছুই ভুল হয়নি। ওই তিনটা সংখ্যার প্রতিটাই standard-র কথা দেওয়া, সব মেশিনে, চিরকালের জন্য।
আর সেজন্যই এটা কামড়ায়। যে দোকানে 0টা জিনিস আছে সেখানে 1টা বেচলে এখন চারশো কোটি জিনিস আছে, আর কোথাও কোনো message ছাপা হয়নি।
তাই যে মান সত্যিই ঋণাত্মক হতে পারে না তার জন্য unsigned ঠিক, আর যে মান থেকে তুমি বিয়োগ করবে তার জন্য ভুল।
Signed overflow অনির্ধারিত, যেটা ভুল হওয়ার চেয়েও খারাপ
একটা signed integer-কে তার মাথা পার করিয়ে দাও, আর C standard তোমার program-র বর্ণনা দেওয়া বন্ধ করে দেয়। "অদ্ভুত সংখ্যা দেয়" না। বর্ণনা দেওয়াই বন্ধ করে দেয়।
ওই কথাটার নাম undefined behaviour, আর মানে হলো compiler ধরে নিতে পারে যে পরিস্থিতিটা কখনো ঘটে না। তারপর ও ওই ধরে নেওয়াটার উপর দাঁড়িয়ে তোমার program বানায়।
আমরা যে মেশিনে এটা চালিয়েছি সেখানে মানটা সীমার নিচে ঘুরে গেছে, যেটা বেশির ভাগ মানুষ আশা করে। একই দৌড়ে একই program নিজের সম্পর্কে একটা প্রশ্নের ভুল উত্তরও দিয়েছে, কারণ compiler ঘুরে যাওয়াটা ধরেই নেয়নি।
এই দুইটাই অনুমোদিত। কোনোটাই compiler-র bug না। Bug-টা ওই program-এ, যেটা overflow করেছে।
তাই অভ্যাসটা হলো: কোনো হিসাব দুইশো কোটি ছাড়াতে পারে মনে হলে ছাড়ানোর আগেই long long নাও, পরে না।
Suffix: তুমি কোন ধরনের সংখ্যা লিখেছ সেটা compiler-কে বলা
তোমার code-এ সাধারণ একটা সংখ্যা, যেমন 42, হলো একটা int। একটা অক্ষর জুড়লে ওটা কী, সেটাই বদলে যায়।
Literal-র suffix আর তাদের specifier
42 int printf("%d", 42)
42u unsigned int printf("%u", 42u)
42L long printf("%ld", 42L)
42LL long long printf("%lld", 42LL)
42ULL unsigned long long printf("%llu", 42ULL)
- বড় হাতের
Lপড়তে সহজ, ছোট হাতেরlদেখতে 1-র মতো। 2147483647 + 1overflow করে, কারণ দুই পাশইint।2147483647LL + 1করে না, কারণ বাঁ পাশ আগে থেকেই চওড়া।- Specifier type-র সঙ্গে মিলতে হবে, নাহলে
printfভুল byte পড়ে।
তাই suffix দিয়ে তুমি হিসাব শুরুর আগেই সেটাকে চওড়া করো, আর specifier দিয়ে উত্তরটা বের করে পড়ো।
সবচেয়ে ছোট প্রমাণ: সবচেয়ে বড় int, তারপর একই যোগটা একটা চওড়া বাক্সে।
#include <stdio.h>
#include <limits.h>
int main(void)
{
int top = INT_MAX;
long long roomy = (long long)INT_MAX + 1;
printf("int can reach : %d\n", top);
printf("long long holds : %lld\n", roomy);
return 0;
}
int can reach : 2147483647
long long holds : 2147483648
সামনের (long long) একটা cast, আর Module 4 ওটা বোঝাবে। + 1 ঘটার আগেই ও মানটাকে চওড়া করে দেয়, আর পুরো কৌশলটা ওটাই।
দুই দিক থেকেই unsigned ঘুরে যাওয়া, আর প্রতিটা সংখ্যা standard-র কথা দেওয়া।
#include <stdio.h>
int main(void)
{
unsigned int stock = 0;
unsigned int big = 4294967295u;
printf("0 - 1 as unsigned : %u\n", stock - 1u);
printf("top + 1 : %u\n", big + 1u);
printf("top + 2 : %u\n", big + 2u);
return 0;
}
0 - 1 as unsigned : 4294967295
top + 1 : 0
top + 2 : 1
তৃতীয় লাইনটা গোনো। ওটা 1, 2 না, কারণ ঘোরাটা একবার ঘটেছে আর তারপর গোনা স্বাভাবিকভাবেই চলেছে।
Run in Compilerদিনে কত পা গুণ কত দিন। দুইটা বাক্স, দুইটা উত্তর, একটাই input।
#include <stdio.h>
int main(void)
{
int steps_per_day = 0;
int days = 0;
scanf("%d %d", &steps_per_day, &days);
int as_int = steps_per_day * days;
long long as_long_long = (long long)steps_per_day * days;
printf("as int : %d\n", as_int);
printf("as long long : %lld\n", as_long_long);
return 0;
}
as int : -294967296
as long long : 4000000000
ওই output 20000 200000 input-র জন্য। কেবল দ্বিতীয় লাইনটা কথা দেওয়া। প্রথমটা এই দৌড়ে যা ছেপেছে তাই, কারণ গুণটা একটা signed type-এ overflow করেছে আর তারপর standard আর কিছুই বলে না।
Cast-টা কোথায় বসেছে দেখো। ওটা গুণের আগে, বাঁ পাশের operand-এ, তাই পুরো হিসাবটাই চওড়া type-এ হয়। (long long)(steps_per_day * days) লিখলে আগে overflow হতো আর ভাঙা জিনিসটাকে পরে চওড়া করা হতো।
এটা কোথায় কাজে লাগছে
- 2038 সালের সমস্যা। Unix সময় রেখেছে 1970 থেকে গোনা second-র একটা signed 32 bit সংখ্যায়। ওই ঘরটা ভরে যাবে 2038 সালের 19 জানুয়ারি, তাই Linux
time_t-কে 64 bit-এ নিয়ে গেছে, আর যারা নেয়নি সেই যন্ত্রগুলো এখনো একটা একটা করে বেরোচ্ছে। - File-র মাপ। Linux-এ
off_t64 bit, যাতে একটা file 2 GB ছাড়াতে পারে। পুরনো build-এ ওই একইoff_tছিল 32 bit, আর সেজন্যই একসময় সব জায়গায় একই 2 GB সীমা দেখা যেত। - IPv4 address। একটা address হলো 32 bit-র unsigned integer, তাই ওগুলোর সংখ্যা 4,294,967,296, আর সেজন্যই দুনিয়ার ভাঁড়ার ফুরিয়েছে।
- রং। একটা pixel-র লাল, সবুজ আর নীল ভাগ তিনটা
unsigned charমান, প্রতিটা 0 থেকে 255। ওই সীমাটা type-ই, কোনো রেওয়াজ না। - Ariane 5, flight 501, 1996। একটা 64 bit দশমিক মানকে এমন একটা 16 bit signed integer-এ নেওয়া হয়েছিল যেটা ওকে ধরতে পারে না। উড়ালের 37 second পরে rocket-টা হারিয়ে যায়। আগের rocket-এ একই code ঠিক ছিল, কারণ সেটা ধীরে উড়ত।
যে ভুলগুলো সবাই করে
১. বাক্সে ধরে না এমন একটা literal।
int big = 3000000000;
printf("%d\n", big);
GCC 12 একটাও message দেয় না, আর নিজের মেশিনে gcc -Wall -Wextra-ও না। Program build হয় আর ছাপে -1294967296। তিনশো কোটি একটা 4 byte signed বাক্সে ধরে না, আর যে মানটা পৌঁছায় সেটা তুমি যা লিখেছিলে তা না। long long নাও।
২. চওড়া মানের জন্য ভুল specifier।
long long big = 5000000000LL;
printf("%d\n", big);
Playground এটা নীরবে compile করে, কারণ format-র পরীক্ষাটা -Wall-র পিছনে থাকে। আমাদের দৌড়ে ওটা ছেপেছে 705032704। %d printf-কে বলেছে 4 byte পড়তে, আর মানটা 8 byte লম্বা। লেখো %lld।
৩. এমন একটা overflow পরীক্ষা, যেটা compiler মুছে ফেলতে পারে।
int x;
scanf("%d", &x);
printf("did it overflow? %d\n", x + 1 < x);
দুইটা command line-র কোনোটাতেই কোনো message নেই। Input 2147483647 দিলে এটা ছেপেছে 0, মানে "overflow হয়নি", অথচ ওই দৌড়ে x + 1-র রাখা মানটা ছিল -2147483648। Signed overflow অনির্ধারিত, তাই compiler ধরে নিতে পারে x + 1 সবসময় x-র চেয়ে বড়, আর না দেখেই উত্তর দিতে পারে। যোগের আগে সীমা মেলাও, পরে না।
৪. Signed মানের সঙ্গে unsigned মান মেলানো।
int a = -1;
unsigned int b = 1;
printf("%d\n", a < b);
Playground কিছুই বলে না; নিজের মেশিনে gcc -Wextra বলে warning: comparison of integer expressions of different signedness। এটা ছাপে 0, কারণ -1 আগে unsigned-এ বদলে গিয়ে 4294967295 হয়। তুলনার দুই পাশ একই পরিবারে রাখো।
Amara একটা sensor-র দুইটা reading যোগ করছে। প্রতিটা একটা int-এ ধরে, যোগফলটা ধরে না।
Input. এক লাইনে দুইটা পূর্ণসংখ্যা a b।
Output. এক লাইনে ওদের যোগফল।
Constraints. 0 <= a, b <= 2000000000।
Sample. Input 2000000000 2000000000 দিলে 4000000000।
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
/* The inputs fit in an int. The answer does not. */
return 0;
}
safe-sum নামে judge করা হয়। সীমার দুই প্রান্তই লুকানো test, আর তার একটাই পুরো সমস্যাটা।
একটা server অনেক বিপুল সংখ্যক second ধরে চলছে। সেটা দিন, ঘণ্টা, মিনিট আর second হিসেবে ছাপো।
Input. এক লাইনে একটা পূর্ণসংখ্যা s, second-র সংখ্যা।
Output. এক লাইনে: d days, h hours, m minutes, s seconds, চারটা সংখ্যা বসানো অবস্থায়।
Constraints. 0 <= s <= 1000000000000। শব্দের ব্যাকরণ ইচ্ছে করেই ঠিক করা হয়নি; যেমন দেখানো হয়েছে ঠিক তেমনই ছাপো।
Sample. Input 90061 দিলে 1 days, 1 hours, 1 minutes, 1 seconds।
#include <stdio.h>
int main(void)
{
long long s = 0;
scanf("%lld", &s);
/* 86400 seconds in a day, 3600 in an hour, 60 in a minute. */
return 0;
}
seconds-to-days নামে judge করা হয়। এক পূর্ণসংখ্যাকে আরেকটা দিয়ে ভাগ করলে ভগ্নাংশটা বাদ পড়ে, আর এখানে ঠিক সেটাই তোমার দরকার।
চালানোর আগে আগেই বলো। দুইটা unsigned মান পড়ো আর ওদের বিয়োগফল unsigned হিসেবে ছাপো, তারপর একই বিয়োগ একটা signed বাক্সে।
Input. এক লাইনে দুইটা অঋণাত্মক পূর্ণসংখ্যা a b, প্রতিটা বড়জোর 4294967295।
Output. দুই লাইন: আগে unsigned বিয়োগফল, তারপর signed-টা।
Constraints. আগে হাতে কলমে 3 5 আর 0 1 মিলিয়ে দেখো।
#include <stdio.h>
int main(void)
{
unsigned int a = 0;
unsigned int b = 0;
scanf("%u %u", &a, &b);
printf("%u\n", a - b);
printf("%lld\n", (long long)a - (long long)b);
return 0;
}
এই module-এ এটা judge করা হয় না। 3 5 দিয়ে চালাও, দুইটা সংখ্যাই লিখে রাখো, তারপর প্রথমটা কাউকে এক বাক্যে বুঝিয়ে দাও।
একটা ছাত্র-পরিচয় নম্বর int-র জন্য বড্ড লম্বা। কেবল ভাগ আর বিয়োগ দিয়ে ওটাকে উপরের অংশ আর নিচের নয় অঙ্কে ভাগ করো।
Input. এক লাইনে একটা পূর্ণসংখ্যা id, 1000000000 থেকে 999999999999999999-র মধ্যে।
Output. দুই লাইন: id / 1000000000, তারপর ভাগটা যা ফেলে দিয়েছিল সেই অংশটা।
Constraints. মানটা কখনো int-এ ধরে না, তাই এখানে প্রতিটা variable long long।
Sample. Input 1234567890123 দিলে 1234 তারপর 567890123।
#include <stdio.h>
int main(void)
{
long long id = 0;
scanf("%lld", &id);
long long top = id / 1000000000LL;
long long rest = 0; /* what is left when the top is taken away */
printf("%lld\n", top);
printf("%lld\n", rest);
return 0;
}
এই module-এ এটা judge করা হয় না। ভাগশেষের operator % আসে Module 4-এ আর এক চিহ্নেই কাজটা করে দিত; - আর * দিয়ে সেখানে পৌঁছানোটাই এখানে আসল কাজ।
যে প্রশ্নগুলো সবার মনে আসে
সব জায়গায়
long longদিয়ে দিলেই কি চিন্তা শেষ?Beginner-র program-র জন্য প্রায় তাই। প্রতিটা মানে 4 byte বেশি লাগে আর একটা গোটা শ্রেণির bug চলে যায়। বড় array বা network packet-এ ওই byte-গুলো গুরুত্ব পেতে শুরু করে, তখন তুমি বাছো।
long longকিint-র চেয়ে ধীর?64 bit মেশিনে মাপা যায় এমনভাবে না, আর প্রতিটা desktop আর phone-ই তাই। ছোট 8 bit chip-এ অনেক ধীর, কারণ processor-কে যোগটা টুকরো করে করতে হয়।
longআরlong long-র তফাত কী?Playground-এ কিছুই না: দুইটাই 8 byte। Windows-এ
long4 byte আরlong long8 byte। ঠিক সেজন্যই এই tracklong longলেখে,longনা।Unsigned-র ঘোরা সংজ্ঞায়িত, অথচ signed overflow না কেন?
Unsigned গণিতের সংজ্ঞাই বৃত্তে গোনা, আর প্রতিটা মেশিন সেটা একইভাবে করে। Signed রূপ একসময় মেশিনভেদে আলাদা ছিল, আর অনির্ধারিত রাখলে compiler আরও জোরালোভাবে optimise-ও করতে পারে।
আমার উত্তর overflow করেছে কি না বুঝব কীভাবে?
হিসাব করার আগেই input-গুলো
INT_MAXবাLLONG_MAX-র সঙ্গে মিলিয়ে। পরে জিজ্ঞেস করা মানে এমন প্রশ্ন করা, যার উত্তর ভাষাটা আগেই দিতে অস্বীকার করেছে।
মূল কথা
- C সর্বনিম্ন সীমা আর একটা ক্রম কথা দেয়,
charছাড়া কোনো type-র নির্দিষ্ট মাপ না। - তুমি যা দেখবে সবেতেই
int4 byte, আর এখনো টিকে থাকা chip-এ 2 byte। longLinux-এ 8 byte আর Windows-এ 4, তাই এই tracklong longলেখে।- Unsigned গণিত ঘুরে যায়, standard-র নিজের নিয়মেই, আর 0 বিয়োগ 1 একটা বিশাল সংখ্যা।
- Signed overflow অনির্ধারিত: compiler ধরে নিতে পারে ওটা কখনো ঘটে না, আর সেভাবেই কাজ করতে পারে।
- হিসাবের আগে suffix বা cast দিয়ে চওড়া করো, আর specifier-টা type-র সঙ্গে মেলাও।
পরের lesson দশমিক বিন্দুওয়ালা সংখ্যা নিয়ে, যেখানে চমকটা বাক্সের মাপ না, বরং এই সত্যটা যে 0.1 কখনো ঠিক 0.1 ছিলই না।
lesson ২ শেষ
শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।
পরেরটা: Floating point: float, double, আর 0.1 কেন ঠিক 0.1 নয়