يوجد نوعان من الأعداد في JavaScript:
- أعداد عادية تخزَّن بصيغة 64-بت IEEE-754، تُعرف أيضًا ب "الأعداد العشرية مضاعفة الدقة" (double precision floating point numbers). هذا النوع هو ما سنستعلمه أغلب الوقت وسنسلط عليه الضوء في هذا الفصل.
- أعداد صحيحة كبيرة (BigInt numbers) تمثِّل عددًا صحيحًا متغير الحجم، إذ قد نلجأ إليها أحيانًا لأن النوع السابق لا يمكن أن يتجاوز القيمة 2^53 أو أن تقل عن -2^53، وسنخصص لهذا النوع فصلًا خاصًا به نظرًا للحاجة إليه في حالات خاصة.
حاليًا، لِنتوسع عن ما نعرفه عنها، وننتقل إلى الحديث عن النوع الأول، الأعداد العادية.
طرائق أخرى لكتابة عدد
تخيل أننا نريد كتابة 1 بليون. الطريقة الواضحة هي:
let billion = 1000000000;
لكن، نتجنب غالبًا كتابة سلسلة طويلة من الأصفار في الحياة الواقعية لأنه من السهل الخطأ في ذلك ولكون ذلك الأمر يأخذ وقتًا أكثر. نكتب غالبًا شيئا مثل "1bn"
بدلًا من بليون أو "7.3bn"
بدلًا من 7 بليون و 300 مليون. يمكن تطبيق الأمر ذاته مع الأعداد الكبيرة.
نُقَصِّر أرقام الأعداد في JavaScript بإضافة الحرف "e"
للعدد وتحديد عدد الأصفار فيه:
// بليون، حرفيًا: 1 وجانبه 9 أصفار let billion = 1e9; alert( 7.3e9 ); // 7,300,000,000
بمعنى آخر، يضرب الشكل "XeY"
العدد X في 1
متبوعًا بعدد Y من الأصفار.
1e3 = 1 * 1000 1.23e6 = 1.23 * 1000000
لنكتب الآن شيئَا صغيرًا جدًا. مثلًا، جزء من المليون من الثانية:
let ms = 0.000001;
كما قمنا سابقًا، يمكن استخدام "e"
لتجنب كتابة الأصفار، يمكننا القول:
let ms = 1e-6; // ستة أصفار على يسار 1
إن قمنا بعد الأصفار في 0.000001
، سنجد عددها 6. لذا يكون الرقم 1e-6
.
بمعنى آخر، وجود رقم سالب بعد "e"
يعني القسمة على 1 متبوعًا بِعدد الأصفار المعطى:
// بالقسمة على 1 متبوعًا ب 3 أصفار 1e-3 = 1 / 1000 (=0.001) // بالقسمة على 1 متبوعًا ب 6 أصفار 1.23e-6 = 1.23 / 1000000 (=0.00000123)
الأعداد الست عشرية، والثنائية والثمانية
تُستخدَم الأعداد الست عشرية بكثرة في JavaScript لتمثيل الألوان، وتشفير الأحرف ولأشياء أخرى عديدة. لذا فإن هناك طريقة أقصر لكتابتها وذلك بوضع السابقة 0x
ثم الرقم. مثلًا:
alert( 0xff ); // 255 alert( 0xFF ); // 255 (العدد ذاته, لا يوجد اختلاف باختلاف حالة الأحرف)
تستخدم الأنظمة الثنائية والثمانية نادرًا، لكنها مدعومة أيضًا باستخدام السابقة 0b
والسابقة 0o
على التوالي:
let a = 0b11111111; // الهيئة الثنائية لِلعدد 255 let b = 0o377; // الهيئة الثمانية لِلعدد 255 alert( a == b ); // صحيح، العدد ذاته 255 في كلا الجانبين
يوجد ثلاثة أنظمة عددية فقط مدعومة بالشكل السابق. يجب استخدام الدالة parseInt
لباقي الأنواع (سنشرحها لاحقًا في هذا الفصل).
toString(base)
يُرجِع التابع num.toString(base)
تمثيلًا نصيًا للمتغير num
إلى النظام العددي المُعطى base
. مثلًا:
let num = 255; alert( num.toString(16) ); // ff alert( num.toString(2) ); // 11111111
يمكن أن تختلف قيمة base
من 2
حتى 36
، والقيمة الافتراضية هي 10
.
حالات الاستخدام الشائعة:
-
base=16
: تستخدم للألوان الست عشرية، وتشفير الأحرُف وغيرها، قد تحوي الخانات الأرقام0..9
أو الأحرفA..F
. -
base=2
: يستخدم بكثرة في تصحيح العمليات الدقيقة، يمكن أن يحوي الرقمين0
أو1
. -
base=36
: هو الحد الأعلى، يمكن أن يحوي الأرقام0..9
أو الأحرُفA..Z
. يمكن استخدام جميع الأحرف اللاتينية لتمثيل عدد. قد يبدو أمرًا ممتعًا لكن يكون مفيدًا في حال احتجنا لتحويل معرف عددي طويل إلى عدد أقصر، مثلًا، لتقصير رابط url. يمكن تمثيله بالنظام العددي ذي الأساس36
:
alert( 123456..toString(36) ); // 2n9c
نقطتين لِاستدعاء تابع
لاحظ أن النقطتين في 123456..toString(36)
ليست خطأ كتابي. إن أردنا استدعاء تابع مباشرة على عدد/ مثل التابع toString
في المثال أعلاه، فسنحتاج إلى نقطتين بعد العدد ..
.
إن وضعنا نقطة واحدة فقط 123456.toString(36)
فسيكون هناك خطأ، لأن JavaScript سَتعتبر أن النقطة هي فاصلة عشرية وأن ما بعدها هو جزء عشري للعدد. فإذا وضعنا نقطة أخرى فستعرف أن الجزء العشري فارغ وتنتقل إلى الدالة.
يمكن كتابتها بهذه الطريقة أيضًا (123456).toString(36)
.
التقريب (Rounding)
أحد الخصائص الأكثر استخدامًا عند التعامل مع الأعداد هي التقريب. يوجد العديد من الدوال المدمجة للتقريب:
-
Math.floor
: تقريب للجزء الأصغر:3.1
تصبح3
، و-1.1
تصبح-2
. -
Math.ceil
: تقريب للجزء الأكبر:3.1
تصبح4
، و-1.1
تصبح-1
. -
Math.round
: تقريب لأقرب عدد صحيح:3.1
تصبح3
،3.6
تصبح4
و-1.1
تصبح-1
. -
Math.trunc
(ليست مدعومة بواسطة المتصفح Internet Explorer): تحذف أي شيء بعد الفاصلة العشرية بدون تقريب:3.1
تصبح3
، -1.1
تصبح-1
.
يختصر الجدول في الأسفل الاختلافات بين هذه التوابع:
Math.floor
|
Math.ceil
|
Math.round
|
Math.trunc
|
|
---|---|---|---|---|
3.1 | 3 | 4 | 3 | 3 |
3.6 | 3 | 4 | 4 | 3 |
-1.1 | -2 | -1 | -1 | -1 |
-1.6 | -2 | -1 | -2 | -1 |
تُعَطِّي هذه التوابع جميع الاحتمالات الممكنة للتعامل مع الجزء العشري للعدد، لكن ماذا إن كنا نريد تقريب العدد إلى خانة محدَّدة بعد الفاصلة العشرية؟
مثلًا، لدينا العدد 1.2345
ونريد تقريب إلى خانتين لنحصل على 1.23
فقط. يوجد طريقتين للقيام بذلك:
1- الضرب والقسمة: مثلًا، لتقريب الرقم إلى الخانة الثانية بعد الفاصلة العشرية، يمكننا ضرب العدد في 100
، ثم نستدعي تابع التقريب ثم نقسم على نفس العدد.
let num = 1.23456; alert( Math.floor(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
2- يقرب التابع toFixed(n)
العدد المستدعى معه إلى الخانة n
بعد الفاصلة العشرية ويُرجِع تمثيلًا نصيًا للنتيجة.
let num = 12.34; alert( num.toFixed(1) ); // "12.3"
يعمل التابع على تقريب العدد للأكبر أو الأصغر وفقًا إلى أقرب قيمة، مثل التابع Math.round
:
let num = 12.36; alert( num.toFixed(1) ); // "12.4"
لاحظ أن مخرجات التابع toFixed
هي نص. إن كان الجزء العشري أقل من المطلوب، تُضاف الأصفار إلى نهاية الرقم:
let num = 12.34; alert( num.toFixed(5) ); // "12.34000", أصفار مضافة لجعل عدد الخانات 5
يمكننا تحويل المخرجات إلى عدد باستخدام الجمع الأحادي أو باستدعاء الدالة Number()
: +num.toFixed(5)
.
حسابات غير دقيقة
يُمَثَّل العدد داخليًا بصيغة 64-بِت IEEE-754، لذا يوجد 64 بِت لتخزين العدد: تستخدم 52 منها لتخزين أرقام العدد، و 11 منها لتخزين مكان الفاصلة العشرية (تكون أصفارًا للاعداد الصحيحة)، و 1 بِت لإشارة العدد.
إن كان العدد كبيرًا جدًا، فَسيزداد عن مساحة التخزين 64-بِت، معطيًا ما لا نهاية:
alert( 1e500 ); // ما لا نهاية
ما قد يكون أقل وضوحًا، ويحدث غالبًا هو ضياع الفاصلة. لاحظ الاختبار الخطأ التالي:
alert( 0.1 + 0.2 == 0.3 ); // خطأ
الجملة السابقة تحدث فعليًا، إن فحصنا ما إن كان مجموع 0.1
و 0.2
هو 0.3
، نحصل على false
. غريب أليس كذلك؟! ما النتيجة إذًا إن لم تكن 0.3
؟
alert( 0.1 + 0.2 ); // 0.30000000000000004
يوجد خطأ آخر هنا غير الموازنة الخطأ. تخيل أننا نقوم بموقع للتسوق الالكتروني، ووضع الزائر بضائع بقيم $0.10
و $0.20
في السلة. سيكون مجموع الطلب $0.30000000000000004
. مما قد يفاجئ أي أحد.
لكن السؤال الأهم، لم يحدث هذا؟
يُخَزَّن العدد في الذاكرة بهيئته الثنائية، سلسلة من البِت - واحدات وأصفار. لكن الأجزاء مثل 0.1
و 0.2
والتي تبدو بسيطة بالنسبة للنظام العددي العشري هي في الحقيقة أجزاء غير منتهية في النظام الثنائي.
بمعنى آخر، ما هو 0.1
؟ هو واحد مقسوم على عشرة 1/10
، عُشر. ويكون من السهل تمثيلة بنظام الأعداد العشري. موازنة بالثلث: 1/3
. الذي يصبح بكسور غير منتهية 0.33333(3)
.
لذا، فإن من المؤكد أن تعمل الأعداد المقسومة على مضاعفات العدد 10
في النظام العشري، ولا تعمل المقسومة على 3
. وكذلك أيضًا في النظام الثنائي، تعمل الأعداد المقسومة على مضاعفات العدد 2
، لكن يصبح العدد 1/10
كسورًا ثنائية غير منتهية.
لا يوجد طريقة لتخزين العدد ذاته 0.1 أو 0.2 بالضبط باستخدام النظام الثنائي، كما لا يمكن تخزين الثلث كجزء عشري.
يحل نمط الأعداد IEEE-754 هذه المشكلة بتقريب العدد إلى أقرب عدد ممكن. لا تتيح لنا قواعد التقريب هذه رؤية خسارة الأجزاء الصغيرة، لكنها تكون موجودة. يمكننا رؤية ذلك فعليًا:
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
وعند جمع عددين، فإن الأجزاء المفقودة تظهر.
هذا يفسر لِمَ 0.1 + 0.2
لا تساوي 0.3
بالضبط.
ليس فقط في JavaScript
هذه المشكلة موجودة في العديد من اللغات البرمجية الأخرى مثل PHP، و Java، و C، و Perl، و Ruby تُعطي النتيجة ذاتها، لأنها تعتمد على الصيغة العددية ذاتها.
هل يمكننا تجنب المشكلة؟ بالطبع، أفضل طريقة هي بتقريب النتيجة بمساعدة التابع toFixed(n)
:
let sum = 0.1 + 0.2; alert( sum.toFixed(2) ); // 0.30
يرجى ملاحظة أن toFixed
تُرجِع نصًا دائمًا. وتتأكد من وجود خانتين فقط بعد العلامة العشرية. هذا يجعل الأمر مريحًا إن كان لدينا موقع تسوق إلكتروني وأردنا عرض $0.30
. يمكننا استخدام الجمع الأحادي في الحالات الأخرى لتحويله إلى عدد:
let sum = 0.1 + 0.2; alert( +sum.toFixed(2) ); // 0.3
يمكننا أيضا ضرب الأعداد مؤقتًا في 100 أو أي عدد أكبر لتحويل إلى أعداد صحيحة ثم إعادة قسمتها على العدد المضروب فيه. هكذا قد ترتفع نسبة الخطأ كما لو كنا نتعامل مع أعداد صحيحة لكننا سنتخلص منها بالقسمة:
alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001
لذا، يقلل نهج الضرب والقسمة الخطأ لكنه لا يزيلة كليًا.
قد نحاول تجنب الأجزاء كليًا في بعض الأحيان. كما لو كنا نتعامل مع متجر إلكتروني، حتى نتمكن من تخزين الأسعار بالسنت بدلًا من الدولار. لكن ماذا إن وضعنا تخفيضًا ب 30%؟ لنكن أكثر دقة، يكون تجنب الأجزاء كليًا نادرًا جدًا. قم بتقريبها فقط لتتخلص من الأجزاء الغير المرغوبة عند الحاجة.
شر البلية ما يضحك
جرب تشغيل ما يلي:
// مرحبًا! أنا عدد يزداد من تلقاء نفسه! alert( 9999999999999999 ); // يظهر 10000000000000000
هذه الحالة تعاني من المشكلة ذاتها: ضياع الدقة. يوجد 64 بِت للعدد، يمكن استخدام 52 منها لتخزين العدد، لكنها غير كافية. لذا يظهر الرقم الأخير.
لا تعرض JavaScript خطأ في مثل هذه الحالات فهي تقوم بأفضل ما لديها لملائمة العدد للصيغة المطلوبة. لكن، لسوء الحظ هذه الصيغة ليست كبيرة كفاية.
صفران
نتيجة أخرى سلبية للتمثيل الداخلي للأعداد هو وجود صفرين 0
و -0
. ذلك لأن الإإشارة تُمَثَّل ببِت مستقل، لذا فيمكن لأي عدد أن يكون سالبًا أو موجبًا بما في ذلك الصفر.
لا يكون هذا الفرق ملحوظًا في أغلب الحالات، لأن المعامِلات مُعَدَّة لتعاملهما كعدد واحد.
الفحص: isFinite و isNaN
هل تذكر القيم العددية الخاصة التالية؟
-
Infinity
(و-Infinity
) هي قيمة عددية خاصة تكون أكبر أو أصغر من أي شيء. -
NaN
تُمَثِّل وجود خطأ (ليس عددًا Not a Number).
تنتمي هذه القيم إلى النوع number
، لكنها ليست أعداد، لذا يوجد توابع خاصة لفحصها:
-
isNaN(value)
يُحوِّل المُعامل إلى عدد ثم يفحص ما إن كانNaN
:
alert( isNaN(NaN) ); // true alert( isNaN("str") ); // true
لكن هل نحتاج لهذا التابع؟ أليس من الممكن استخدام الموازنة فقط === NaN
؟ الإجابة للأسف هي لا. القيمة NaN
هي فريدة ولا يمكن أن تساوي أي شيء، حتى نفسها:
alert( NaN === NaN ); // false
-
isFinite(value)
يُحوِّل مُعامله إلى عدد ويُرجِع القيمةtrue
إن كان عددًا عاديًا، أي لا يكونNaN
أوInfinity
أو-Infinity
:
alert( isFinite("15") ); // true alert( isFinite("str") ); // false, NaN alert( isFinite(Infinity) ); // false, Infinity
تستخدم isFinite
أحيانًا للتحقق ما إن كان النص عددًا عاديًا:
let num = +prompt("Enter a number", ''); // أو قيمة غير عددية -Infinity أو Infinity سيكون صحيحًا إلا إن أدخلت alert( isFinite(num) );
يرجى ملاحظة أن الفراغ أو المسافة الواحدة تُعامل معاملة الصفر 0
في جميع التوابع العددية بما فيها isFinite
.
الموازنة باستخدام Object.is
يوجد تابع خاص مدمج في اللغة يدعى Object.is
يوزان القيم كما ===
لكنه أكثر موثوقية لسببين:
-
أنه يعمل مع
NaN
: أي "Object.is(NaN, NaN) === true"
وهذا أمر جيد. -
القيمتان
0
و-0
مختلفتان: "Object.is(0, -0) === false"
، الأمر صحيح تقنيًا، لأن العدد لديه إشارة داخليًا مما يجعل القيم مختلفة حتى لو كانت باقي الخانات أصفارًا.
يكون التابع Object.is(a, b)
نفس a === b
في باقي الحالات.
تُستخدم طريقة الموازنة هذه غالبًا في توصيف JavaScript. عندما تحتاج خوارزمية لموازنة كون قيمتين متطابقتان تمامًا فإنها تستخدم Object.is
(تُسَمَّى داخليًا القيمة ذاتها "SameValue").
parseInt و parseFloat
التحويل العددي باستخدام الجمع +
أو Number()
يعد صارمًا. إن لم تكن القيمة عددًا فإنها تفشل:
alert( +"100px" ); // NaN
الاستثناء الوحيد هو المسافات الفارغة ببداية أو نهاية النص، إذ يتم تجاهلها.
لكن، يوجد لدينا في الواقع قيمًا بالوحدات، مثل "100px"
أو "12pt"
في CSS. في العديد من الدول أيضا، يُلحَق رمز العملة بالقيمة، فمثًلا، لدينا "19€"
ونريد استخراج قيمة عددية من ذلك. هذا ما يقوم به التابعان parseInt
و parseFloat
، إذ يقرآن العدد من النص المعطى حتى تعجزان على ذلك فتتوقف العملية. في حال وجود خطأ، يعيدان العدد المُجَمَّع. فيعيد التابع parseInt
عددًا صحيحًا، بينما يعيد التابع parseFloat
عددًا عشريًا:
alert( parseInt('100px') ); // 100 alert( parseFloat('12.5em') ); // 12.5 // يُرجَع العدد الصحيح فقط alert( parseInt('12.3') ); // 12 // تُوقِف النقطة الثانية عملية التحليل alert( parseFloat('12.3.4') ); // 12.3
يوجد بعض الحالات يرجع فيها التابع parseInt
أو parseFloat
القيمة NaN
وذلك عندما لا يوجد أي رقم لإرجاعه. ففي المثال التالي، يوقف الحرف الأول العملية:
alert( parseInt('a123') ); // NaN
المُعامِل الثاني للتابع parseInt(str, radix)
لدى التابع parseInt()
مُعامِل ثانٍ اختياري. والذي يحدد نوع النظام العددي، لذا يمكن للتابع parseInt
تحويل النصوص ذات الأعداد الست عشرية، الثنائية وهكذا:
alert( parseInt('0xff', 16) ); // 255 alert( parseInt('ff', 16) ); // 255 alert( parseInt('2n9c', 36) ); // 123456
دوال رياضية أخرى
تحتوي JavaScript على الكائن المُدمَج Math الذي يحتوي على مكتبة صغيرة بالدوال والثوابت الرياضية. إليك بعض الأمثلة:
-
Math.random()
: تُرجِع عددًا عشوائيًا من 0 إلى 1 (لا تتضمن 1):
alert( Math.random() ); // 0.1234567894322 alert( Math.random() ); // 0.5435252343232 alert( Math.random() ); // ... (أي رقم عشوائي)
-
Math.max(a, b, c...)
/
Math.min(a, b, c...)
: تُرجِع القيمة الأكبر أو الأصغر من المُعامِلات:
alert( Math.max(3, 5, -10, 0, 1) ); // 5 alert( Math.min(1, 2) ); // 1
-
Math.pow(n, power)
: تُرجع العددn
مرفوعًا إلى الأُس المُعطى:
alert( Math.pow(2, 10) ); // 2^10 = 1024
يوجد المزيد من الدوال والثوابت في الكائن Math
، بما فيها علم المُثَلَّثات، والتي يمكنك ايجادها في توثيق الكائن Math.
الخلاصة
لكتابة أعداد كبيرة:
-
أضِف
"e"
مع عدد الأصفار الخاصة بالعدد المطلوب، مثل:123e6
هو123
مع 6 أصفار. -
قيمة سالبة بعد
"e"
تقسم العدد على 1 مع عدد الأصفار المُعطى.
لِأنظمة العد المختلفة:
-
يمكن كتابة الأعداد مباشرة بالنظام الستعشري (
0x
)، أو الثُماني (0o
)، أو الثُنائي (0b
). -
parseInt(str, base)
تحوِّل النصstr
إلى عدد صحيح بالنظام العددي المُعطىbase
، و2 ≤ base ≤ 36
. -
num.toString(base)
تحوِّل العدد إلى نص بالنظام العددي المُعطىbase
.
لتحويل القيم مثل 12pt
and 100px
إلى عدد:
-
استخدم
parseInt
أوparseFloat
لتحويل سلس، والتي تقرأ العدد من نص وتُرجِع القيمة التي استطاعت قرائتها قبل حصول أي خطأ.
للأجزاء:
-
التقريب باستخدام
Math.floor
، أوMath.ceil
، أوMath.trunc
، أوMath.round
أوnum.toFixed(precision)
. - تذكر وجود ضياع في دقة الجزء العشري عند التعامل مع الكسور.
للمزيد من الدوال الرياضية:
- اطلع على الكائن Math عندما تحتاج ذلك، هذه المكتبة صغيرة جدًا، لكنها تغطي الاحتياجات الأساسية.
المهام
جمع الأعداد من الزائر
الأهمية: 5
انشِئ سكربت يتيح للمستخدم ادخال رقمين ثم أعرض مجموعهما.
الحل
let a = +prompt("The first number?", ""); let b = +prompt("The second number?", ""); alert( a + b );
لاحظ عامل الجمع الأحادي +
قبل prompt
. يحوِّل القيم إلى أعداد. وإلا فإن a
و b
ستكون نصوصًا وسيكون مجموعهما بدمجهما: "1" + "2" = "12"
.
لماذا 6.35.toFixed(1) == 6.3؟
الأهمية: 4
تُدَوِّر كلًا من Math.round
و toFixed
العدد إلى أقرب عدد له وفقًا للتوثيق: الأجزاء من 0..4
تُدَوَّر للأسفل، بينما الأجزاء 5..9
تثدَوَّر للأعلى.
مثلًا:
alert( 1.35.toFixed(1) ); // 1.4
في المثال المشابه أدناه، لِمَ تُدَوَّر 6.35
إلى 6.3
، وليس 6.4
؟
alert( 6.35.toFixed(1) ); // 6.3
كيف نُدَوِّر 6.35
بالطريقة الصحيحة؟
الحل
الجزء 6.35
هو عبارة عن عدد غير منتهي في الصيغة الثنائية. وكجميع الحالات المشابهة، يُخَزَّن مع ضياع في الدقة. لنرَ:
alert( 6.35.toFixed(20) ); // 6.34999999999999964473
قد يتسبب ضياع الدقة في زيادة أو نقصان أي عدد. يكون العدد في هذه الحالة أقل بقليل من قيمته الفعلية، ولهذا يُدَوَّر للأسفل. ماذا عن العدد 1.35
؟
alert( 1.35.toFixed(20) ); // 1.35000000000000008882
جعل ضياع الدقة هذا الرقم أكبر بقليل مما هو عليه مما تسبب في تقريبه للأعلى.
كيف يمكننا حل مشكلة تقريب العدد 6.35
حتى يُدَوَّر بالشكل الصحيح
يجب أن نحوله إلى عدد صحيح قبل التقريب:
alert( (6.35 * 10).toFixed(20) ); // 63.50000000000000000000
لاحظ عدم وجود أي ضياع في دقة العدد 63.5
. ذلك لأن الجزء العشري 0.5
يساوي 1/2
. يمكن تمثيل الأجزاء المقسومة على 2
تُمَثَّل بشكل صحيح في النظام الثنائي. يمكننا تقريب العدد الآن:
alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(rounded) -> 6.4
كرر حتى يصبح المُدخَل عددًا
الأهمية: 5
أنشِئ الدالة readNumber
والتي تطلب من الزائر إدخال عدد حتى يقوم بإدخال قيمة عددية صحيحة. يجب أن تكون القيمة المُرجَعة عددًا.
يمكن للزائر إيقاف العملية بإدخال سطر فارغ أو الضغط على "CANCEL". يجب أن تُرجِع الدالة null
في هذه الحالة.
الحل
function readNumber() { let num; do { num = prompt("Enter a number please?", 0); } while ( !isFinite(num) ); if (num === null || num === '') return null; return +num; } alert(`Read: ${readNumber()}`);
طريقة الحل معقدة قليلًا حتى نتمكن من معالجة حالات الأسطر الفارغة/null
. لذا فإن الشيفرة تستقبل المدخلات حتى تكون عددًا عاديًا. تُطَبِّق كلًا من null
(cancel) والسطر الفارغ شروط الأعداد كونها تساوي القيمة العددية 0
.
بعد توقف الشيفرة يجب معاملة null
والأسطر الفارغة بطريقة خاصة (إرجاع null
). لأن تحويلها إلى أعداد يُرجِع 0
.
حلقة غير منتهية أحيانًا
الأهمية: 4
الحلقة التالية غير منتهية، ولا تتوقف أبدًا. لماذا؟
let i = 0; while (i != 10) { i += 0.2; }
الحل
ذلك لأن i
لن يساوي 10
أبدًا. نفذ الشيفرة التالية لرؤية قيم i
:
let i = 0; while (i < 11) { i += 0.2; if (i > 9.8 && i < 10.2) alert( i ); }
لا توجد قيمة تساوي 10
تمامًا. تحدث مثل هذه الأمور بسبب ضياع الدقة عند إضافة الأجزاء مثل 0.2
. الخلاصة، تجنب التحقق من المساواة عند التعامل مع الأجزاء العشرية.
رقم عشوائي من العدد الأدنى إلى الأقصى
الأهمية: 2
تُنشِئ الدالة Math.random()
المُضَمَنَة في اللغة قيمة عشوائية بين 0
و 1
(ليس بما في ذلك 1
). اكتب الدالة random(min, max)
لتوليد عدد عشري عشوائي من min
إلى max
(بما لا يتضمن max
).
أمثلة عن عملها:
alert( random(1, 5) ); // 1.2345623452 alert( random(1, 5) ); // 3.7894332423 alert( random(1, 5) ); // 4.3435234525
الحل
نريد تعيين جميع القيم من الفترة 0…1 إلى القيم من min
إلى max
. يمكن القيام بذلك في مرحلتين:
-
إذا ضربنا قيمة عشوائية من 0…1 في
max-min
. فإن فترة القيم الممكنة تزيد0..1
إلى0..max-min
. -
إذا أضفنا
min
الآن، تصبح الفترة منmin
إلىmax
.
الدالة:
function random(min, max) { return min + Math.random() * (max - min); } alert( random(1, 5) ); alert( random(1, 5) ); alert( random(1, 5) );
قيمة صحيحة عشوائية من min إلى max
الأهمية: 2
أنشِئ دالة randomInteger(min, max)
تقوم بتوليد قيمة صحيحة عشوائية من min
إلى max
بما في ذلك min
و max
.
يجب أن يظهر كل رقم من الفترة min..max
بفرص متساوية. مثال على طريقة العمل:
alert( randomInteger(1, 5) ); // 1 alert( randomInteger(1, 5) ); // 3 alert( randomInteger(1, 5) ); // 5
يمكنك استخدام حل المثال السابق كأساس لهذه المهمة.
الحل
الطريقة السهلة والخطأ
الحل الأسهل لكنه خطأ سيكون بتوليد قيمة من min
إلى max
وتقريبها:
function randomInteger(min, max) { let rand = min + Math.random() * (max - min); return Math.round(rand); } alert( randomInteger(1, 3) );
الدالة تعمل لكنها خطأ. احتمال ظهور القيم الطرفية min
و max
أقل بمرتين من باقي القيم. إن شغلنا المثال أعلاه لعدة مرات، فينرى ظهور 2
بصورة أكبر.
يحدث ذلك لأن Math.round()
تأخذ رقما من الفترة 1..3
وتُدَوِرها كما يلي:
values from 1 ... to 1.4999999999 become 1 values from 1.5 ... to 2.4999999999 become 2 values from 2.5 ... to 2.9999999999 become 3
نلاحظ الآن أن لدى 1
قيم أقل بمرتين من 2
وكذلك 3
.
الطريقة الصحيحة
يوجد العديد من الطرق الصحيحة لحل هذه المهمة. إحداها هو بتعديل حدود الفترة. للتأكد من وجود فرص متساوية، نُوَلِّد قيمًا من 0.5
إلى 3.5
، ثم إضافة الاحتمالات الممكنة للأطراف:
function randomInteger(min, max) { // (max+0.5) إلى (min-0.5) التقريب الآن من let rand = min - 0.5 + Math.random() * (max - min + 1); return Math.round(rand); } alert( randomInteger(1, 3) );
طريقة بديلة هي استخدام الدالة Math.floor
لرقم عشوائي من min
إلى max+1
:
function randomInteger(min, max) { // (max+1) إلى min التقريب الآن من let rand = min + Math.random() * (max + 1 - min); return Math.floor(rand); } alert( randomInteger(1, 3) );
جميع الفترات أصبحت متوازنة الآن:
values from 1 ... to 1.9999999999 become 1 values from 2 ... to 2.9999999999 become 2 values from 3 ... to 3.9999999999 become 3
لدى جميع الفترات الطول ذاته مما يجعل التوزيع النهائي موحدًا.
ترجمة -وبتصرف- للفصل Numbers من كتاب The JavaScript Language
اقرأ أيضًا
- المقال التالي: السلاسل النصية (strings)
- المقال السابق: توابع الأنواع الأولية
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.