إخراج غريب بالمقارنة مع تعويم مع تعويم حرفي
-
12-09-2019 - |
سؤال
float f = 0.7;
if( f == 0.7 )
printf("equal");
else
printf("not equal");
لماذا الإخراج not equal
?
لماذا يحدث هذا؟
المحلول
يحدث هذا لأنه في بيانك
if(f == 0.7)
يتم التعامل مع 0.7 كمضاعفة. حاول 0.7F لضمان التعامل مع القيمة كطويلة:
if(f == 0.7f)
ولكن كما اقترح مايكل في التعليقات أدناه، يجب ألا تختبر أبدا للمساواة الدقيقة للقيم العائمة.
نصائح أخرى
هذه الإجابة لاستكمال تلك الموجودة: لاحظ أن 0.7 غير تم تمثيلية تماما إما تماما كعوام (أو مزدوج). إذا تم تمثيله بالضبط، فلن يكون هناك فقدان المعلومات عند التحويل إلى تعويم ثم العودة إلى المزدوج، ولن تواجه هذه المشكلة.
يمكن القول أنه يجب أن يكون هناك تحذير مترجم للثابتات العائمة الحرفية التي لا يمكن تمثيلها تماما، خاصة عندما تكون المعيار غامض للغاية فيما يتعلق بما إذا كان سيتم إجراء التقريب في وقت التشغيل في الوضع الذي تم تعيينه في ذلك الوقت أو في تجميع الوقت في وضع التقريب آخر.
جميع الأرقام غير الصحيحة التي يمكن تمثيلها بالضبط 5
كما هو آخر رقم عشري. لسوء الحظ، العكس ليس صحيحا: بعض الأرقام لديها 5
كأفضل رقم عشري آخر ولا يمكن تمثيلها بالضبط. يمكن تمثيل الأعداد الصحيحة الصغيرة جميعها بالضبط، وتنفذ تقسيم قوة 2 رقما يمكن تمثيله بآخر يمكن تمثيله، طالما أنك لا تدخل عالم الأرقام الشورمية.
بادئ ذي بدء، دعنا ننظر داخل رقم التعويم. أنا آخذ 0.1F هو 4 بايت طويلة (ثنائي 32)، في عرافة هو
3D CC CC CD.
من خلال Standart IEEE 754 لتحويله إلى عشري يجب أن نفعل مثل هذا:
في القرص المضغوط الثنائي 3D CC CC
0 01111011 1001100 11001100 11001101
هنا الرقم الأول هو توقيع قليلا. 0 يعني (-1) ^ 0 أن عددنا إيجابي.
الثانية 8 بت كبير. في ثنائي، هو 01111011 - في عشري 123. ولكن الأسترال الحقيقي هو 123-127 (دائما 127) =-4, ، يعني أننا نحتاج إلى ضرب الرقم الذي سنحصل عليه بحلول 2 ^ (- 4).
آخر 23 بايت هو الدقة الهادفة. هناك feel the first the fee 1 / (2 ^ 1) (0.5)، ثانية بنسبة 1 / (2 ^ 2) (0.25) وهلم جرا. هنا ما نحصل عليه:
نحتاج إلى إضافة جميع الأرقام (الطاقة 2) وإضافة إلى ذلك 1 (دائما 1، عن طريق ستاندارت). أنه
1,60000002384185791015625
الآن دعنا نضرب هذا الرقم بحلول 2 ^ (- 4)، إنه من الأس. نحن فقط تراجع الرقم أعلاه بنسبة 2 أربعة مرات:
0,100000001490116119384765625
اعتدت MS حاسبة
**
الآن الجزء الثاني. تحويل من عشري إلى ثنائي.
**
أنا آخذ الرقم 0.1
إنها سهولة لأنه لا يوجد جزء صحيح. قم بتسجيل الدخول الأول - إنه 0. الدقة الراهبة والأهمية التي سأحسبها الآن. يتضاعف المنطق برقم كامل (0.1 * 2 = 0.2) وإذا كان أكبر من 1 محاكاة ومتابعة.
والرقم هو .00011001100110011001100110011، تقول ستاندارت إنه يجب علينا التحول إلى اليسار قبل أن نحصل على 1. (شيء). كيف ترى نحن بحاجة إلى 4 نوبات، من هذا الرقم حساب الأسهم(127-4=123). والأهمية الدقة الآن
10011001100110011001100(وهناك ضائعة بت).
الآن العدد كله. تسجيل بت 0 الأسهم هو 123 (01111011) وتحمل الدقة هو 10011001100110011001100 وكامل هو
00111101110011001100110011001100 دعنا نقارنه مع أولئك الذين لدينا من الفصل السابق
00111101110011001100110011001101
كما ترى القليل قليلا ليست متساوية. ذلك لأنني اقتطاع الرقم. يعرف وحدة المعالجة المركزية والمترجم أن هذا شيء بعد الدقة الرفيعة لا يمكن أن يحمل وتوضع فقط بت الأخير إلى 1.
المشكلة التي تواجهها هي أن المشكلات التي تواجهها، كما لاحظ المعلقون الآخرون، أنه غير آمن عموما لاختبار التعادل الدقيق بين العوامات، كأخطاء التهيئة، أو أخطاء التقريب في الحسابات يمكن أن يعرض اختلافات طفيفة تسبب == المشغل لإرجاع FALSE.
ممارسة أفضل هي أن تفعل شيئا مثل
float f = 0.7;
if( fabs(f - 0.7) < FLT_EPSILON )
printf("equal");
else
printf("not equal");
على افتراض أنه تم تعريف FLT_EPSILON كقيمة تعويم صغيرة بشكل مناسب لمنصتك.
نظرا لأن أخطاء التقريب أو التهيئة من غير المرجح أن تتجاوز قيمة FLT_EPSILON، فسيمنحك هذا اختبار المكافئ الموثوق الذي تبحث عنه.
الكثير من الإجابات حول الويب يخطئ في النظر إلى الفرق الأوعي بين أرقام النقطة العائمة، وهذا هو صالح فقط للحالات الخاصة، والطريقة القوية هي النظر في الاختلاف النسبي كما في أدناه:
// Floating point comparison:
bool CheckFP32Equal(float referenceValue, float value)
{
const float fp32_epsilon = float(1E-7);
float abs_diff = std::abs(referenceValue - value);
// Both identical zero is a special case
if( referenceValue==0.0f && value == 0.0f)
return true;
float rel_diff = abs_diff / std::max(std::abs(referenceValue) , std::abs(value) );
if(rel_diff < fp32_epsilon)
return true;
else
return false;
}
تم ربط سؤال دقيق آخر بالضبط بهذا السبب في وقت متأخر من السنتين. لا أعتقد أن الإجابات المذكورة أعلاه كاملة.
int fun1 ( void )
{
float x=0.7;
if(x==0.7) return(1);
else return(0);
}
int fun2 ( void )
{
float x=1.1;
if(x==1.1) return(1);
else return(0);
}
int fun3 ( void )
{
float x=1.0;
if(x==1.0) return(1);
else return(0);
}
int fun4 ( void )
{
float x=0.0;
if(x==0.0) return(1);
else return(0);
}
int fun5 ( void )
{
float x=0.7;
if(x==0.7f) return(1);
else return(0);
}
float fun10 ( void )
{
return(0.7);
}
double fun11 ( void )
{
return(0.7);
}
float fun12 ( void )
{
return(1.0);
}
double fun13 ( void )
{
return(1.0);
}
Disassembly of section .text:
00000000 <fun1>:
0: e3a00000 mov r0, #0
4: e12fff1e bx lr
00000008 <fun2>:
8: e3a00000 mov r0, #0
c: e12fff1e bx lr
00000010 <fun3>:
10: e3a00001 mov r0, #1
14: e12fff1e bx lr
00000018 <fun4>:
18: e3a00001 mov r0, #1
1c: e12fff1e bx lr
00000020 <fun5>:
20: e3a00001 mov r0, #1
24: e12fff1e bx lr
00000028 <fun10>:
28: e59f0000 ldr r0, [pc] ; 30 <fun10+0x8>
2c: e12fff1e bx lr
30: 3f333333 svccc 0x00333333
00000034 <fun11>:
34: e28f1004 add r1, pc, #4
38: e8910003 ldm r1, {r0, r1}
3c: e12fff1e bx lr
40: 66666666 strbtvs r6, [r6], -r6, ror #12
44: 3fe66666 svccc 0x00e66666
00000048 <fun12>:
48: e3a005fe mov r0, #1065353216 ; 0x3f800000
4c: e12fff1e bx lr
00000050 <fun13>:
50: e3a00000 mov r0, #0
54: e59f1000 ldr r1, [pc] ; 5c <fun13+0xc>
58: e12fff1e bx lr
5c: 3ff00000 svccc 0x00f00000 ; IMB
لماذا ترجع Fun3 و Fun4 واحدا وليس الآخرين؟ لماذا يعمل Fun5؟
إنه يتعلق بالغة. تقول اللغة أن 0.7 مزدوجة إلا إذا كنت تستخدم بناء الجملة هذا 0.7F، فهذا هو واحد. وبالتالي
float x=0.7;
يتم تحويل 0.7 المزدوج إلى واحدة ومخزنة في X.
if(x==0.7) return(1);
تقول اللغة إن علينا الترويج لها بدقة أعلى حتى يتم تحويل الواحد في X إلى مضاعفة ومقارنة مع 0.7 المزدوج.
00000028 <fun10>:
28: e59f0000 ldr r0, [pc] ; 30 <fun10+0x8>
2c: e12fff1e bx lr
30: 3f333333 svccc 0x00333333
00000034 <fun11>:
34: e28f1004 add r1, pc, #4
38: e8910003 ldm r1, {r0, r1}
3c: e12fff1e bx lr
40: 66666666 strbtvs r6, [r6], -r6, ror #12
44: 3fe66666 svccc 0x00e66666
واحد 3F333333 مزدوج 3fe66666666666666
كما أشار أليكساندر إذا ظل هذا الإجابة IEEE 754
seeeeeeeeffffffffffffffffffff.
ومضاعف
seeeeeeeeeeeefffffffffffffffffffffffffffffffffffffffff.
مع 52 بت من الكسر بدلا من 23 هذا واحد لديه.
00111111001100110011... single
001111111110011001100110... double
0 01111110 01100110011... single
0 01111111110 01100110011... double
تماما مثل 1/3 في قاعدة 10 هو 0.3333333 ... إلى الأبد. لدينا نمط متكرر هنا 0110
01100110011001100110011 single, 23 bits
01100110011001100110011001100110.... double 52 bits.
وهنا هو الجواب.
if(x==0.7) return(1);
يحتوي X على 01100110011001100110011 كجزء كبير، عندما يتم تحويل ذلك مرة أخرى لمضاعفة الكسر
01100110011001100110011000000000....
وهو ما لا يساوي
01100110011001100110011001100110...
لكن هنا
if(x==0.7f) return(1);
لا يحدث هذا الترويج أن أنماط بت نفسها مقارنة مع بعضها البعض.
لماذا 1.0 العمل؟
00000048 <fun12>:
48: e3a005fe mov r0, #1065353216 ; 0x3f800000
4c: e12fff1e bx lr
00000050 <fun13>:
50: e3a00000 mov r0, #0
54: e59f1000 ldr r1, [pc] ; 5c <fun13+0xc>
58: e12fff1e bx lr
5c: 3ff00000 svccc 0x00f00000 ; IMB
0011111110000000...
0011111111110000000...
0 01111111 0000000...
0 01111111111 0000000...
في كلتا الحالتين الكسر هو كل الأصفار. لذلك التحويل من مزدوج إلى واحد لمضاعفة لا يوجد فقدان الدقة. يتحول من واحدة إلى ضعف مقارنة القيم بالضبط بالضبط.
أعلى إجابة صوتية وفحصها Halfdan هي الإجابة الصحيحة، هذه هي حالة من الدقة المختلطة ويجب ألا تفعل مقارنة متساوين.
لماذا لم يظهر في هذه الإجابة. 0.7 فشل 1.0 يعمل. لماذا لم تظهر 0.7 فشل. السؤال المكرر 1.1 يفشل كذلك.
تعديل
يمكن إخراج المساواة من هذه المشكلة هنا، فهو سؤال مختلف تم الرد عليه بالفعل، لكنه نفس المشكلة وأيضا لديه "ما ..." الصدمة الأولية.
int fun1 ( void )
{
float x=0.7;
if(x<0.7) return(1);
else return(0);
}
int fun2 ( void )
{
float x=0.6;
if(x<0.6) return(1);
else return(0);
}
Disassembly of section .text:
00000000 <fun1>:
0: e3a00001 mov r0, #1
4: e12fff1e bx lr
00000008 <fun2>:
8: e3a00000 mov r0, #0
c: e12fff1e bx lr
لماذا يعرض واحد أقل من والآخر لا يقل عن؟ عندما يجب أن تكون متساوية.
من أعلى نعرف قصة 0.7.
01100110011001100110011 single, 23 bits
01100110011001100110011001100110.... double 52 bits.
01100110011001100110011000000000....
اقل من.
01100110011001100110011001100110...
0.6 هو نمط متكرر مختلف 0011 بدلا من 0110.
ولكن عند تحويلها من مزدوج إلى واحدة أو بشكل عام عند تمثيلها كواحد من IEEE 754.
00110011001100110011001100110011.... double 52 bits.
00110011001100110011001 is NOT the fraction for single
00110011001100110011010 IS the fraction for single
يستخدم IEEE 754 أوضاع التقريب أو تقريب أو جولة أو جولة إلى صفر. المبرمات تميل إلى الجولة افتراضيا. إذا كنت تتذكر التقريب في المدرسة الدراسية 12345678 إذا كنت أرغب في الجولة إلى الرقم الثالث من الأعلى، فستكون 12300000 ولكن جولة إلى الرقم التالي 1235000 إذا كان الرقم بعد 5 أو أكبر ثم تقريب. 5 هو 1/2 من 10 الأساس (العشري) في ثنائي 1 هو 1/2 من القاعدة، لذلك إذا كان الرقم بعد الموقف الذي نريد جولة هو 1 ثم تقريب آخر لا. لذلك لمدة 0.7 لم ندخل، لأننا نفعل 0.6 جولة.
والآن من السهل أن نرى ذلك
00110011001100110011010
تحويلها إلى ضعف بسبب (x <0.7)
00110011001100110011010000000000....
أكبر من
00110011001100110011001100110011....
لذلك دون الحاجة إلى التحدث عن استخدام المساواة، لا تزال المشكلة لا تزال تقدم نفسها 0.7 هو مزدوج 0.7F هو واحد، يتم الترويج للعملية إلى أعلى دقة إذا كانت تختلف.
النظر في هذا:
int main()
{
float a = 0.7;
if(0.7 > a)
printf("Hi\n");
else
printf("Hello\n");
return 0;
}
إذا (0.7> أ) هنا هو متغير تعويم و 0.7
هو ثابت مزدوج. ثابت مزدوج 0.7
أكبر من متغير تعويم أ. وبالتالي فإن الحالة إذا كانت راضية وطبعها 'Hi'
مثال:
int main()
{
float a=0.7;
printf("%.10f %.10f\n",0.7, a);
return 0;
}
انتاج:
0.7000000000 0.6999999881
إذا قمت بتغيير نوع البيانات F ل مزدوج, ، سوف تطبع مساو, ، هذا بسبب الثوابت في النقطة العائمة المخزنة في مزدوج وغير العائمة في طويل, ، الدقة المزدوجة عالية وتطفو لها قيمة أقل دقة، وقيمة مزدوجة مخزنة في 64 قيمة البتة الثنائية والطفو المخزنة في 32 Bit Binary، سيكون من الواضح تماما إذا رأيت طريقة تحويل أرقام النقطة العائمة إلى التحويل الثنائي.