علة الدقة في دول مجلس التعاون الخليجي؟

StackOverflow https://stackoverflow.com/questions/1338045

  •  20-09-2019
  •  | 
  •  

سؤال

لا أستطيع إلا أن أفترض أن هذا خطأ. أول تأكيد يمر أثناء فشل الثانية:

double sum_1 =  4.0 + 6.3;
assert(sum_1 == 4.0 + 6.3);

double t1 = 4.0, t2 = 6.3;

double sum_2 =  t1 + t2;
assert(sum_2 == t1 + t2);

إن لم يكن خطأ، لماذا؟

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

المحلول

هذا شيء عض لي أيضا.

نعم، لا ينبغي أبدا مقارنة أرقام النقطة العائمة عن المساواة بسبب التقريب، وربما تعرف ذلك.

ولكن في هذه الحالة، كنت الحوسبة t1+t2, ، ثم حسابها مرة أخرى. بالتاكيد يجب أن تنتج نتيجة متطابقة؟

إليك ما يحدث على الأرجح. سأراهن أنك تقوم بتشغيل هذا على وحدة المعالجة المركزية X86، صحيحة؟ يستخدم X86 FPU 80 بت لسجلاتها الداخلية، ولكن يتم تخزين القيم في الذاكرة كضوج 64 بت.

لذا t1+t2 يتم حساب أول 80 بت من الدقة، ثم - أفترض - تخزينها في الذاكرة sum_2 مع 64 بت من الدقة - وبعض التقريب يحدث. للحصول على تأكيد، يتم تحميله مرة أخرى في سجل نقطة عائمة، و t1+t2 يتم حسابها مرة أخرى، مرة أخرى مع 80 بت من الدقة. الآن أنت تقارن sum_2, ، والتي تم تقريبها سابقا إلى قيمة نقطة عائمة 64 بت، مع t1+t2, ، التي تم حسابها بدقة أعلى (80 بت) - وهذا هو السبب في أن القيم ليست متطابقة تماما.

تعديل فلماذا تمر الاختبار الأول؟ في هذه الحالة، المحول البرمجي ربما يقيم 4.0+6.3 في تجميع الوقت وتخزينها ككمية 64 بت - كلاهما للمهمة ولأدوات. يتم مقارنة القيم مماثلة جدا، وتمرير الأكهاد.

التحرير الثاني إليك رمز التجميع الذي تم إنشاؤه للجزء الثاني من التعليمات البرمجية (دول مجلس التعاون الخليجي، x86)، مع التعليقات - يتبع إلى حد كبير السيناريو الموضح أعلاه:

// t1 = 4.0
fldl    LC3
fstpl   -16(%ebp)

// t2 = 6.3
fldl    LC4
fstpl   -24(%ebp)

// sum_2 =  t1+t2
fldl    -16(%ebp)
faddl   -24(%ebp)
fstpl   -32(%ebp)

// Compute t1+t2 again
fldl    -16(%ebp)
faddl   -24(%ebp)

// Load sum_2 from memory and compare
fldl    -32(%ebp)
fxch    %st(1)
fucompp

ملاحظة جانبية مثيرة للاهتمام: تم تجميع ذلك دون تحسين. عندما يتم تجميعها مع -O3, ، التحويل البرمجي يحسن الكل من الكود بعيدا.

نصائح أخرى

أنت تقارن أرقام النقطة العائمة. لا تفعل ذلك، تحتوي أرقام النقاط العائمة على خطأ بدقة متأصلة في بعض الحالات. بدلا من ذلك، خذ القيمة المطلقة لفرق القيمتين وتأكيدها على أن القيمة أقل من عدد قليل (EPSILON).

void CompareFloats( double d1, double d2, double epsilon )
{
    assert( abs( d1 - d2 ) < epsilon );
} 

هذا لا علاقة له بالمترجم وكل شيء يتعلق بهذه الطريقة التي يتم بها تنفيذ أرقام النقطة العائمة. هنا هو المواصفات IEEE:

http://www.eecs.berkeley.edu/~wkahan/ieeee754status/ieee754.pdf.

لقد استغرقت مشكلتك في بلدي Intel Core 2 Duo، نظرت إلى رمز التجميع. إليك ما يحدث: عندما يقيم التحويل البرمجي الخاص بك t1 + t2, ، نعم هو كذلك

load t1 into an 80-bit register
load t2 into an 80-bit register
compute the 80-bit sum

عندما يخزن في sum_2 نعم هو كذلك

round the 80-bit sum to a 64-bit number and store it

