لماذا أمر يؤثر على التقريب عند إضافة متعددة الزوجي في C#

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

سؤال

النظر في ما يلي C# كود:

double result1 = 1.0 + 1.1 + 1.2;
double result2 = 1.2 + 1.0 + 1.1;

if (result1 == result2)
{
    ...
}

result1 يجب أن يساوي دائما result2 صحيح ؟ الشيء هو, لا.result1 3.3 و result2 هو 3.3000000000000003.الفرق الوحيد هو أمر من الثوابت.

وأنا أعلم أن يضاعف يتم تنفيذها في مثل هذه الطريقة التي التقريب يمكن أن تحدث مشكلات.أنا على علم بأنني يمكن استخدام الكسور العشرية بدلا من ذلك إذا كنت بحاجة إلى الدقة المطلقة.أو التي يمكن أن تستخدم الرياضيات.الجولة() في بلدي إذا كان البيان.أنا الطالب الذي يريد أن يفهم ماذا C# compiler يقوم به.يمكن لأحد أن يقول لي ؟

تحرير:

شكرا لكل من هو حتى الآن اقترح القراءة على حساب النقطة العائمة و/أو تحدث عن المتأصلة دقة كيفية المعالجة المركزية مقابض الزوجي.ولكن أشعر أن التوجه الرئيسي سؤالي لا تزال دون إجابة.الذي هو خطأي لعدم صياغة ذلك بشكل صحيح.اسمحوا لي أن وضعه مثل هذا:

كسر رمز أعلاه ، أتوقع العمليات التالية تحدث:

double r1 = 1.1 + 1.2;
double r2 = 1.0 + r1
double r3 = 1.0 + 1.1
double r4 = 1.2 + r3

دعونا نفترض أن كل ما سبق الإضافات كان خطأ التقريب (مرقمة e1..e4).حتى r1 يحتوي على خطأ التقريب e1, r2 يتضمن أخطاء التقريب e1 + e2, r3 يحتوي على e3 و r4 يحتوي على e3 + e4.

الآن, أنا لا أعرف كيف بالضبط كيف أخطاء التقريب يحدث ولكن كنت أتوقع e1+e2 إلى المساواة في e3+e4.من الواضح أنه لا ، ولكن يبدو أن الخطأ بطريقة أو بأخرى على لي.شيء آخر هو أنه عند تشغيل التعليمات البرمجية أعلاه, أنا لا أحصل على أي أخطاء التقريب.هذا ما يجعلني اعتقد انها C# compiler أن يفعل شيئا غريبا بدلا من وحدة المعالجة المركزية.

أعرف أني أطلب الكثير و ربما أفضل إجابة أي شخص يمكن أن تعطي على درجة الدكتوراه في تصميم وحدة المعالجة المركزية ، لكني فكرت في سؤالك.

تحرير 2

تبحث في ايل من بلدي الأصلي نموذج التعليمات البرمجية ، فمن الواضح أن المترجم ليس وحدة المعالجة المركزية أن يفعل هذا:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
    .maxstack 1
    .locals init (
        [0] float64 result1,
        [1] float64 result2)
    L_0000: nop 
    L_0001: ldc.r8 3.3
    L_000a: stloc.0 
    L_000b: ldc.r8 3.3000000000000003
    L_0014: stloc.1 
    L_0015: ret 
}

المترجم هو إضافة الأرقام بالنسبة لي!

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

المحلول

كنت أتوقع e1+e2 إلى المساواة في e3+e4.

هذا ليس تماما على عكس تتوقع

 floor( 5/3 ) + floor( 2/3 + 1 )

المساواة

 floor( 5/3 + 2/3 ) + floor( 1 )

إلا أنت ضرب من قبل 2^53 قبل أن يأخذ الكلمة.

باستخدام 12 بت الدقة النقطة العائمة و الاقتطاع مع القيم الخاصة بك:

1.0            =  1.00000000000
1.1            =  1.00011001100
1.2            =  1.00110011001

1.0 + 1.1      = 10.00011001100 // extended during sum
r1 = 1.0 + 1.1 = 10.0001100110  // truncated to 12 bit
r1  + 1.2      = 11.01001100101 // extended during sum
r2 = r1  + 1.2 = 11.0100110010  // truncated to 12 bit

1.1 + 1.2      = 10.01001100110 // extended during sum
r3 = 1.1 + 1.2 = 10.0100110011  // truncated to 12 bit
r3 + 1.0       = 11.01001100110 // extended during sum
r4 = r3  + 1.0 = 11.0100110011  // truncated to 12 bit

