Learn C Programming

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

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

Arithmetic, integer division আর modulo-র ফাঁদ

Freeপড়া

এই lesson-এ যা শিখবে

  • পূর্ণসংখ্যা আর double, দুইটার উপরেই +, -, *, / আর % চালাতে পারবে।
  • শুধু দুই পাশের মান দেখে বলতে পারবে C কোন ভাগটা করবে।
  • program চালানোর আগেই ভাগশেষের চিহ্ন কী হবে সেটা বলে দিতে পারবে।

David তিনটা মার্ক পড়ে, যোগ করে, তারপর 3 দিয়ে ভাগ করে। মার্ক তিনটা 75, 82 আর 82। ওর program ছাপে 79।

ও কাগজে যোগটা মিলিয়ে দেখে: 239। ভাগটাও মিলিয়ে দেখে: 79.67। program মার্ক নিয়ে ভুল করেনি, আর round-ও করছে না। ও যেটা চেয়েছিল, তার বদলে অন্য একটা ভাগ করছে।

Zara লাইনটার দিকে দুই সেকেন্ড তাকিয়ে বলে, "তিনটাই তো int"। পুরো lesson-টা ওই কথাটাই।

পাঁচটা operator, আর অদ্ভুত মাত্র একটা

C-তে পাঁচটা arithmetic operator আছে। তার চারটা স্কুলে যেমন শিখেছ ঠিক তেমনই চলে।

পাঁচটা arithmetic operator

a + b     যোগ
a - b     বিয়োগ
a * b     গুণ
a / b     ভাগফল        দুই পাশেই পূর্ণসংখ্যা হলে integer division
a % b     ভাগশেষ        শুধু পূর্ণসংখ্যায়, double-এ কখনো না
  • %-কে বলে modulo বা remainder operator। যা পড়ে থাকে সেটা দেয়।
  • % double নেয় না। 7.5 % 2 compile-ই হয় না।
  • C-তে ঘাতের কোনো operator নেই। 2 ^ 8 256 না, আর ওটা আসলে কী সেটা lesson 4 বলবে।

প্রতিটাই দুইটা মান নেয় আর তৃতীয় একটা মান বানায়। তুমি = না লেখা পর্যন্ত কিছুই জমা হয় না।

#include <stdio.h>

int main(void)
{
    int a = 17;
    int b = 5;

    printf("sum        %d\n", a + b);
    printf("difference %d\n", a - b);
    printf("product    %d\n", a * b);
    printf("quotient   %d\n", a / b);
    printf("remainder  %d\n", a % b);
    return 0;
}
sum        22
difference 12
product    85
quotient   3
remainder  2

শেষ দুই লাইন একসাথে পড়ো। 17-র মধ্যে তিনটা পাঁচ আছে আর 2 পড়ে থাকে, আর C তোমাকে ওই কথাটার দুই টুকরা আলাদা করে দেয়।

ভাগটা ঠিক করে দুই পাশের মান, বাক্স না

David-র এই নিয়মটাই দরকার ছিল। C শুধু /-র দুই পাশের মান দেখে, আর কিছুই দেখে না।

দুইটাই পূর্ণসংখ্যা হলে C করে integer division: ভাগ করে, আর ভগ্নাংশটা ফেলে দেয়। যেকোনো একটা double হলে C আসল ভাগটা করে।

ফলটা তুমি কোন ধরনের বাক্সে রাখছ তার কোনো ভূমিকা নেই। assignment ঘটার আগেই ভগ্নাংশটা হারিয়ে গেছে।

#include <stdio.h>

int main(void)
{
    int total = 239;

    double wrong = total / 3;
    double right = total / 3.0;

    printf("wrong %.2f\n", wrong);
    printf("right %.2f\n", right);
    return 0;
}
wrong 79.00
right 79.67

দুই লাইনই একটা double-এ জমা করে। কিন্তু দ্বিতীয়টার কাছেই শুধু জমা করার মতো ভগ্নাংশ ছিল।

তাই সমাধানটা =-র বাঁ পাশে কখনো না। ওটা ডান পাশে, আর ওটা এক অক্ষরের: 3.0 লেখো, নয়তো একটা cast দাও, যেটা lesson 6 ঠিকমতো দেখাবে।

Integer division কাটে শূন্যের দিকে

"ভগ্নাংশটা ফেলে দেয়" কথাটায় আর একটা শব্দ লাগে, যখন উত্তরটা ঋণাত্মক।

