هل هناك طريقة للقيام "الصحيح" بالتقريب الحسابي في .NET؟ / ج#
-
22-09-2019 - |
سؤال
أحاول أن أحصل على رقم إلى مكانه العشري الأول ، وبالنظر إلى الخيارات المتوسطة المختلفة ، يبدو أنه يعمل بشكل جيد. تنشأ مشكلة على الرغم من أن هذا الرقم يحتوي على أماكن عشرية عشرية من شأنها أن تؤثر بشكل حسابي على التقريب.
مثال:
مع 0.1
, 0.11..0.19
و 0.141..0.44
إنها تعمل:
Math.Round(0.1, 1) == 0.1
Math.Round(0.11, 1) == 0.1
Math.Round(0.14, 1) == 0.1
Math.Round(0.15, 1) == 0.2
Math.Round(0.141, 1) == 0.1
ولكن مع 0.141..0.149
يعود دائما 0.1
, ، برغم من 0.146..0.149
يجب أن تدور إلى 0.2
:
Math.Round(0.145, 1, MidpointRounding.AwayFromZero) == 0.1
Math.Round(0.146, 1, MidpointRounding.AwayFromZero) == 0.1
Math.Round(0.146, 1, MidpointRounding.ToEven) == 0.1
Math.Round(0.146M, 1, MidpointRounding.ToEven) == 0.1M
Math.Round(0.146M, 1, MidpointRounding.AwayFromZero) == 0.1M
حاولت الخروج بوظيفة تعالج هذه المشكلة ، وهي تعمل بشكل جيد لهذه الحالة ، لكنها بالطبع تفشل ببراعة إذا حاولت أن تدور حول أي 0.144449
إلى أول رقم عشري (يجب أن يكون 0.2
, ، لكن النتائج 0.1
.) (هذا لا يعمل مع Math.Round () أيضًا.)
private double "round"(double value, int digit)
{
// basically the old "add 0.5, then truncate to integer" trick
double fix = 0.5D/( Math.Pow(10D, digit+1) )*( value >= 0 ? 1D : -1D );
double fixedValue = value + fix;
// 'truncate to integer' - shift left, round, shift right
return Math.Round(fixedValue * Math.Pow(10D, digit)) / Math.Pow(10D, digit);
}
أفترض أن الحل هو تعداد جميع الأرقام ، والعثور على القيمة الأولى أكبر من 4 ثم تدور لأعلى ، أو حولها لأسفل. المشكلة 1: هذا يبدو أحمق ، المشكلة 2: ليس لدي أي فكرة عن كيفية تعداد الأرقام دون غزليون من الضربات وطرحها.
قصة قصيرة طويلة: ما هي أفضل طريقة للقيام بذلك؟
المحلول
Math.Round()
يتصرف بشكل صحيح.
عندما تقوم بإجراء تقريب من نقطة الوسط القياسية ، لا تحتاج أبدًا إلى النظر إلى ما وراء رقم واحد عشري وراء المكان الذي تتجول فيه. إذا كنت تتجول إلى أقرب العاشر ، فلن تحتاج أبدًا إلى النظر إلى ما وراء الرقم الثاني بعد النقطة العشرية.
الفكرة مع تقريب نقطة الوسط هي أن نصف الأرقام الفاصلة يجب أن يتجول ويجب أن يتجول نصفهم. لذلك بالنسبة للأرقام بين 0.1 و 0.2 ، يجب أن يدور نصفها إلى 0.1 ونصف أن يدور إلى 0.2. نقطة الوسط بين هذين الرقمين هي 0.15 ، لذلك هذا هو العتبة للتجول. 0.146 أقل من 0.15 ، لذلك يجب أن يدور إلى 0.1.
Midpoint
0.10 0.15 0.20
|----------------|----|---------------------|
0.146
<---- Rounds Down
نصائح أخرى
لا أحصل على ما تحاول إنجازه هنا. 0.149 مدور إلى مكان واحد عشري هو 0.1 ، وليس 0.2
التقريب ليست عملية تكرارية ، فأنت تدور مرة واحدة فقط.
لذلك 0.146 مدور إلى 1 رقم عشري هو 0.1.
أنت لا تفعل هذا:
0.146 --> 0.15
0.15 --> 0.2
أنت تفعل هذا فقط:
0.146 --> 0.1
خلاف ذلك ، ما يلي:
0.14444444444444446
سوف يدور أيضًا إلى 0.2 ، لكنه لا ، ولا ينبغي.
لا تحاول مضاعفة "أخطاء" التقريب. وهو ما تحاول القيام به.
.146 ينبغي جولة إلى .1 إذا كنت ستذهب إلى مكان عشري واحد.
من خلال تقريبه إلى .15 أولاً ، ثم مرة أخرى إلى .2 ، فأنت فقط تقدم المزيد من الخطأ الدائري ، وليس أقل.