٢٥ مارس ٢٠٢١

الأعداد

يوجد نوعان من الأعداد في JavaScript:

  1. أعداد عادية تخزَّن بصيغة 64-بت IEEE-754، تُعرف أيضًا ب “الأعداد العشرية مضاعفة الدقة” (double precision floating point numbers). هذا النوع هو ما سنستعلمه أغلب الوقت وسنسلط عليه الضوء في هذا الفصل.
  2. أعداد صحيحة كبيرة (BigInt numbers) تمثِّل عددًا صحيحًا متغير الحجم، إذ قد نلجأ إليها أحيانًا لأن النوع السابق لا يمكن أن يتجاوز القيمة 2^53 أو أن تقل عن‑2^53، وسنخصص لهذا النوع فصلًا خاصًا به نظرًا للحاجة إليه في حالات خاصة.

حاليًا، لِنتوسع عن ما نعرفه عنها، وننتقل إلى الحديث عن النوع الأول، الأعداد العادية.

طرق أخرى لكتابة عدد

تخيل أننا نريد كتابة 1 بليون. الطريقة الواضحة هي:

let billion = 1000000000;

We also can use underscore _ as the separator:

let billion = 1_000_000_000;

Here the underscore _ plays the role of the “syntactic sugar”, it makes the number more readable. The JavaScript engine simply ignores _ between digits, so it’s exactly the same one billion as above.

In real life though, we try to avoid writing long sequences of zeroes. We’re too lazy for that. We’ll try to write something like "1bn" for a billion or "7.3bn" for 7 billion 300 million. The same is true for most large numbers.

In JavaScript, we can shorten a number by appending the letter "e" to it and specifying the zeroes count:

alert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000)

let billion = 1e9;  // بليون، حرفيًا: 1 وجانبه 9 أصفار

In other words, `e` multiplies the number by `1` with the given zeroes count.

```js
1e3 = 1 * 1000 // e3 means *1000
1.23e6 = 1.23 * 1000000 // e6 means *1000000

لنكتب الآن شيئَا صغيرًا جدًا. مثلًا، جزء من المليون من الثانية:

let ms = 0.000001;

كما قمنا سابقًا، يمكن استخدام "e" لتجنب كتابة الأصفار، يمكننا القول:

let ms = 1e-6; // ستة أصفار على يسار 1

إن قمنا بعد الأصفار في 0.000001، سنجد عددها 6. لذا يكون الرقم 1e-6.

بمعنى آخر، وجود رقم سالب بعد "e" يعني القسمة على 1 متبوعًا بِعدد الأصفار المعطى:

// -3 بالقسمة على 1 متبوعًا ب 3 أصفار
1e-3 = 1 / 1000 (=0.001)

// -6 بالقسمة على 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

Rounding

One of the most used operations when working with numbers is rounding.

There are several built-in functions for rounding:

Math.floor
Rounds down: 3.1 becomes 3, and -1.1 becomes -2.
Math.ceil
Rounds up: 3.1 becomes 4, and -1.1 becomes -1.
Math.round
Rounds to the nearest integer: 3.1 becomes 3, 3.6 becomes 4, the middle case: 3.5 rounds up to 4 too.
Math.trunc (not supported by Internet Explorer)
Removes anything after the decimal point without rounding: 3.1 becomes 3, -1.1 becomes -1.

Here’s the table to summarize the differences between them:

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

These functions cover all of the possible ways to deal with the decimal part of a number. But what if we’d like to round the number to n-th digit after the decimal?

For instance, we have 1.2345 and want to round it to 2 digits, getting only 1.23.

إن وضعنا نقطة واحدة فقط ‎123456.toString(36)‎ فسيكون هناك خطأ، لأن JavaScript سَتعتبر أن النقطة هي فاصلة عشرية وأن ما بعدها هو جزء عشري للعدد. فإذا وضعنا نقطة أخرى فستعرف أن الجزء العشري فارغ وتنتقل إلى الدالة.

يمكن كتابتها بهذه الطريقة أيضًا ‎(123456).toString(36)‎.

## التقريب (Rounding)
أحد الخصائص الأكثر استخدامًا عند التعامل مع الأعداد هي التقريب. يوجد العديد من الدوال المدمجة للتقريب:

    alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
    ```

يختصر الجدول في الأسفل الاختلافات بين هذه التوابع:

