Module ৪ · Operator আর type কনভার্শন
Type conversion: promotion, casting আর চুপচাপ হারিয়ে যাওয়া মান
এই lesson-এ যা শিখবে
- একটা operator-র দুই পাশের type আলাদা হলে ও কোন type-এ কাজ করবে সেটা আগেই বলতে পারবে।
(type)দিয়ে cast লিখতে পারবে আর তার দুইটা সৎ ব্যবহারের প্রতিটা কীসের জন্য বলতে পারবে।- তিনটা নীরব ক্ষতির নাম বলতে পারবে: truncation, narrowing আর চিহ্ন হারানো।
Maria-র একটা খারাপ import-র পরে stock-র গোনা -1, আর তার একটা পরীক্ষা দেখে গোনাটা 1u সীমার নিচে কি না।
ও লেখে count < limit। উত্তর আসে মিথ্যা। Program বলছে, মাইনাস এক এক-র চেয়ে ছোট না।
কিছুই নষ্ট হয়নি। সীমার গায়ের ওই u-টা বদলে দিয়েছে তুলনাটা কোন type-এ হবে, আর ওই বদলে -1 টেকে না।
C কাজ করার আগে রূপান্তর করে, সব সময়
C-র প্রায় প্রতিটা operator-র দুই পাশে একই type লাগে। না মিললে C আগে একটাকে বদলে নেয়।
এটা তুমি না চাইতেই ঘটে আর কোনো বার্তা ছাড়াই ঘটে। এটার নাম implicit conversion, আর এই lesson-র বিষয় ওটাই।
দুই সেট নিয়ম কাজটা করে: integer promotion, যেটা ছোট type-গুলোর উপর ঘটে, আর usual arithmetic conversions, যেটা বাকিটা মেটায়।
Integer promotion: ছোট type-র উপরে আদৌ কাজ হয় না
একটা char বা short কখনোই সেই type না যেটায় arithmetic operator কাজ করে। দুইটাকেই আগে int-এ তোলা হয়।
#include <stdio.h>
int main(void)
{
char small = 'A';
short shorter = 300;
printf("a char is %zu byte\n", sizeof(small));
printf("a short is %zu bytes\n", sizeof(shorter));
printf("char + char is %zu bytes\n", sizeof(small + small));
printf("short + short is %zu bytes\n", sizeof(shorter + shorter));
return 0;
}
a char is 1 byte
a short is 2 bytes
char + char is 4 bytes
short + short is 4 bytes
এক byte-র দুইটা মান যোগ হলো আর উত্তরটা চার byte চওড়া। + যখন ওদের দেখল, তখন কোনোটাই আর char ছিল না।
এটা ভালো খবর। এই জন্যই 'A' + 1 উপচে পড়তে পারে না, আর এই জন্যই Module 2-র character নিয়ে অঙ্ক বুদ্ধিমানের মতো চলেছিল।
Usual arithmetic conversions, একটা মই হিসেবে
দুই পাশ অন্তত int হয়ে গেলে C এই মইয়ে দুইটার উঁচুটা বাছে আর অন্যটাকে তুলে ওখানে নিয়ে যায়।
প্রথম ধাপটা মেনে নেওয়া সহজ: একটা int-র সাথে একটা double মেশালে double পাও। Lesson 1-র 3.0 এই নিয়মটাই ছিল।
int আর unsigned int-র মাঝের ধাপটাই মানুষের টাকা খরচ করায়।
Signed-র সাথে unsigned-র দেখা, আর চিহ্নটা হারে
int আর unsigned int একই চওড়া। ওদের দেখা হলে C signed-টাকে unsigned-এ বদলে নেয়।
-1-কে unsigned int-এ বদলালে কোনো error বা শূন্য আসে না। আসে 4294967295, ওই type-র ধরতে পারা সবচেয়ে বড় মান।
#include <stdio.h>
int main(void)
{
int count = -1;
printf("as unsigned %u\n", (unsigned int)count);
printf("count < 1u %d\n", count < 1u);
printf("count < 1 %d\n", count < 1);
return 0;
}
as unsigned 4294967295
count < 1u 0
count < 1 1
মাঝের লাইনটাই Maria-র bug। তুলনাটা ঠিক অঙ্কই করেছে, কিন্তু ভুল জোড়া মানের উপর।
Playground কিছুই বলে না। স্থানীয় gcc -Wall বলে warning: comparison of integer expressions of different signedness: 'int' and 'unsigned int'।
তাই সমাধান হলো দুই পাশই signed রাখা, নয়তো unsigned পাশটাকে ইচ্ছে করে নামিয়ে আনা, যখন তুমি জানো ওটা যথেষ্ট ছোট।
Cast, আর তার দুইটা সৎ ব্যবহার
Cast মানে রূপান্তরটা তুমি নিজে, লিখে করছ। ওটা lesson 5-র table-র 2 নম্বর সারি, তাই ওটা যেকোনো অঙ্কের চেয়ে শক্ত করে ধরে।
Cast operator
(type)value
(double)total / count একটা পাশকে double করো, তারপর ভাগ করো
(int)price ইচ্ছে করে double-র পূর্ণ অংশটা রাখো
(unsigned int)n signed একটা মানকে unsigned হিসেবে পড়ো
- ওটা ডান পাশের একটামাত্র মানের উপর খাটে, পুরো expression-র উপর না।
(double)total / countশুধুtotal-কেই বদলায়। - ওটুকুই যথেষ্ট: এক পাশ
doubleহয়ে গেলে মই অন্য পাশটাকেও তুলে আনে। (double)(total / count)আলাদা জিনিস, আর ওটাই bug, সমাধান না।
দুইটা ব্যবহার সৎ। প্রথমটা হলো আসল ভাগে বাধ্য করা, যেটা lesson 1 একটা 3.0 দিয়ে করেছিল আর ভাজক variable হলে করতে পারত না।
দ্বিতীয়টা এমন একটা narrowing যেটা তুমি চাও আর লিখে রাখতে রাজি। (int)price বলে, "আমি জানি এতে ভগ্নাংশ হারাবে, আর আমি সেটাই চাই"।
#include <stdio.h>
int main(void)
{
int total = 239;
int count = 3;
double price = 99.87;
printf("no cast %d\n", total / count);
printf("cast left %.2f\n", (double)total / count);
printf("cast outside %.2f\n", (double)(total / count));
printf("to int %d\n", (int)price);
return 0;
}
no cast 79
cast left 79.67
cast outside 79.00
to int 99
তৃতীয় লাইনটাই সেই ভুল যেটা সমাধানের মতো দেখায়। Cast-র কাছে পৌঁছানোর আগেই ভাগটা integer division হিসেবে শেষ হয়ে গেছে।
তৃতীয় আরেকটা ব্যবহার, না বুঝে compiler-র warning চুপ করানো, সৎ না, আর এই track ওটা করে না।
তিনটা নীরব ক্ষতি, আর প্রতিটা কোথায় ঘটে
নিচের তিনটার একটাও Playground-র command line-এ কোনো বার্তা দেয় না। প্রতিটাই type-র ফাঁক দিয়ে একটা মান বেরিয়ে যাওয়া।
- Truncation. একটা
doubleint-এ রাখলে ভগ্নাংশ হারায়, আর ওটা round না করে শূন্যের দিকে কাটে। 99.87 হয়ে যায় 99। - Narrowing. চওড়া একটা পূর্ণসংখ্যা সরু একটায় রাখলে শুধু যতটুকু আঁটে ততটুকু bit থাকে। Signed type-এ মানটা না আঁটলে ফলটা implementation defined, আর GCC মুড়িয়ে দেয়: একটা
char-এ 300 হয়ে যায় 44। - চিহ্ন হারানো. ঋণাত্মক একটা মান unsigned-এ বদলালে বড় একটা ধনাত্মক মান হয়ে যায়। এটা standard-এ পুরোপুরি নির্ধারিত, আর তাতে ব্যাপারটা আরও খারাপ: কিছুই ভুল না, তাই কেউ নালিশও করতে পারে না।
স্থানীয় gcc -Wconversion প্রথম দুইটা ধরে: warning: conversion from 'double' to 'int' may change value। ওই flag -Wall-এ নেই, আর বেশির ভাগ project ওটা কখনো চালু করে না।
তাই রক্ষাটা কোনো compiler flag না। রক্ষাটা হলো মানটা যেখানে জন্মায় সেখানেই তার type বেছে নেওয়া, আর Module 2-র পুরো যুক্তিটাই ছিল সেটা।
দুইটা পূর্ণসংখ্যা ঢোকে, একটা আসল গড় বেরোয়।
#include <stdio.h>
int main(void)
{
int total = 0;
int count = 0;
scanf("%d %d", &total, &count);
printf("%.2f\n", (double)total / count);
return 0;
}
79.67
ওই output-টা 239 3 input-র জন্য। বাঁ পাশের মানে একটা cast, আর বাকিটা মই করে দেয়।
একই মানকে একবার signed সীমার সাথে, একবার unsigned সীমার সাথে মেলানো।
#include <stdio.h>
int main(void)
{
int count = 0;
scanf("%d", &count);
printf("against 1 %d\n", count < 1);
printf("against 1u %d\n", count < 1u);
printf("cast fixes %d\n", count < (int)1u);
return 0;
}
against 1 1
against 1u 0
cast fixes 1
ওই output-টা -1 input-র জন্য। 0 দিলে তিন লাইনই মিলে যায়, আর এই জন্যই অসতর্ক একটা পরীক্ষায় এটা ধরা পড়ে না।
টাকা-পয়সার একটা দাম, একটা রসিদের দরকারি দুই পূর্ণসংখ্যায় ভাঙা।
#include <stdio.h>
int main(void)
{
double price = 0.0;
scanf("%lf", &price);
int taka = (int)price;
int paisa = (int)(price * 100) - taka * 100;
printf("%d taka %d paisa\n", taka, paisa);
return 0;
}
99 taka 87 paisa
ওই output-টা 99.87 input-র জন্য। দুইটা cast-ই ইচ্ছাকৃত আর দুইটাই লেখা আছে।
এটা ভঙ্গুরও, আর Module 2-র lesson 3 কারণটা বলেছে: কিছু দাম গুণ করলে তুমি যে পূর্ণ সংখ্যাটা আশা করেছিলে তার সামান্য নিচে নামে। আসল একটা দোকানের খাতা শুরু থেকেই পয়সায় গোনে আর দশমিক দাম কখনো রাখেই না।
Run in Compilerএটা কোথায় কাজে লাগে
- প্রতিটা report-র প্রতিটা গড়। যে dashboard 79.67-র জায়গায় 79 দেখায়, তার একটা cast বাদ পড়েছে, আর নতুন code-এ এটাই সবচেয়ে চেনা সংখ্যার bug।
size_tদিয়ে index করা।sizeofআর C library-র প্রতিটা দৈর্ঘ্য unsigned। Signed একটা index নিয়ে শূন্যের নিচে নামা আর ওগুলোর একটার সাথে তুলনা করা loop-টাই Maria-র bug, Module 9-এ।- Byte স্তরের code। একটা network বা file parser
unsigned charপড়ে আরint-এ কাজ করে, integer promotion-র কারণে। ওই জোড়াটা ভুল হলেই parser ঋণাত্মক দৈর্ঘ্য পড়তে শুরু করে। - Ariane 5, 1996। Module 2-র lesson 2 এই গল্পটা বলেছিল: 64 bit-র একটা দশমিক মান 16 bit-র signed একটা পূর্ণসংখ্যায় ঢোকানো হয়েছিল, যেটা ওটা ধরতে পারত না। উৎক্ষেপণের 37 সেকেন্ড পরে রকেটটা ধ্বংস করা হয়।
যে ভুলগুলো সবাই করে
১. Signed একটা মানকে unsigned একটার সাথে মেলানো।
int count = -1;
printf("%d\n", count < 1u);
Playground-এ নীরব; স্থানীয় gcc -Wall বলে warning: comparison of integer expressions of different signedness: 'int' and 'unsigned int'। এটা 0 ছাপে। দুই পাশই signed রাখো।
২. মানের বদলে উত্তরটাকে cast করা।
int total = 239;
printf("%.2f\n", (double)(total / 3));
কোনো command line-এই বার্তা নেই, আর ছাপে 79.00। ভাগটা শেষ হওয়ার পরে cast-টা এসেছে। (double)total / 3 লেখো।
৩. একটা double-কে int-এ রেখে round আশা করা।
double price = 99.87;
int whole = price;
printf("%d\n", whole);
দুইটা command line-এই নীরব; স্থানীয় gcc -Wconversion বলে warning: conversion from 'double' to 'int' may change value। এটা 99 ছাপে, 100 না। Round করতে <math.h>-র round নাও।
৪. যে মান আঁটে না সেটা সরু একটা type-এ রাখা।
int big = 300;
char narrow = (char)big;
printf("%d\n", narrow);
কোনো command line-এই বার্তা নেই, কারণ cast-টা বলছে তুমি ইচ্ছে করেই করেছ। Playground-এ এটা 44 ছাপে, আর ওই ফলটা নিয়ম না, implementation defined। সরু করার আগে সীমাটা মিলিয়ে নাও।
David পাঁচটা পূর্ণ মার্কের গড় চায়, বাস্তব সংখ্যা হিসেবে ছাপা।
Input. এক লাইনে পাঁচটা পূর্ণসংখ্যা।
Output. এক লাইনে তাদের গড়, ঠিক দুই ঘর দশমিক পর্যন্ত।
Constraints. প্রতিটা মার্ক 0 আর 100-র মধ্যে।
Sample. Input 75 82 82 90 61 দিলে 78.00।
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
int c = 0;
int d = 0;
int e = 0;
scanf("%d %d %d %d %d", &a, &b, &c, &d, &e);
/* Add as integers. Then one cast, on one operand of the division. */
return 0;
}
এই module-এ গ্রেড হয় না। একবার ইচ্ছে করে cast-টা ভাগের বাইরে বসাও, ভুল উত্তরটা দেখার জন্য।
Run in CompilerAmara মিলিয়ে দেখছে ছাপার আগেই ও একটা expression-র type বলে দিতে পারে কি না।
Input. এক লাইনে দুইটা পূর্ণসংখ্যা n আর m।
Output. তিন লাইন: n / m %d দিয়ে, তারপর n / (double)m %.2f দিয়ে, তারপর n * 1.0 / m %.2f দিয়ে।
Constraints. -1000 <= n <= 1000, আর 1 <= m <= 1000।
Sample. Input 239 3 দিলে 79, তারপর 79.67, তারপর 79.67।
#include <stdio.h>
int main(void)
{
int n = 0;
int m = 0;
scanf("%d %d", &n, &m);
/* Three lines. The last two reach the same answer by different routes. */
return 0;
}
এই module-এ গ্রেড হয় না। চালানোর আগে মুখে বলো তিনটা ভাগের প্রতিটা কোন type-এ হলো।
Run in CompilerKenji একটা রসিদের লাইন লিখছে আর দশমিক একটা দাম দুই পূর্ণসংখ্যা হিসেবে চায়।
Input. এক লাইনে একটা দশমিক মান, দশমিকের পরে বড়জোর দুই অঙ্ক।
Output. এক লাইনে পূর্ণ অংশ, একটা space, তারপর দুই অঙ্কের ভগ্নাংশটা পূর্ণসংখ্যা হিসেবে।
Constraints. 0 <= দাম <= 100000, দশমিকের পরে বড়জোর দুই ঘর।
Sample. Input 99.87 দিলে 99 87।
#include <stdio.h>
int main(void)
{
double price = 0.0;
scanf("%lf", &price);
/* Two deliberate casts, and one subtraction between them. */
return 0;
}
এই module-এ গ্রেড হয় না। দ্বিতীয় cast-র আগে 0.5 যোগ করো আর বলো তাতে কোন input-গুলোর উত্তর বদলায়।
Run in CompilerMaria-র import-র পরে কিছু গোনা ঋণাত্মক হয়ে গেছে, আর সীমার পরীক্ষাটাকে সেটা সামলাতে হবে।
Input. এক লাইনে দুইটা পূর্ণসংখ্যা: signed একটা count আর unsigned হিসেবে দেওয়া একটা সীমা।
Output. দুই লাইন: সীমাটা unsigned রেখে count < limit তুলনার মান, তারপর signed অঙ্কে ঠিকভাবে করা তুলনার মান।
Constraints. -1000000 <= count <= 1000000, আর 0 <= limit <= 1000000।
Sample. Input -1 1 দিলে 0, তারপর 1।
#include <stdio.h>
int main(void)
{
int count = 0;
unsigned int limit = 0;
scanf("%d %u", &count, &limit);
/* First line: no cast. Second line: one cast on the limit. */
return 0;
}
unsigned-fix নামে গ্রেড হয়। ঋণাত্মক নয় এমন প্রতিটা count-এ দুই লাইনই মেলে, তাই লুকানো test-গুলো ওখানেই যেখানে চিহ্নটা বদলায়।
সচরাচর যে প্রশ্নগুলো আসে
Cast কি variable-টাকে বদলে দেয়?
না। ওটা শুধু ওই একটা expression-র জন্য বদলানো একটা মান বানায়। Variable-এ যা ছিল তাই থাকে।
(double)total / count-এ একটাই cast লাগে কেন?কারণ বাকিটা মই করে দেয়। এক পাশ
doubleহয়ে গেলে C ভাগ করার আগে অন্য পাশটাকেও তুলে মিলিয়ে নেয়।তাহলে কি
unsignedব্যবহার করাই খারাপ?না, bit-র জন্য আর ঋণাত্মক হতে পারে না এমন গোনার জন্য ওটাই ঠিক type। বিপদটা কেবল ওই সীমানায়, যেখানে ওর সাথে signed একটা মানের দেখা হয়।
কাটার বদলে round করব কীভাবে?
<math.h>-রroundনাও, তারপর cast করো। Cast-র আগে 0.5 যোগ করা কেবল ঋণাত্মক নয় এমন মানে চলে, আর ওই আধা-উত্তরটা চেনা একটা bug।-Wconversionচালু করা উচিত?নতুন একটা project-এ চেষ্টা করে দেখো। ওটা এত লাইনের নালিশ করে যেগুলো আসলে ঠিক, যে কারণে ওটা
-Wall-এ নেই আর বেশির ভাগ দল ওটা বন্ধ রাখে।
মূল কথা
- প্রায় প্রতিটা operator-র আগে C দুই পাশকে একটা সাধারণ type-এ বদলে নেয়।
charআরshort-কেint-এ তোলা হয়, তাই ওদের ভিতরে কখনো অঙ্ক হয় না।- মই নিচের মানটাকে তোলে;
int-র পাশেunsigned intথাকলে ফল unsigned। - ঋণাত্মক মান unsigned-এ বদলে খুব বড় ধনাত্মক হয়ে যায়, নীরবে আর বৈধভাবে।
- Cast ডান পাশের একটামাত্র মানে খাটে, তাই ওটা বসে মানের গায়ে, উত্তরের গায়ে না।
- Truncation, narrowing আর চিহ্ন হারানো,
-Wconversionছাড়া তিনটাই নীরব।
এরপর আসছে module-র গ্রেড হওয়া সেট, যেখানে এই ছয়টা lesson দশটা problem আর একটা judge-র মুখোমুখি হয়।
lesson ৬ শেষ
শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।
পরেরটা: Problem: যে expression কামড় দেয়