ثم == تقارن المقارنة بمبلغ 80 بت إلى مبلغ 64 بت، ويتم تختلفه، في المقام الأول لأن الجزء الكسر 0.3 لا يمكن تمثيله تماما باستخدام رقم نقطة عائمة ثنائية، لذلك تقوم بمقارنة "تكرار عشري" (تكرار فعلا ثنائية) التي تم اقتطاعها إلى أطوال مختلفة.

ما هو مهيج حقا هو أنه إذا قمت بالمترجم مع gcc -O1 أو gcc -O2, ، دول مجلس التعاون الخليجي يقوم الحساب الخطأ في تجميع الوقت، وتختفي المشكلة. ربما هذا على ما يرام وفقا للمعايير، لكنه مجرد سبب آخر لم يكن جي سيC ليس مترجمي المفضل.


PS عندما أقول ذلك == يقارن مبلغ 80 بت مع مبلغ 64 بت، بالطبع يعني حقا أنه يقارن الإصدار الموسع من مجموع 64 بت. قد تفعل جيدا للتفكير

sum_2 == t1 + t2

يحل إلى

extend(sum_2) == extend(t1) + extend(t2)

و

sum_2 = t1 + t2

يحل إلى

sum_2 = round(extend(t1) + extend(t2))

مرحبا بكم في عالم رائع من النقطة العائمة!

عند مقارنة أرقام النقطة العائمة للتقارب، فأنت عادة تريد قياس اختلافها النسبي، والذي يعرف باسم

if (abs(x) != 0 || abs(y) != 0) 
   rel_diff (x, y) = abs((x - y) / max(abs(x),abs(y))
else
   rel_diff(x,y) = max(abs(x),abs(y))

علي سبيل المثال،

rel_diff(1.12345, 1.12367)   = 0.000195787019
rel_diff(112345.0, 112367.0) = 0.000195787019
rel_diff(112345E100, 112367E100) = 0.000195787019

الفكرة هي قياس عدد الأرقام الكبيرة الرائدة في عدد المشترك؛ إذا كنت تأخذ -Log10 من 0.000195787019 تحصل على 3.70821611، وهو ما يتعلق بعدد 10 أرقام 10 أرقام كل الأمثلة مشتركة.

إذا كنت بحاجة إلى تحديد ما إذا كانت أرقام نقطة عائمة متساوية، يجب عليك فعل شيء مثل

if (rel_diff(x,y) < error_factor * machine_epsilon()) then
  print "equal\n";

حيث آلة EPSILON هي أصغر عدد يمكن الاحتفاظ بها في Mantissa من أجهزة النقاط العائمة المستخدمة. تحتوي معظم لغات الكمبيوتر على دعوة وظيفة للحصول على هذه القيمة. يجب أن يستند Error_Factor إلى عدد الأرقام المهمة التي تعتقد أنه سيتم استهلاكها بواسطة أخطاء التقريب (وغيرها) في حسابات الأرقام x و y. على سبيل المثال، إذا كنت تعرف أن X و Y كانت نتيجة حوالي 1000 ملخص ولم تعرف أي حدود على الأرقام التي يجري تلخيصها، فسأقوم بتعيين error_factor إلى حوالي 100.

حاول إضافة هذه الروابط ولكنها لم تستطع لأن هذا هو أول مشاركتي:

  • en.wikipedia.org/wiki/relative_difference.
  • en.wikipedia.org/wiki/machine_epsilon.
  • en.wikipedia.org/wiki/significanand (Mantissa)
  • en.wikipedia.org/wiki/rounding_error.

قد يكون ذلك في إحدى الحالات، وينتهي بك الأمر بمقارنة مزدوجة 64 بت إلى سجل داخلي 80 بت. قد يكون التنوير للنظر في تعليمات الجمعية في دول مجلس التعاون الخليجي في الحالتين ...

مقارنات من الأرقام الدقيقة المزدوجة غير دقيقة بطبيعتها. على سبيل المثال، يمكنك في كثير من الأحيان العثور عليها 0.0 == 0.0 عودة خاطئة. وبعد هذا يرجع إلى الطريقة التي يخزنها FPU وتتبع الأرقام.

ويكيبيديا تقول:

اختبار للمساواة مشكلة. قد تنتج تسلسلتان حسابي على حسابي رياضيا قيما مختلفة من النقاط العائمة.

ستحتاج إلى استخدام دلتا لإعطاء التسامح لمقارناتك، بدلا من القيمة الدقيقة.

يمكن أن تكون هذه "المشكلة" هذه "ثابتة عن طريق استخدام هذه الخيارات:

-MSS2 -MFPMATH = SSE

كما هو موضح في هذه الصفحة:

http://www.network-theory.co.uk/docs/gccintro/gccintro_70.html.

بمجرد أن استخدمت هذه الخيارات، مرر كلا التأكيدين.

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