|      | `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)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) العدد المستدعى معه إلى الخانة `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](https://en.wikipedia.org/wiki/IEEE_754-2008_revision)، لذا يوجد 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` بالضبط.

```smart header="Not only 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 يوزان القيم كما === لكنه أكثر موثوقية لسببين:

  1. أنه يعمل مع NaN: أي Object.is(NaN, NaN) === true وهذا أمر جيد.
  2. القيمتان 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, the first symbol stops the process
المُعامِل الثاني للتابع parseInt(str, radix)
alert(parseInt('0xff', 16)); // 255
alert(parseInt('ff', 16)); // 255, و تعمل ايضا بدون 0x

alert(parseInt('2n9c', 36)); // 123456

دوال رياضية أخرى

تحتوي JavaScript على الكائن المُدمَج Math الذي يحتوي على مكتبة صغيرة بالدوال والثوابت الرياضية. إليك بعض الأمثلة:

Math.random()

Returns a random number from 0 to 1 (not including 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)

Returns n raised to the given power.

alert( Math.pow(2, 10) ); // 2 in power 10 = 1024

There are more functions and constants in Math object, including trigonometry, which you can find in the docs for the Math object.

Summary

To write numbers with many zeroes:

  • Append "e" with the zeroes count to the number. Like: 123e6 is the same as 123 with 6 zeroes 123000000.
  • A negative number after "e" causes the number to be divided by 1 with given zeroes. E.g. 123e-6 means 0.000123 (123 millionths).

For different numeral systems:

  • Can write numbers directly in hex (0x), octal (0o) and binary (0b) systems.
  • parseInt(str, base) parses the string str into an integer in numeral system with given base, 2 ≤ base ≤ 36.
  • num.toString(base) converts a number to a string in the numeral system with the given base.

الملخص

لكتابة أعداد كبيرة:

  • أضِف "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 عندما تحتاج ذلك، هذه المكتبة صغيرة جدًا، لكنها تغطي الاحتياجات الأساسية.

مهمه

انشِئ سكربت يتيح للمستخدم ادخال رقمين ثم أعرض مجموعهما.

قم بتشغيل العرض التوضيحي

let a = +prompt("The first number?", "");
let b = +prompt("The second number?", "");

alert( a + b );

لاحظ عامل الجمع الأحادي + قبل prompt. يحوِّل القيم إلى أعداد. وإلا فإن a و b ستكون نصوصًا وسيكون مجموعهما بدمجهما: "1" + "2" = "12".

تُدَوِّر كلًا من 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(مقرب) -> 6.4

أنشِئ الدالة readNumber والتي تطلب من الزائر إدخال عدد حتى يقوم بإدخال قيمة عددية صحيحة. يجب أن تكون القيمة المُرجَعة عددًا.

يمكن للزائر إيقاف العملية بإدخال سطر فارغ أو الضغط على “CANCEL”. يجب أن تُرجِع الدالة null في هذه الحالة.

قم بتشغيل العرض التوضيحي

افتح sandbox بالإختبارات.

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()}`);

The solution is a little bit more intricate that it could be because we need to handle null/empty lines.

So we actually accept the input until it is a “regular number”. Both null (cancel) and empty line also fit that condition, because in numeric form they are 0.

After we stopped, we need to treat null and empty line specially (return null), because converting them to a number would return 0.

افتح الحل الإختبارات في sandbox.

الحلقة التالية غير منتهية، ولا تتوقف أبدًا. لماذا؟

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. الخلاصة، تجنب التحقق من المساواة عند التعامل مع الأجزاء العشرية.

تُنشِئ الدالة 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. يمكن القيام بذلك في مرحلتين:

  1. إذا ضربنا قيمة عشوائية من 0…1 في max-min. فإن فترة القيم الممكنة تزيد 0..1 إلى 0..max-min.
  2. إذا أضفنا 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) );

أنشِئ دالة randomInteger(min, max)‎ تقوم بتوليد قيمة صحيحة عشوائية من min إلى max بما في ذلك min و max.

يجب أن يظهر كل رقم من الفترة min..max بفرص متساوية. مثال على طريقة العمل:

Examples of its work:

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

لدى جميع الفترات الطول ذاته مما يجعل التوزيع النهائي موحدًا.

خريطة الدورة التعليمية