C নিচের দিকে round করে না। ও শূন্যের দিকে কাটে। তাই 7 / 2 হলো 3, আর -7 / 2 হলো -3, -4 না।

Integer division দুই দিক থেকেই শূন্যের দিকে কাটে দুইটা উত্তরই শূন্যের দিকে সরে, কখনো ঋণাত্মক অসীমের দিকে না 0 -4 -3 -2 2 3 4 -3.5 -7 / 2 হলো -3 3.5 7 / 2 হলো 3

এটা C99 ঠিক করে দিয়েছে। পুরনো C ঋণাত্মক ক্ষেত্রটা compiler-র উপর ছেড়ে দিত, আর সেই কথা লেখা বই এখনো পাবে।

তাই integer division মানে "নিচে round করা" না। মানে "ভগ্নাংশ ফেলে দেওয়া", আর প্রতিটা ঋণাত্মক সংখ্যায় এই দুই কথা আলাদা।

ভাগশেষের চিহ্ন বাঁ পাশের মানের চিহ্ন ধরে

ভাগ যখন শূন্যের দিকে কাটে, %-র চিহ্নটা অঙ্ক থেকেই বেরিয়ে আসে। C কথা দেয় যে (a / b) * b + a % b সমান a।

উল্টো দিক থেকে ভাবলে নিয়মটা পরিষ্কার: a % b-র চিহ্ন হয় a-র চিহ্ন, b যাই হোক।

Expressionভাগফলভাগশেষভাগশেষের চিহ্ন
7 / 3, 7 % 3217 ধরে
-7 / 3, -7 % 3-2-1-7 ধরে
7 / -3, 7 % -3-217 ধরে
-7 / -3, -7 % -32-1-7 ধরে
#include <stdio.h>

int main(void)
{
    printf("  7 / 3 = %2d    7 %% 3 = %2d\n", 7 / 3, 7 % 3);
    printf(" -7 / 3 = %2d   -7 %% 3 = %2d\n", -7 / 3, -7 % 3);
    printf("  7 / -3 = %2d   7 %% -3 = %2d\n", 7 / -3, 7 % -3);
    printf(" -7 / -3 = %2d  -7 %% -3 = %2d\n", -7 / -3, -7 % -3);
    return 0;
}
  7 / 3 =  2    7 % 3 =  1
 -7 / 3 = -2   -7 % 3 = -1
  7 / -3 = -2   7 % -3 =  1
 -7 / -3 =  2  -7 % -3 = -1

Format string-র ভিতরে %% একটা শতকরা চিহ্ন ছাপে। ওখানে একটা % দিলে ওটা specifier শুরু করে দিত।

দ্বিতীয় সারিটা দুইবার পড়ো। -7 % 3 হলো -1, আর অনেক ভাষা এখানে 2 বলে। Python বলে। C বলে না।

তাই Python-র অভ্যাস থেকে লেখা "সংখ্যাটাকে 0 থেকে 2-র মধ্যে আনো" লাইনটা C-তে ঋণাত্মক input-এ ভাঙে।

শুধু / আর % দিয়ে অঙ্ক আলাদা করা

এই দুইটা operator মিলে একটা সংখ্যাকে টুকরা করে। % 10 দেয় শেষ অঙ্কটা; / 10 ওটা সরিয়ে দেয়।

#include <stdio.h>

int main(void)
{
    int code = 4728;

    printf("last digit      %d\n", code % 10);
    printf("tens digit      %d\n", code / 10 % 10);
    printf("without the last %d\n", code / 10);
    printf("first digit     %d\n", code / 1000);
    return 0;
}
last digit      8
tens digit      2
without the last 472
first digit     4

দশকের লাইনটা বাঁ থেকে ডানে পড়ো: আগে 10 দিয়ে ভাগ, তারপর ভাগশেষ। / আর %-র precedence সমান, তাই ওরা লেখার ক্রমেই চলে।

তাই অঙ্ক নিয়ে কাজ করতে loop বা string কিছুই লাগে না। Module 6 যেকোনো সংখ্যক অঙ্কের উপর loop চালাবে; আজ সংখ্যাটা আগে থেকেই জানা।

শূন্য দিয়ে ভাগ, দুই রকম, আর কোনোটাই নিয়ম না

