نمایش داده و خطای عددی؛ از بیت و مبنا تا انتخاب نوع داده
در این درس ۸ بخش
در مسئلهٔ الگوریتم، انتخاب داده بخشی از راهحل است. کد دانشجویی «00137»، تعداد ۱۳۷ پرونده و مبلغ ۱۳۷ تومان ظاهری شبیه دارند، اما یک کار انجام نمیدهند. اولی شناسه است، دومی شمارش و سومی مقدار با واحد پول. اگر همه را بیفکر به عدد تبدیل کنیم، ممکن است صفرهای شناسه از بین برود یا واحدها مخلوط شوند. هدف این درس، ساختن مدار یا حفظکردن جدول همهٔ کدهای متن نیست؛ میخواهیم بفهمیم نمایش محدود داده چگونه روی مقایسه، محاسبه و پذیرش ورودی اثر میگذارد. پیش از هر مثال، مقدار و واحد خروجی را حدس بزنید و بعد اجرا کنید.
بیت، بایت و وزن جایگاه
بیت واحدی با دو حالت متمایز است که آنها را صفر و یک مینامیم. در این درس هر بایت هشت بیت دارد. با هشت بیت میتوان ۲۵۶ الگوی متفاوت ساخت؛ اگر قرارداد ما عدد صحیح بدون علامت باشد، آن الگوها میتوانند مقدار صفر تا ۲۵۵ را نشان دهند. همان الگوها با قراردادی دیگر میتوانند نقش دیگری داشته باشند. از روی یک رشته بیت بهتنهایی نمیتوان فهمید با عدد، رنگ یا بخشی از متن روبهرو هستیم. نوع داده و قالب فایل، بخشی از تفسیر لازم را فراهم میکنند.
در مبنای ده، جایگاهها از راست وزن یک، ده، صد و بیشتر دارند. در مبنای دو، وزنها یک، دو، چهار، هشت، شانزده و به همین ترتیباند. برای 11010 از راست، دو و هشت و شانزده روشناند؛ مجموع آنها ۲۶ میشود. صفرِ جایگاه چهار به معنی اضافهنشدن آن وزن است، نه حذف جایگاه. اگر همین متن را بدون اعلام مبنا بخوانیم، ممکن است آن را یازده هزار و ده تصور کنیم. در پاسخ تشریحی همواره مبنا را در متن یا برچسب روشن بنویسید.
مثال اول: تبدیل دودویی با اجرای دستی
بیتهای ورودی: 1 1 0 1 0
مقدار اولیه: value ← 0
برای هر بیت از چپ به راست:
value ← 2 × value + مقدار همان بیت
ردگیری value: 1 → 3 → 6 → 13 → 26
خروجی دهدهی: 26چرا این روش کار میکند؟ هر بار که رقم تازه از سمت راست به عدد قبلی اضافه میشود، وزن رقمهای قبلی دو برابر میشود؛ سپس رقم تازه با وزن یک اضافه میشود. این توضیح از حفظکردن تنها خروجی مهمتر است. مثال زیر عمداً ورودی ثابت و معتبر دارد؛ هنوز تابعی برای پذیرش هر متن دلخواه نیست. در تابع عمومی، ورودی خالی، نویسهای غیر از صفر و یک و طول بیش از قرارداد را باید بررسی کرد. عبارت Number(digit) در اینجا فقط برای رقم از قبل شناختهشده استفاده شده است.
import assert from 'node:assert/strict';
const bits = '11010';
let value = 0;
for (const digit of bits) {
value = value * 2 + Number(digit);
}
assert.equal(value, 26);
assert.equal(value.toString(2), bits);
assert.equal(0b11010, 26);
console.log(value);
console.log(value.toString(2));بلوک مستقل را در 02-binary.mjs ذخیره کنید. دو سطر خروجی، 26 و 11010 هستند. تبدیل برگشتی با toString(2) نمایش مبنای دو را میسازد، اما صفرهای آغازین را برای شما نگه نمیدارد. مقدار 26 با نمایش 00011010 همان عدد است. وقتی قالب داده دقیقاً هشت بیت میخواهد، طول نمایش هم قرارداد جداگانهای دارد. همچنین نوشتن 0b11010 در کد، یک literal دودویی است؛ این به معنی آن نیست که متغیر JavaScript از نظر حافظه فقط پنج بیت اشغال میکند.
عدد ریاضی با نوع عددی برنامه یکسان نیست
در ریاضی میتوان دربارهٔ عددهای صحیح دلخواه صحبت کرد؛ در برنامه باید محدودیت نمایش را شناخت. نوع Number در JavaScript از قالب ممیز شناور دودویی با دقت دوگانه استفاده میکند. همهٔ عددهای صحیحِ محدودهٔ امن آن دقیق و از هم قابل تمایزند، اما بیرون از آن محدوده ممکن است دو صحیح متفاوت به یک مقدار نمایشپذیر تبدیل شوند. امنبودن ورودیها نیز کافی نیست: حاصل جمع یا ضرب بعدی ممکن است از محدوده خارج شود. برای یک الگوریتم عددی، محدودیت عمل را همراه محدودیت داده بنویسید.
عدد اعشاری ساده در مبنای ده، لزوماً نمایش متناهی در مبنای دو ندارد. همانطور که یکسوم در دهدهی به رقمهای تکراری میرسد، بعضی کسرها در نمایش دودویی به تقریب نیاز دارند. در نتیجه، 0.1 + 0.2 دقیقاً با مقدار Number مربوط به 0.3 برابر نمیشود. این رفتار خرابی جمع یا ضعف یک رایانهٔ خاص نیست؛ نتیجهٔ مدل نمایش است. انتخاب آستانهٔ اختلاف، گردکردن یا واحد پایه باید از نیاز مسئله بیاید، نه از افزودن بیتوضیح یک عدد بسیار کوچک به همهٔ مقایسهها.
مثال دوم: دقت عدد و هویت شناسه
import assert from 'node:assert/strict';
assert.equal(0.1 + 0.2 === 0.3, false);
assert.equal(10 + 20, 30);
assert.equal(Number.isSafeInteger(9007199254740991), true);
assert.equal(Number.isSafeInteger(9007199254740992), false);
assert.equal(9007199254740992 === 9007199254740993, true);
const studentId = '00137';
assert.equal(Number(studentId).toString(), '137');
console.log(0.1 + 0.2);
console.log(10 + 20);
console.log(studentId);فایل 02-precision.mjs سه سطر 0.30000000000000004، سپس 30 و سپس 00137 چاپ میکند. مقایسهٔ دو literal بزرگ در این مثال درست برمیگردد، چون هر دو پیش از مقایسه به یک مقدار قابل نمایش گرد شدهاند؛ این شاهدی است که مقایسهٔ موفق همیشه تفاوت ورودیهای ریاضی را حفظ نمیکند. مدخل #number برای نحو نوع عددی و #data-types برای تفکیک نوعها مفید است. BigInt برای بعضی محاسبات صحیح بزرگ وجود دارد، اما جایگزین خودکار Number یا راهحل عمومی پول و اعشار نیست؛ در این دوره محدودهها را کوچک و صریح نگه میداریم.
متن نیز به قرارداد رمزگذاری نیاز دارد
نویسهٔ نمایشی، واحد رشته و بایت یک چیز نیستند. Unicode چارچوبی برای نشانهها فراهم میکند؛ UTF-8 یک روش رمزگذاری آنها به بایت است. رشتهٔ JavaScript بر اساس واحدهای UTF-16 سنجیده میشود. برای متن سادهٔ لاتین ممکن است چند شمارش یکسان شود و همین اتفاق ما را به تعمیم نادرست بکشاند. در متن فارسی یا ایموجی تفاوت آشکارتر است. اگر محدودیت فایل بر حسب بایت است، استفاده از طول رشته بدون اعلام واحد کافی نیست. اگر مسئله تعداد حروف قابل مشاهده است، حتی شمارش code point هم همیشه پایان کار نیست.
import assert from 'node:assert/strict';
const word = 'سلام';
const bytes = new TextEncoder().encode(word);
assert.equal(word.length, 4);
assert.equal(bytes.length, 8);
assert.equal(new TextDecoder().decode(bytes), word);
const cat = '🐱';
assert.equal(cat.length, 2);
assert.equal([...cat].length, 1);
assert.equal(new TextEncoder().encode(cat).length, 4);
console.log(JSON.stringify({ units: word.length, utf8Bytes: bytes.length }));
console.log(JSON.stringify({ units: cat.length, codePoints: [...cat].length }));خروجی فایل 02-text-bytes.mjs نشان میدهد «سلام» چهار واحد رشته و هشت بایت UTF-8 دارد. ایموجی گربه دو واحد UTF-16، یک code point و چهار بایت UTF-8 دارد. مثال فقط تفاوت واحدها را ثابت میکند و الگوریتم عمومی شمارش همهٔ واحدهای نوشتاری نیست. TextEncoder کدگذاری UTF-8 را انجام میدهد و TextDecoder در این دادهٔ معتبر آن را برمیگرداند. نگهداری متن را با چسباندن دستی عدد کد حروف پیاده نکنید؛ ابزار استاندارد و قرارداد روشن، اشتباه را کمتر میکنند.
مقدار بولی و انتخاب ورودی مناسب
نتیجهٔ پرسشهایی مانند «تعداد نامنفی است؟» یک مقدار بولی، درست یا نادرست، است. این نتیجه با خود تعداد فرق دارد. برای پذیرش تعداد شرکتکننده باید هم نوع و هم صحیحبودن و محدوده بررسی شوند. مقدار «تعداد داریم» نباید بهجای تعداد واقعی ذخیره شود. در تصمیمهای بعدی، روشنبودن این تفاوت به خواندن شرط کمک میکند. مدخل #boolean را برای نمایش این نوع مرور کنید؛ جدول تصمیم درس چهارم نشان میدهد ترکیب چند پرسش بولی چگونه به شاخهٔ اجرایی میرسد.
نمونهسؤال و پاسخ تشریحی
عدد دودویی 101101 را به دهدهی تبدیل کنید. چرا 00101101 مقدار عددی دیگری ایجاد نمیکند، ولی طول نمایش فرق دارد؟
پاسخ تشریحی: وزنهای روشن ۳۲، ۸، ۴ و ۱ هستند؛ مجموع ۴۵ است. صفرهای اضافهٔ سمت چپ مقدار عدد صحیح نامنفی را عوض نمیکنند، اما نمایش را از شش بیت به هشت بیت میرسانند. اگر قرارداد، یک بایت بدون علامت باشد، نمایش هشتبیتی مفید است. از این مثال نباید نتیجه گرفت نوع Number در JavaScript دقیقاً با همان هشت بیت ذخیره میشود.
برای کد دانشجویی «00082»، تعداد حاضرین کلاس و هزینهٔ ۱۲۵۰۰۰ تومان چه نوع داده و محدودیتی پیشنهاد میکنید؟
پاسخ تشریحی: کد دانشجویی متن است تا صفرهای آغازین و هویت آن حفظ شود. تعداد حاضرین عدد صحیح نامنفی و دارای سقف متناسب با کلاس است؛ عدد اعشاری یا منفی رد میشود. هزینه در قرارداد آموزشی، عدد صحیح تومان و دارای سقف معتبر است. یکسانبودن ظاهر رقمی، دلیل یکسانبودن معنا و نوع داده نیست. محدودیت را در مرز ورود بررسی کنید.
چرا نتیجهٔ 0.1 + 0.2 را نباید در همهٔ برنامهها با گردکردن تصادفی «اصلاح» کرد؟ آیا برابرشدن چاپی دو عدد، درستی محاسبه را ثابت میکند؟
پاسخ تشریحی: باید ابتدا واحد، دقت مجاز و سیاست گردکردن مسئله معلوم شود. کوتاهکردن نمایش ممکن است فقط خطا را پنهان کند و نتیجهٔ محاسبهٔ بعدی را تغییر ندهد. برای پول صحیحِ این دوره، ذخیرهٔ واحد پایهٔ صحیح سادهتر است، اما جمع و ضرب بعدی نیز باید در محدوده بماند. برابری متن چاپشده، اثبات برابری دقیق دو مقدار داخلی یا صحت مدل نیست.
اجرا، منبع و معیار تحویل
هر بلوک JavaScript مستقل است؛ آنها را با نام اعلامشده ذخیره کنید و با دستور node و نام فایل اجرا کنید. برای تحویل، یک جدول از شناسه، تعداد، طول و متن فارسی بسازید و واحد هرکدام را مشخص کنید. سپس تبدیل 11010 را بدون اجرای کد توضیح دهید و یک مثال از تفاوت نمایش عدد با هویت متن ارائه کنید. منابع رسمی برای مطالعهٔ بیشتر؛ تألیف و مثالهای مستقل: تحریریه درسآوند.
CS50x 2026 — Lecture 0: https://cs50.harvard.edu/x/notes/0/
MDN — Numbers and strings: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Numbers_and_strings
MDN — Number.isSafeInteger: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger
MDN — TextEncoder: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
فایل مثالها، راهنمای فارسی اجرا و عیبیابی، پروژهٔ گزارش نمرهها و آزمون جمعبندی تألیفی با پاسخ تشریحی، رایگان در صفحهٔ بستهٔ تمرین همین دوره ارائه میشود:
https://www.darsavand.com/resources/computer-algorithms-practice-kit
تألیف و مثالهای اختصاصی: تحریریه درسآوند.
متن درسهای عمومی را بدون ورود بخوانید.