حتى تغيير ترتيب العمليات/truncations يسبب الخطأ إلى تغيير ، r4 != r2.إذا قمت بإضافة 1.1 و 1.2 في هذا النظام ، الجزء الأخير يحمل حتى لا تضيع على الاقتطاع.إذا قمت بإضافة 1.0 إلى 1.1, الجزء الأخير من 1.1 فقدت وبالتالي فإن النتيجة ليست هي نفسها.

في واحد طلب ، التقريب (قبل اقتطاع) يزيل زائدة 1.

في آخر ترتيب التقريب يزيل زائدة 0 كل الأوقات.

واحد لا يساوي الصفر ؛ حتى الأخطاء ليست هي نفسها.

الزوجي العديد من أجزاء من الدقة و C# ربما يستخدم التقريب بدلا من الاقتطاع ، ولكن نأمل أن هذا نموذج بسيط يوضح لك أخطاء مختلفة يمكن أن يحدث مع مختلف orderings نفس القيم.

الفرق بين fp و الرياضيات هو + هي إختصار ل 'add ثم جولة' بدلا من مجرد إضافة.

نصائح أخرى

C# compiler لا تفعل أي شيء.وحدة المعالجة المركزية.

إذا كان لديك في وحدة المعالجة المركزية السجل ، ثم إضافة B, نتيجة تخزينها في السجل A+B, يقترب العائمة الدقة المستخدمة

إذا كنت ثم يضاف ج الخطأ يضيف.الخطأ هذه إضافة ليست متعدية العملية ، مما النهائي الفرق.

انظر الكلاسيكية ورقة (ما كل عالم الكمبيوتر يجب أن تعرف عن حساب الفاصلة العائمة) على هذا الموضوع.هذا النوع من الأشياء هو ما يحدث مع حساب النقطة العائمة.فإنه يأخذ في عالم الكمبيوتر أن أقول لك أن 1/3+1/3+1/3 أليس يساوي 1...

ترتيب عمليات النقطة العائمة هو المهم.لا مباشرة الإجابة على سؤالك, ولكن يجب أن تكون دائما حذرا مقارنة أرقام النقطة العائمة.أنه من المعتاد أن تشمل التسامح:

double epsilon = 0.0000001;
if (abs(result1 - result2) <= epsilon)
{
    ...
}

قد يكون هذا من الفائدة: ما كل عالم الكمبيوتر يجب أن تعرف عن حساب الفاصلة العائمة

result1 يجب أن يساوي دائما result2 أليس كذلك ؟

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

سوف تحتاج إلى قراءة بعض التحليل العددي التمهيدي.

لماذا الأخطاء ليست نفس اعتمادا على النظام ويمكن تفسير مختلف سبيل المثال.

دعنا نقول أن الأرقام أقل من 10 ، فإنه يمكن تخزين جميع الأرقام ، لذلك فإنه يمكن تخزين 1, 2, 3, وذلك على ما يصل إلى 10 ، ولكن بعد 10 ، فإنه يمكن فقط تخزين كل العدد الثاني ، الداخلية بسبب فقدان الدقة ، وبعبارة أخرى ، فإنه يمكن فقط تخزين 10, 12, 14, الخ.

الآن مع هذا المثال, سوف نرى لماذا التالية تنتج نتائج مختلفة:

1 + 1 + 1 + 10 = 12 (or 14, depending on rounding)
10 + 1 + 1 + 1 = 10

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

فعلى سبيل المثال ، 3.00000000003 + 3.00000000003 قد ينتهي 6.00000000005 (لاحظ لا 6 في النهاية) ، ولكن 3.00000000003 + 2.99999999997 قد ينتهي 6.00000000001 و مع ذلك:

step 1: 3.00000000003 + 3.00000000003 = 6.00000000005
step 2: 6.00000000005 + 2.99999999997 = 9.00000000002

ولكن تغيير النظام:

step 1: 3.00000000003 + 2.99999999997 = 6.00000000001
step 2: 6.00000000001 + 3.00000000003 = 9.00000000004

لذلك سوف المسألة.

الآن, بالطبع, كنت قد يكون محظوظا في أن الأمثلة المذكورة أعلاه توازن بعضها البعض, في أن أول البديل من قبل .xxx1 وغيرها من أسفل .xxx1 ، مما يتيح لك .xxx3 في كلا, ولكن لا يوجد ضمان.

أنت في الواقع لا تستخدم نفس القيم لأن نتائج المتوسطة مختلفة:

double result1 = 2.1 + 1.2;
double result2 = 2.2 + 1.1;

لأن الزوجي لا يمكن أن تمثل القيم العشرية بالضبط يمكنك الحصول على نتائج مختلفة.

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