পূর্ণসংখ্যাকে শূন্য দিয়ে ভাগ করা undefined behaviour। Standard এখানে program-র উপর কোনো শর্তই দেয় না।

Playground-এ program কিছু ছাপার আগেই থেমে যায় আর run-টা ব্যর্থ বলে দেখায়। ওটা যা হতে পারে তার একটা, ভরসা করার মতো কিছু না।

Floating point-র গল্প আলাদা। Playground-র compiler IEEE 754 মানে, যেখানে শূন্য দিয়ে ভাগ করলে অসীম আসে।

#include <stdio.h>

int main(void)
{
    double zero = 0.0;

    printf("%f\n", 1.0 / zero);
    printf("%f\n", -1.0 / zero);
    return 0;
}
inf
-inf

শূন্যকে শূন্য দিয়ে ভাগ করলে আসে nan, মানে "সংখ্যা না"। ওটার বানান library-ভেদে বদলায়, তাই এখানে কোনো output block ওটার দাবি করছে না।

তাই সৎ কথাটা হলো: ভাজকটা তুমি নিজে পরীক্ষা করো। কোনো আচরণই এমন না যার উপর design দাঁড় করানো যায়।

Example 1: দুই রকম ভাগ দেখানোর সবচেয়ে ছোট program

একই জোড়া সংখ্যা, দুইবার ভাগ, মাঝে এক অক্ষরের তফাত।

#include <stdio.h>

int main(void)
{
    printf("%d\n", 9 / 4);
    printf("%.2f\n", 9 / 4.0);
    return 0;
}
2
2.25

প্রথম লাইনটা দ্বিতীয়টার round করা রূপ না। ওটা আলাদা একটা কাজ, যার চিহ্নটা মিলে গেছে।

Run in Compiler
Example 2: মিনিট থেকে ঘণ্টা আর মিনিট

সবচেয়ে চেনা জোড়া। / দেয় পূর্ণ অংশ, % দেয় যা পড়ে থাকে।

#include <stdio.h>

int main(void)
{
    int minutes = 0;
    scanf("%d", &minutes);

    int hours = minutes / 60;
    int left = minutes % 60;

    printf("%d h %d m\n", hours, left);
    return 0;
}
3 h 25 m

ওই output-টা 205 input-র জন্য। তিন ঘণ্টা মানে 180 মিনিট, আর বাকি 25 মিনিটে চতুর্থ ঘণ্টা হয় না।

Run in Compiler
Example 3: David যেটা চেয়েছিল, সেই গড়

তিনটা মার্ক ঢুকবে, একটা গড় বেরোবে, দুই ঘর দশমিক পর্যন্ত ছাপা হবে।

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    int c = 0;
    scanf("%d %d %d", &a, &b, &c);

    int total = a + b + c;
    double average = total / 3.0;

    printf("total   %d\n", total);
    printf("average %.2f\n", average);
    return 0;
}
total   239
average 79.67

ওই output-টা 75 82 82 input-র জন্য। যোগফলটা int থাকে, কারণ মার্ক পূর্ণসংখ্যা। শুধু ভাগটার একটা বাস্তব সংখ্যা দরকার, আর 3.0 সেটাই দেয়।

Run in Compiler

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

  • ঘড়ি। Linux kernel সেকেন্ডের একটা গোনাকে ঘণ্টা, মিনিট আর সেকেন্ডে ভাঙে ঠিক Example 2-র / আর % জোড়া দিয়ে। তুমি যত timestamp পড়েছ, সবই একটা বড় সংখ্যা হিসেবে শুরু হয়েছিল।
  • Pagination। এক পাতায় 20টা সারি দেখানো একটা তালিকা শেষ পাতার নম্বর বের করে (total + 19) / 20 দিয়ে। Integer division ভগ্নাংশ ফেলে দেয় বলেই কৌশলটা কাজ করে।
  • ISBN-র check digit। বইয়ের নম্বরের শেষ অঙ্কটা একটা ভারযুক্ত যোগফল আর % 11 দিয়ে বের করা হয়। Scanner ভুল টাইপ করা নম্বর কোনো database-কে জিজ্ঞেস করার আগেই ফিরিয়ে দেয়।
  • জোড় না বিজোড়। একটা game engine frame % 2-র উপর আচরণ বদলায়, আর SQLite একটা table-র মাপে % দিয়ে hash bucket বাছে। দুই জায়গাতেই ভাগশেষ সেই কাজটা করছে যেটা নইলে একটা loop করত।

