Julia একটি সাধারণ উদ্দেশ্যের প্রোগ্রামিং ভাষা, যা বেশিরভাগ প্রোগ্রামিং কাজে ব্যবহার করা যায়। তবে বাস্তবে এর প্রধান ব্যবহারক্ষেত্রগুলো সাধারণত ইঞ্জিনিয়ারিং ও বিজ্ঞানে। দ্রুত, বহুমুখী ও অত্যাধুনিক সংখ্যাগত গণনাই এর ডিজাইনের কেন্দ্রে।
একটি ইন্টিজার হলো দশমিক বিন্দু ছাড়া একটি "পূর্ণ" সংখ্যা।
Basics ধারণায় আমরা দেখেছি যে, টাইপ উল্লেখ না করেই একটি ভ্যারিয়েবলে ইন্টিজার মান অ্যাসাইন করা যায়।
পড়তে সুবিধার জন্য অঙ্কের বিভাজক হিসেবে আন্ডারস্কোর ব্যবহার করা যায়। কম্পাইলার এগুলো উপেক্ষা করে।
julia> x = 3
3
julia> typeof(x)
Int64
julia> large_number = 1_234_567_890
1234567890
ভেতরে ভেতরে কম্পাইলার আপনার CPU-এর জন্য সবচেয়ে উপযুক্ত সাইনড ইন্টিজার টাইপটি বেছে নেবে। আধুনিক PC-তে এটি সাধারণত Int64 হয়, যা বেশিরভাগ কাজের জন্যই যথেষ্ট।
টাইপ নিয়ে ভবিষ্যতের একটি ধারণায় আরও বিস্তারিত আলোচনা হবে। আপাতত, numeric টাইপ সম্পর্কে আপনার ধারণা যথেষ্ট স্বচ্ছন্দ হলেই এই লেখাটি বুঝতে অসুবিধা হবে না।
পরের কোনো ধারণায় আমরা দেখব, Julia নিজে থেকেই খুব বড়, বহুমাত্রিক অ্যারে সমর্থন করে। এগুলো উপলব্ধ মেমরির পরিমাণ এবং ডেটা স্থানান্তরের ব্যান্ডউইথ, দুটোকেই কঠিন পরীক্ষায় ফেলে।
প্রোগ্রামারকে আরও সূক্ষ্ম নিয়ন্ত্রণ দিতে চাইলে কাঙ্ক্ষিত সাইনড বা আনসাইনড ইন্টিজার টাইপ উল্লেখ করা যায়, এবং এটি বেশ সাধারণ ব্যাপারও বটে। যাঁরা আগে Python ব্যবহার করেছেন, এমন অনেক Julia শিক্ষার্থী এটিকে সাধারণ Python-এর বদলে NumPy-র সঙ্গে তুলনা করতে চাইতে পারেন।
julia> y = Int8(42)
42
julia> typeof(y)
Int8
julia> z = UInt32(1024)
0x00000400
লক্ষ্য করুন, Julia ডিফল্টভাবে আনসাইনড ইন্টিজার হেক্সাডেসিমেল ফরম্যাটে দেখায়, কারণ আনসাইনড টাইপ প্রায়ই নিম্নস্তরের বিট অপারেশনে ব্যবহৃত হয়।
বাইনারি, অক্টাল বা হেক্সাডেসিমেল হিসেবেও ইন্টিজার লেখা যায়, যথাক্রমে 0b, 0o ও 0x প্রিফিক্স দিয়ে।
julia> a = 0x10
0x10
julia> Int(a)
16
julia> b = 0b1010 # displays as hexadecimal
0x0a
BigInt
প্রতিটি ইন্টিজার টাইপের একটি সর্বোচ্চ ও সর্বনিম্ন মান থাকে, যা এটি সংরক্ষণ করতে পারে:
julia> typemax(Int8)
127
julia> typemin(Int8)
-128
এই বৈধ সীমার বাইরে গেলে ইন্টিজার ওভারফ্লো ঘটে, যার ফলাফল দেখতে খুব অদ্ভুত লাগে।
julia> c = Int8(126)
126
julia> c * c
4
আমরা নিশ্চয়ই একমত হব যে 126 * 126-এর মান ৪ হওয়া উচিত নয়!
বিস্তারিত ব্যাখ্যার জন্য CPU রেজিস্টার কীভাবে ইন্টিজার সংরক্ষণ করে তা জানা দরকার, যা এই ধারণার পরিধির বাইরে।
"চওড়া" টাইপ ব্যবহার করে সমস্যাটি কমিয়ে আনা যায়, Int128 বা UInt128 পর্যন্ত (নিচে "চওড়া" বলতে কী বোঝায় তা আলোচনা করা হয়েছে)। তবে ওভারফ্লো সমস্যায় পড়ার আগে সবারই কঠিন সীমা আছে।
একটি সমাধান হলো BigInt টাইপ ব্যবহার করা, যা কেবল আপনার কম্পিউটারের মেমরি দিয়েই সীমাবদ্ধ। এটি আরবিট্রারি-প্রিসিশন পাটিগণিতের Julia-র বাস্তবায়ন।
julia> 2 ^ 70
0
julia> big = BigInt(2) ^ 70
1180591620717411303424
julia> UInt128(big)
0x00000000000000400000000000000000
যেহেতু ২-এর ৭০ ঘাত Int64-এ ধরে না, উপরের উদাহরণের প্রথম গণনাটি ব্যর্থ হয়। তবে লক্ষ্য করুন, কোনো এরর বার্তা আসে না। প্রোগ্রামার, সাবধান!
২-কে কাস্টিং করে BigInt বানালে সঠিক উত্তর পাওয়া যায়, আর হেক্সাডেসিমেলে দেখালে আরও স্পষ্ট হয় যে এটি ২-এরই একটি ঘাত।
আশ্চর্যের কিছু নেই যে ফ্লোটিং-পয়েন্ট সংখ্যায় ঐচ্ছিকভাবে একটি দশমিক বিন্দু থাকে, আর বিন্দুর পরে একটি ভগ্নাংশ অংশ থাকে।
julia> f = 3.45
3.45
julia> typeof(f)
Float64
অবশ্যই, সায়েন্টিফিক নোটেশন সমর্থিত।
julia> avogadro = 6.02e23
6.02e23
ইন্টিজারের মতোই, বেশিরভাগ কাজে ডিফল্ট টাইপই যথেষ্ট, তবে অন্য সাইনড টাইপও পাওয়া যায়। আনসাইনড ফ্লোটিং-পয়েন্ট টাইপ অবশ্য নেই।
শর্টকাট হিসেবে f0 সাফিক্স ব্যবহার করে Float32 মান তৈরি করা যায়।
julia> f32 = 4.56f0
4.56f0
julia> typeof(f32)
Float32
ইন্টিজারের মতোই, অন্য আকারের ফ্লোটের জন্য স্পষ্ট কাস্ট দরকার।
সর্বোচ্চ ও সর্বনিম্ন মান দেখে আপনি অবাক হতে পারেন:
julia> typemax(Float64)
Inf
julia> typemin(Float64)
-Inf
অসীম একটি বৈধ মান!
তবে ফ্লোটিং-পয়েন্ট সংখ্যার কাজের পরিসর সীমিত; খুব বড় কিছুকে কেবল Inf মান দেওয়া হয়, আর খুব ছোট কিছুকে 0.0-তে রাউন্ড করা হয়।
এই সীমাগুলো দেখতে আমরা আরেক জোড়া ফাংশন ব্যবহার করতে পারি: মোটামুটি ± 10 ^ 308।
julia> floatmax(Float64)
1.7976931348623157e308
julia> floatmin(Float64)
2.2250738585072014e-308
এছাড়া নির্ভুলতাও সীমিত; সীমিত কিন্তু বড় সংখ্যক উল্লেখযোগ্য অঙ্ক প্রকাশ করা হয় (Float64-এর জন্য প্রায় ১৫)।
Basics ধারণায় আলোচনা অনুযায়ী, গাণিতিক অপারেটর বেশিরভাগ ক্ষেত্রে শিশুদের শেখানো সাধারণ পাটিগণিতের মতোই কাজ করে। লক্ষ্য করুন, সূচকীকরণে ^ ব্যবহৃত হয়, ** নয় (অন্য ভাষায় দুটোই বেশি দেখা যায়)।
2 + 3 # 5 (addition)
2 - 3 # -1 (subtraction)
2 * 3 # 6 (multiplication)
8 / 2 # 4.0 (division)
8 % 3 # 2 (remainder)
2 ^ 3 # 8 (exponentiation)
তবে Julia-র কয়েকটি বিশেষ দিক নিয়ে আলোচনা করার মতো।
julia> x = 4.2
4.2
julia> 2 * x
8.4
julia> 2x
8.4
julia> 2.4x
10.08
এটা আপনাকে অবাক করতে পারে।
বেশিরভাগ অন্য কম্পিউটার ভাষার মতোই, ইনফিক্স অপারেটর হিসেবে * ব্যবহার করা সবসময়ই সম্ভব।
তবে Julia তৈরি করেছেন এমন মানুষ, যাঁরা মনে করেন কোড যতটা সম্ভব গাণিতিক সমীকরণের মতো দেখানো উচিত।
যেহেতু ভ্যারিয়েবলের নাম অবশ্যই একটি অক্ষর দিয়ে শুরু হতে হবে, তাই নামের আগে একটি সংখ্যা (ইন্টিজার বা ফ্লোটিং-পয়েন্ট) বসালে সেটিকে ইমপ্লিসিট গুণ হিসেবে ধরা হয়।
উদাহরণস্বরূপ, কোনো গোলকের পৃষ্ঠতলের ক্ষেত্রফল চাইলে 4 * pi * r * r-এর বদলে আমরা এভাবে করতে পারি:
julia> surface(r) = 4π * r^2
surface (generic function with 1 method)
julia> surface(3)
113.09733552923255
যদিও π একটি বিল্ট-ইন ধ্রুবক, এটি একটি (গ্রিক) অক্ষরও। তাই পার্সারের π-কে r থেকে আলাদা করতে এখনও একটি স্পষ্ট * দরকার।
ইনফিক্স অপারেটর হিসেবে / ব্যবহার করলে ইন্টিজার ইনপুট দিলেও ফলাফল সবসময় ফ্লোটিং-পয়েন্ট হবে।
ইন্টিজার ভাগের জন্য আরও কিছু বিকল্প আছে:
julia> 10 / 3
3.3333333333333335
julia> div(10, 3)
3
julia> 10 ÷ 3
3
julia> 10 // 3
10//3
div() ফাংশনটি ইন্টিজার ভাগের জন্য, যেখানে ফলাফল শূন্যের দিকে ছেঁটে ফেলা হয়: ধনাত্মক সংখ্যায় নিচের দিকে, ঋণাত্মক সংখ্যায় উপরের দিকে।
সমার্থক হিসেবে ইনফিক্স অপারেটর ÷ ব্যবহার করা যায়, এবারও লক্ষ্য এটিকে আরও গাণিতিক দেখানো। আপনি যদি Julia-সচেতন এডিটর ব্যবহার করেন, তবে এটি \div লিখে তারপর <Tab> চাপুন।
// অপারেটরের জন্য সিলেবাসের পরে আলাদা একটি ধারণা লাগবে।
আপাতত আমরা কেবল বলতে পারি, //-এর ফলাফল একটি "মূলদ" সংখ্যা, যাকে বেশিরভাগ মানুষ ভগ্নাংশ বলে ডাকে তারই আনুষ্ঠানিক নাম।
লব ও হর থেকে সাধারণ গুণনীয়ক বাদ দেওয়া হয়, যাতে দুটি ইন্টিজারের অনুপাত পাওয়া যায়, যাকে বলা হয় "লঘিষ্ঠ আকার"।
julia> rationalnum = 22 // 6
11//3
julia> typeof(rationalnum)
Rational{Int64}
আমাদের কাছে মূলদ সংখ্যা আছে। "অমূলদ" সংখ্যা কী?
julia> π
π = 3.1415926535897...
julia> typeof(π)
Irrational{:π}
একটি অমূলদ সংখ্যা হলো এমন একটি সংখ্যা, যাকে দুটি ইন্টিজারের অনুপাতে কমানো যায় না। সাধারণ উদাহরণের মধ্যে আছে π, e (অয়লারের সংখ্যা), এবং √2 (২-এর বর্গমূল) এর মতো অনেক মূল।
Julia গণিত করতে চায় যথাযথভাবে।
এটা প্রায়ই স্বয়ংক্রিয়ভাবে ঘটে:
julia> x = 2 + 3.5
5.5
julia> typeof(x)
Float64
আমরা একটি Int64-এর সঙ্গে একটি Float64 যোগ করলাম, আর ফলাফল এল Float64।
আসলে যোগ করার আগে ইন্টিজারটিকে নীরবে Float64-তে রূপান্তর করা হয়।
Julia-তে সংখ্যাগত টাইপের "প্রস্থ" বলে একটি ধারণা আছে।
Int64, Int16-এর চেয়ে চওড়া।একটি এক্সপ্রেশনে যদি বিভিন্ন টাইপ মেশানো হয়, তবে প্রয়োজন অনুযায়ী প্রতিটিকে ব্যবহৃত সবচেয়ে চওড়া টাইপে "প্রমোট" করা হয়।
রূপান্তর জোর করে ঘটাতে আমরা একটি ইন্টিজারকে নির্দিষ্ট টাইপে কাস্ট করতে পারি, যেমন Float64(5)।
অথবা কেবল float(5) ব্যবহার করুন, কম্পাইলারকে উপযুক্ত টাইপ বেছে নিতে দিন।
ফ্লোট থেকে ইন্টিজারে রূপান্তর অনিবার্যভাবে আরও জটিল। দশমিক বিন্দুর পরে যা থাকে তা নিয়ে আপনি কী করতে চান?
round() ফাংশনটি নিকটতম পূর্ণ সংখ্যায় রূপান্তর করে; ৪.৫-এর মতো সমান দূরত্বের ক্ষেত্রে নিকটতম জোড় পূর্ণ সংখ্যায় রাউন্ড হয়।floor() নিচের দিকে রাউন্ড করে, ceil() উপরের দিকে, trunc() শূন্যের দিকে।Int32() দিয়ে, একটি InexactError নিয়ে ব্যর্থ হবে।তবে ডিফল্টভাবে এই ফাংশনগুলো আপনার কাঙ্ক্ষিত ইন্টিজার টাইপটি রিটার্ন করে না। চাইলে পছন্দের আউটপুট টাইপ উল্লেখ করা যায়।
julia> round(4.5)
4.0
julia> round(Int64, 4.5)
4
julia> round(Int, 4.5) # => default integer type
4
julia> ceil(Int16, 4.3)
5
digits কিওয়ার্ড দিয়ে দশমিক বিন্দুর পরে নির্দিষ্ট সংখ্যক অঙ্কে রাউন্ড করাও সম্ভব।
julia> round(π, digits=10)
3.1415926536
আরও বিস্তারিত জানতে ম্যানুয়াল দেখুন।
এটা নিশ্চয়ই শুধু একটি এরর দেয়? আসলে বিষয়টা তত সহজ নয়।
÷ বা // দিয়ে ইন্টিজার ভাগ করলে আপনি প্রত্যাশা অনুযায়ীই একটি এরর হবে।
/ দিয়ে ফ্লোটিং-পয়েন্ট ভাগ করলে সাধারণ কম্পিউটার সায়েন্সের ধাঁচের বদলে যা ইঞ্জিনিয়ারিংয়ের ধাঁচ বলে মনে হতে পারে, সেটাই নেওয়া হয়:
julia> 2 / 0
Inf
julia> 0 / 0
NaN
আগের একটি অংশে আলোচনা অনুযায়ী, Julia-তে অসীম একটি বৈধ ফ্লোটিং-পয়েন্ট সংখ্যা, যা Inf দিয়ে প্রকাশ করা হয়।
লবও যদি শূন্য হয়, তবে ফলাফল গাণিতিকভাবে অনির্ধারিত। তখন Julia এটিকে "সংখ্যা নয়" হিসেবে ধরে, যা NaN দিয়ে প্রকাশ করা হয়।
এটা অদ্ভুত লাগলে, বাস্তব জগতের (তাই প্রায়ই বেশ এলোমেলো) বড় বড় অ্যারে নিয়ে কাজ করার প্রেক্ষাপটে ভাবুন। এগিয়ে যেতে হলে সমস্যাযুক্ত মানগুলো চিহ্নিত করে সরে যাওয়াই ভালো।
মানগুলো শেষহীনভাবে হাতে যাচাই করার প্রোগ্রাম লেখা কষ্টকর হবে, আর রানটাইম পারফরম্যান্সও নিশ্চিতভাবে খারাপ হবে। সামান্য গোলমালেই এরর বার্তা দিয়ে থেমে যাওয়া আপনার প্রোগ্রামকে ব্যবহারকারীদের কাছে খুবই অজনপ্রিয় করে তুলবে!
Conditionals ধারণায় বর্ণনা অনুযায়ী, সমতা পরীক্ষা সাধারণত == অপারেটর দিয়ে করা হয়।
ইন্টিজার, ক্যারেক্টার, স্ট্রিং ইত্যাদির জন্য এটি ভালোই কাজ করে। তবে ফ্লোটিং-পয়েন্ট মানের নির্ভুলতা সীমিত, আর একই ফলাফল হিসাব করার ভিন্ন ভিন্ন পদ্ধতিতে সামান্য ভিন্ন মান আসতে পারে। Float64-এর ক্ষেত্রে এটি সাধারণত ১৫তম উল্লেখযোগ্য অঙ্কের আশেপাশে হয়: সামান্য পার্থক্য, কিন্তু "সমতা" নয়।
প্রচলিতভাবে প্রোগ্রামারদের পরামর্শ হলো ফ্লোটিং-পয়েন্ট মানের সঙ্গে == কখনোই ব্যবহার করবেন না: ফলাফল অনির্দেশ্য।
বহুল ব্যবহৃত বিকল্প হলো পার্থক্যের পরম মানকে কিছু অনুমোদিত সহনসীমার (যাকে প্রায়ই এপসাইলন বা ϵ বলা হয়) সঙ্গে তুলনা করা। তাই a == b-এর বদলে abs(a - b) < epsilon ব্যবহার করুন।
Julia isapprox() ফাংশন দিয়ে আরও পরিচ্ছন্ন একটি বিকল্প দেয়।
পরম সহনসীমার জন্য সিনট্যাক্স হলো isapprox(a, b, atol=epsilon)। এই ক্ষেত্রে atol কিওয়ার্ডটি অবশ্যই লাগবে।
আপেক্ষিক সহনসীমা প্রায়ই বেশি কাজে লাগে। এটিই ডিফল্ট, তাই isapprox(a, b) rtol-এর জন্য কিছু যুক্তিসঙ্গত মান বেছে নেওয়ার চেষ্টা করে (নিয়মগুলো বেশ জটিল)।
তুলনা করা মানগুলোর ভগ্নাংশ হিসেবে আপেক্ষিক সহনসীমাও উল্লেখ করা যায়। তাই isapprox(a, b, rtol=0.01 পরীক্ষা করে যে মানগুলো একে অন্যের ১% মধ্যে আছে কি না।
Julia-র চিরাচরিত ধরনে, ডিফল্ট ক্ষেত্রের একটি সমার্থক গাণিতিক অপারেটর আছে: a ≈ b (অপারেটরটি \approx লিখে তারপর <tab> চেপে বসাতে হয়)।
মূলদ সংখ্যার পাশাপাশি, সিলেবাসের পরের অংশগুলোতে আলোচনা হবে:
2.3 + 4.5im।