سؤال

الكود التالي في C # (.NET 3.5 SP1) هو حلقة لا حصر لها على جهازي:

for (float i = 0; i < float.MaxValue; i++) ;

وصلت إلى الرقم 16777216.0 و 16777216.0 + 1 يتم تقييمه إلى 16777216.0. بعد في هذه المرحلة: أنا + 1!

هذا هو بعض الجنون.

أدرك أن هناك بعض عدم الدقة في كيفية تخزين أرقام النقطة العائمة. لقد قرأت تلك الأرقام الكاملة 2 ^ 24 أكثر مما لا يمكن تخزينها بشكل صحيح كعوام.

لا يزال الرمز أعلاه، يجب أن يكون صالحا في C # حتى إذا كان الرقم لا يمكن تمثيله بشكل صحيح.

لماذا لا تعمل؟

يمكنك الحصول على نفس الشيء الذي يحدثه لفترة طويلة ولكنه يستغرق وقتا طويلا جدا. 9007199254740992.0 هو الحد المزدوج.

هل كانت مفيدة؟

المحلول

الحق، وبالتالي فإن القضية هي أنه من أجل إضافة واحدة إلى تعويم، يجب أن تصبح

16777217.0

يحدث ذلك فقط أن هذا هو على حدود ل Radix ولا يمكن تمثيله تماما كعائمة. (أعلى قيمة المتاحة التالية 16777218.0)

لذلك، تقريب إلى أقرب تعويم تمثيلية

16777216.0

اسمحوا لي أن أطرح السؤال بهذه الطريقة:

لأن لديك يطفو على السطح كمية الدقة، عليك زيادة من خلال عدد أعلى وأعلى.

تعديل:

حسنا، هذا صعب بعض الشيء لشرح، ولكن حاول ذلك:

float f = float.MaxValue;
f -= 1.0f;
Debug.Assert(f == float.MaxValue);

سيعمل هذا على ما يرام، لأنه عند هذه القيمة، من أجل تمثيل اختلاف 1.0F، ستحتاج إلى أكثر من 128 بت من الدقة. تعويم لديه 32 بت فقط.

Edit2.

بواسطة حساباتي، 128 رقما ثنائيا على الأقل غير موقعة سيكون من الضروري.

log(3.40282347E+38) * log(10) / log(2) = 128

كحل لمشكلتك، يمكنك حلقة من خلال أرقام 128 بت. ومع ذلك، سيستغرق هذا ما لا يقل عن عقد من الزمان.

نصائح أخرى

تخيل على سبيل المثال أن رقم النقطة العائمة يمثله ما يصل إلى 2 أرقام عشرية مهمة، بالإضافة إلى الأسترال: في هذه الحالة، يمكنك الاعتماد من 0 إلى 99 بالضبط. ستكون التالية 100، ولكن نظرا لأنه يمكنك فقط الحصول على رقمين مهمين يتم تخزينه ك "1.0 مرات 10 إلى قوة 2". إضافة واحدة إلى ذلك سيكون ... ماذا؟

في أحسن الأحوال، سيكون 101 كنتيجة وسيطة، والتي سيتم تخزينها بالفعل (عبر خطأ في التقريب الذي يتجاهل الأرقام الثالثة الضئيلة) ك "1.0 مرات 10 إلى قوة 2" مرة أخرى.

لفهم ما يحدث خطأ سيتعين عليك قراءة معيار IEEE النقطة العائمة

دعونا نعلم بنية النقطة العائمة عدد لثانية واحدة:

يتم تقسيم رقم النقطة العائمة إلى قسمين (موافق 3، ولكن يتجاهل بت تسجيل الدخول لمدة ثانية).

لديك استعادة ومانيسا. مثل ذلك:

smmmmmmmmeeeeeee

ملاحظة: هذا ليس مناسبا لعدد البتات، لكنه يمنحك فكرة عامة عن ما يحدث.

لمعرفة الرقم الذي لدينا الحساب التالي:

mmmmmm * 2^(eeeeee) * (-1)^s

إذن ما هو float.maxvalue سيكون؟ حسنا، ستكون لديك أكبر مانتيسا محتملة وأكبر أسيون محتمل. دعونا نتظاهر أن هذا يبدو شيئا مثل:

01111111111111111

في الواقع، نحدد نان و + -NF واثنين من الاتفاقيات الأخرى، ولكنها تجاهلها للثانية لأنها ليست ذات صلة بسؤالك.

لذلك، ماذا يحدث عندما يكون لديك 9.9999*2^99 + 1ب حسنا، ليس لديك شخصيات مهمة كافية لإضافة 1. نتيجة لذلك، يتم تقريبه إلى نفس العدد. في حالة النقطة العائمة واحدة الدقة النقطة التي +1 يبدأ في الحصول على تقريبه يحدث 16777216.0

لا علاقة له بالفيضان، أو أن تكون بالقرب من القيمة القصوى. يتمتع القيمة العائمة ل 16777216.0 بتمثيل ثنائي 16777216. يمكنك بعد ذلك زيادة ذلك بنسبة 1، لذلك يجب أن يكون 16777217.0، إلا أن التمثيل الثنائي 16777217.0 هو 16777216 !!! لذلك لا يتم زيادة ذلك أو لا يفعل الزيادة على الأقل ما تتوقعه.

فيما يلي فئة كتبها Jon Skeet التي توضح هذا:

doubleconverter.cs.

جرب هذا الرمز معه:

double d1 = 16777217.0;
Console.WriteLine(DoubleConverter.ToExactString(d1));

float f1 = 16777216.0f;
Console.WriteLine(DoubleConverter.ToExactString(f1));

float f2 = 16777217.0f;
Console.WriteLine(DoubleConverter.ToExactString(f2));

لاحظ كيف التمثيل الداخلي 16777216.0 هو نفسه 16777217.0!

التكرار عندما تقترب من float.maxvalue لديه أقل من هذه القيمة. يضيف التكرار التالي إلى الأول، لكنه لا يمكن أن يحمل عددا أكبر من float.maxvalue. وبالتالي فإنه يحمل قيمة أصغر بكثير، ويبدأ الحلقة مرة أخرى.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top