যে ভুলগুলো সবাই করে

১. বাক্স বদলে ভাগটা ঠিক করার চেষ্টা।

double average = (75 + 82 + 82) / 3;
printf("%.2f\n", average);

কোনো command line-এই কোনো বার্তা নেই, আর ছাপে 79.00। double-র পালা আসার আগেই ভাগটা শেষ। 3-কে 3.0 করো।

২. দশমিক সংখ্যার ভাগশেষ %-র কাছে চাওয়া।

double price = 7.5;
printf("%f\n", price % 2);

GCC 12 বলে error: invalid operands to binary % (have 'double' and 'int')। এটা compile-র সময়েই আটকে যায়, আর সেটা দয়া। সত্যিই দরকার হলে <math.h>-র fmod নাও।

৩. ঋণাত্মক সংখ্যা থেকে ধনাত্মক ভাগশেষ আশা করা।

int index = -1;
printf("%d\n", index % 7);

কোনো command line-এই বার্তা নেই, আর ছাপে -1। একই লাইনে Python উত্তর দেয় 6। চুপ থাকাটাই এখানে সমস্যা: input ঋণাত্মক না হওয়া পর্যন্ত ভুল উত্তরটা ঠিক দেখায়।

৪. দুইয়ের ঘাত আট বোঝাতে 2 ^ 8 লেখা।

printf("%d\n", 2 ^ 8);

Playground-এর GCC 12 এখানে কিছুই বলে না, program build হয় আর ছাপে 10। GCC 13 বা তার পরেরগুলো কোনো flag ছাড়াই ধরে: warning: result of '2^8' is 10; did you mean '1 << 8' (256)?। C-তে ঘাতের operator নেই; ^ হলো lesson 4-র bitwise exclusive or।

মাথা খাটাও

David 7.0 আশা করছে, কারণ ও 2 দিয়ে ভাগ করে তারপর 2 দিয়ে গুণ করেছে।

#include <stdio.h>

int main(void)
{
    printf("%.1f\n", 7 / 2 * 2.0);
    return 0;
}

এটা ছাপে 6.0। ঠিক কোন কাজটা আগে হলো আর তার মান কত ছিল, সেটা বলো। তারপর কঠিন অংশটা: 2.0-কে সামনে এনে 2.0 * 7 / 2 লেখো আর বলো কী বদলাল আর কেন।

* আর /-র precedence সমান, তাই কড়াভাবে বাঁ থেকে ডানে পড়ো। প্রথম যে operator-টা পাচ্ছ, তার দুই পাশে কোন type বসে আছে জিজ্ঞেস করো।

অনুশীলন ১সহজ

Maria তিন অঙ্কের একটা locker code মিলিয়ে দেখছে আর অঙ্কগুলো আলাদা করে ছাপাতে চায়।

Input. এক লাইনে একটা পূর্ণসংখ্যা n।

Output. এক লাইনে তিনটা অঙ্ক, মাঝে একটা করে space।

Constraints. 100 <= n <= 999।

Sample. Input 407 দিলে 4 0 7।

#include <stdio.h>

int main(void)
{
    int n = 0;
    scanf("%d", &n);

    /* Hundreds with /, units with %, and the middle one needs both. */

    return 0;
}

digit-split নামে গ্রেড হয়। কোনো loop না, কোনো array না; তিনটা expression-ই পুরো program।

Run in Compiler
অনুশীলন ২সহজ

Kenji প্রতিটা video-র দৈর্ঘ্য পূর্ণ মিনিটে রাখে আর ওটা ঘণ্টা-মিনিটে দেখাতে চায়।

Input. এক লাইনে একটা পূর্ণসংখ্যা m, মিনিটের সংখ্যা।

Output. এক লাইনে ঘণ্টা, একটা space, তারপর পড়ে থাকা মিনিট।

Constraints. 0 <= m <= 1000000।

Sample. Input 205 দিলে 3 25।

#include <stdio.h>

int main(void)
{
    int m = 0;
    scanf("%d", &m);

    /* Example 2, with the output shape the statement asks for. */

    return 0;
}

এই module-এ গ্রেড হয় না। 0 আর 60 নিজে চালিয়ে দেখো; দুইটার উত্তরেই একটা শূন্য আছে।

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

Amara চারটা মার্ক জমা করে আর তার গড় চায়, গড়ের পূর্ণ অংশ না।

Input. এক লাইনে চারটা পূর্ণসংখ্যা।

Output. এক লাইনে গড়, ঠিক দুই ঘর দশমিক পর্যন্ত।

Constraints. প্রতিটা মার্ক 0 থেকে 100-র মধ্যে।

Sample. Input 75 82 82 90 দিলে 82.25।

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    int c = 0;
    int d = 0;
    scanf("%d %d %d %d", &a, &b, &c, &d);

    /* Add as integers. Divide by a number that has a decimal point. */

    return 0;
}

এই module-এ গ্রেড হয় না। আগে 4 দিয়ে চালাও, তারপর 4.0 দিয়ে, আর দেখো এক অক্ষরে উত্তর বদলে যায়।

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

Zara একটা scheduler পরীক্ষা করছে আর জানতে চায় একটা সংখ্যা আরেকটাকে পুরোপুরি ভাগ করে কি না।

Input. এক লাইনে দুইটা পূর্ণসংখ্যা n আর d।

Output. এক লাইনে 1, যদি d n-কে পুরোপুরি ভাগ করে, নয়তো 0।

Constraints. -1000000000 <= n <= 1000000000, আর 1 <= d <= 1000000000।

Sample. Input 91 7 দিলে 1। Input -91 6 দিলে 0।

#include <stdio.h>

int main(void)
{
    int n = 0;
    int d = 0;
    scanf("%d %d", &n, &d);

    /* A comparison is worth 1 or 0, so print it with %d. No if needed. */

    return 0;
}

is-divisible নামে গ্রেড হয়। লুকানো test-এ ঋণাত্মক n আছে, তাই ভাগশেষটা কীসের সাথে মেলাবে সেটা ভেবে ঠিক করো।

Run in Compiler

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

  • C সরাসরি সঠিক উত্তরটা দেয় না কেন?

    কারণ integer division আলাদা, দ্রুততর একটা যন্ত্র-নির্দেশ, আর বেশির ভাগ program পূর্ণ অংশটাই চায়। C তোমাকে অন্যটা আলাদা করে চাইতে বলে।

  • a / b * b কি সব সময় a হয়?

    পূর্ণসংখ্যায় না। 7 / 2 * 2 হলো 6। গুণটা দেখার আগেই ভগ্নাংশটা ফেলে দেওয়া হয়েছিল।

  • ভাগশেষ কখনো ঋণাত্মক হবে না, এমন করব কীভাবে?

    (n % d + d) % d লেখো। প্রথম ভাগশেষটা ঋণাত্মক হতে পারে, d যোগ করলে ওটা উঠে আসে, আর দ্বিতীয় % ওটাকে আবার সীমার মধ্যে নামায়।

  • long long-এ কি % ব্যবহার করা যায়?

    যায়। % প্রতিটা integer type-এ চলে, char আর unsigned সহ। শুধু floating type-গুলোই বাদ।

  • % কি *-র চেয়ে খরচি?

    বেশির ভাগ processor-এ ভাগ আর ভাগশেষই সবচেয়ে ধীর অঙ্ক। এটা কেবল খুব ব্যস্ত একটা loop-র ভিতরে গুরুত্ব পায়, আর Module 16 এতে ফিরে আসে।

মূল কথা

  • C-তে পাঁচটা arithmetic operator, আর % চলে শুধু পূর্ণসংখ্যায়।
  • / integer division কি না সেটা দুই পাশের মান ঠিক করে; গন্তব্যের type-র কোনো কথা নেই।
  • Integer division শূন্যের দিকে কাটে, তাই -7 / 2 হলো -3।
  • a % b নেয় a-র চিহ্ন, যেটা Python-র আচরণ না।
  • % 10 আর / 10 একটা সংখ্যাকে অঙ্ক ধরে ধরে খোলে।
  • পূর্ণসংখ্যাকে শূন্য দিয়ে ভাগ করা undefined behaviour; এখানে double-কে শূন্য দিয়ে ভাগ করলে inf আসে।

এরপর তুমি সংখ্যা নিয়ে হিসাব করা ছেড়ে সংখ্যা মেলাতে শুরু করবে, আর দেখা যাবে মেলানোর উত্তরটাও একটা সংখ্যা।

lesson ১ শেষ

শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।

পরেরটা: Relational আর logical operator, আর short circuit