سؤال

إجراء تجربة سريعة تتعلق هل الضرب المزدوج مكسور في .NET؟ وقراءة بعض المقالات حول تنسيق سلسلة C#، اعتقدت أن هذا:

{
    double i = 10 * 0.69;
    Console.WriteLine(i);
    Console.WriteLine(String.Format("  {0:F20}", i));
    Console.WriteLine(String.Format("+ {0:F20}", 6.9 - i));
    Console.WriteLine(String.Format("= {0:F20}", 6.9));
}

سيكون المعادل C# لرمز C هذا:

{
    double i = 10 * 0.69;

    printf ( "%f\n", i );
    printf ( "  %.20f\n", i );
    printf ( "+ %.20f\n", 6.9 - i );
    printf ( "= %.20f\n", 6.9 );
}

ومع ذلك فإن C# ينتج الإخراج:

6.9
  6.90000000000000000000
+ 0.00000000000000088818
= 6.90000000000000000000

على الرغم من أنني ظهرت مساوية للقيمة 6.8999999999999946709 (بدلاً من 6.9) في مصحح الأخطاء.

مقارنة بـ C الذي يوضح الدقة المطلوبة بالتنسيق:

6.900000                          
  6.89999999999999946709          
+ 0.00000000000000088818          
= 6.90000000000000035527          

ماذا يحدث هنا؟

(Microsoft.NET Framework الإصدار 3.51 SP1 / Visual Studio C# 2008 Express Edition)


لدي خلفية في الحوسبة الرقمية وخبرة في تنفيذ الحساب الفاصل - وهي تقنية لتقدير الأخطاء بسبب حدود الدقة في الأنظمة العددية المعقدة - على منصات مختلفة.للحصول على المكافأة، لا تحاول أن تشرح دقة التخزين - في هذه الحالة يكون الفرق بمقدار ULP واحد مضاعف 64 بت.

للحصول على المكافأة، أريد أن أعرف كيف (أو ما إذا كان) .Net يمكنه تنسيق مضاعفة بالدقة المطلوبة كما هو واضح في كود C.

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

المحلول

والمشكلة هي أن صافي سوف أكون دائما جولة double إلى 15 الأرقام العشرية كبيرة <م> قبل تطبيق التنسيق الخاص بك، بغض النظر عن الدقة التي شكل طلبك وبغض النظر عن القيمة العشرية بالضبط من الرقم الثنائي .

وكنت أعتقد أن المصحح Visual Studio لديها قناعاتها الروتينية شكلها / العرض الذي الوصول مباشرة إلى الرقم الثنائي الداخلي، وبالتالي فإن التناقضات بين الخاص بك C # رمز، رمز C والمصحح.

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

وبدلا من ذلك، يمكنك استخدام جون السكيت في الطبقة DoubleConverter (وربطها من له <لأ href = "http://csharpindepth.com/Articles/General/FloatingPoint.aspx" يختلط = "noreferrer"> "نقطة عائمة الثنائية و. NET" المقالة ). وهذا له طريقة ToExactString التي ترجع قيمة عشرية بالضبط من double. هل يمكن بسهولة تعديل هذا لتمكين التقريب من الإخراج إلى دقة محددة.

double i = 10 * 0.69;
Console.WriteLine(DoubleConverter.ToExactString(i));
Console.WriteLine(DoubleConverter.ToExactString(6.9 - i));
Console.WriteLine(DoubleConverter.ToExactString(6.9));

// 6.89999999999999946709294817992486059665679931640625
// 0.00000000000000088817841970012523233890533447265625
// 6.9000000000000003552713678800500929355621337890625

نصائح أخرى

Digits after decimal point
// just two decimal places
String.Format("{0:0.00}", 123.4567);      // "123.46"
String.Format("{0:0.00}", 123.4);         // "123.40"
String.Format("{0:0.00}", 123.0);         // "123.00"

// max. two decimal places
String.Format("{0:0.##}", 123.4567);      // "123.46"
String.Format("{0:0.##}", 123.4);         // "123.4"
String.Format("{0:0.##}", 123.0);         // "123"
// at least two digits before decimal point
String.Format("{0:00.0}", 123.4567);      // "123.5"
String.Format("{0:00.0}", 23.4567);       // "23.5"
String.Format("{0:00.0}", 3.4567);        // "03.5"
String.Format("{0:00.0}", -3.4567);       // "-03.5"

Thousands separator
String.Format("{0:0,0.0}", 12345.67);     // "12,345.7"
String.Format("{0:0,0}", 12345.67);       // "12,346"

Zero
Following code shows how can be formatted a zero (of double type).

String.Format("{0:0.0}", 0.0);            // "0.0"
String.Format("{0:0.#}", 0.0);            // "0"
String.Format("{0:#.0}", 0.0);            // ".0"
String.Format("{0:#.#}", 0.0);            // ""

Align numbers with spaces
String.Format("{0,10:0.0}", 123.4567);    // "     123.5"
String.Format("{0,-10:0.0}", 123.4567);   // "123.5     "
String.Format("{0,10:0.0}", -123.4567);   // "    -123.5"
String.Format("{0,-10:0.0}", -123.4567);  // "-123.5    "

Custom formatting for negative numbers and zero
String.Format("{0:0.00;minus 0.00;zero}", 123.4567);   // "123.46"
String.Format("{0:0.00;minus 0.00;zero}", -123.4567);  // "minus 123.46"
String.Format("{0:0.00;minus 0.00;zero}", 0.0);        // "zero"

Some funny examples
String.Format("{0:my number is 0.0}", 12.3);   // "my number is 12.3"
String.Format("{0:0aaa.bbb0}", 12.3);

ونلقي نظرة على هذه MSDN إشارة . في المذكرات أنها تنص على أن أرقام يتم تقريب إلى عدد المنازل العشرية المطلوب.

إذا بدلا من ذلك يمكنك استخدام "{0: R}" انها ستنتج ما يشار إلى قيمة "ذهابا وإيابا"، نلقي نظرة على هذا <وأ href = "http://msdn.microsoft.com/en -US / مكتبة / dwhawy9k.aspx # RFormatString "يختلط =" noreferrer "> MSDN إشارة للحصول على مزيد من المعلومات، وهنا لي رمز وإخراج:

double d = 10 * 0.69;
Console.WriteLine("  {0:R}", d);
Console.WriteLine("+ {0:F20}", 6.9 - d);
Console.WriteLine("= {0:F20}", 6.9);

الانتاج

  6.8999999999999995
+ 0.00000000000000088818
= 6.90000000000000000000

ورغم أن هذا السؤال مغلقة في الوقت نفسه، وأعتقد أنه من الجدير بالذكر كيف جاءت هذه الجريمة البشعة إلى حيز الوجود. بطريقة ما، قد نلوم C # المواصفات، التي تنص على أن ضعف يجب أن يكون بدقة 15 أو 16 أرقام (نتيجة IEEE-754). وأبعد من ذلك قليلا على (القسم 4.1.6) انها ذكر أن يسمح للتطبيقات لاستخدام <م> أعلى الدقة. فتذكروا: <م> أعلى ، وليس أقل من ذلك. حتى يسمح لهم الخروج من IEEE-754: تعبيرا عن x * y / z نوع حيث x * y ستسفر +/-INF ولكن سيكون في نطاق صالح بعد تقسيم، لم يكن لديك لينتج خطأ. هذه الميزة تجعله أسهل للمعدين لاستخدام أعلى دقة في الهندسة المعمارية حيث يهمني أن تسفر عن أداء أفضل.

ولكن وعدت "سبب". وهنا أقتبس (التي طلبتها مورد في واحدة من تعليقاتكم الأخيرة) من في المشتركة المصدر CLI ، في clr/src/vm/comnumber.cpp:

<اقتباس فقرة>   

"من أجل إعطاء الأرقام التي هي على حد سواء   ودية لعرض و   جولة trippable، نحن تحليل عدد   باستخدام 15 رقما ومن ثم تحديد ما إذا كان   فإنه رحلات ذهابا وإيابا إلى نفس القيمة. إذا   فعلت ذلك، نقوم بتحويل هذا العدد ل   سلسلة، وإلا فإننا إعادة التوزيع باستخدام 17   أرقام وعرض ذلك ".

وبعبارة أخرى: قرر فريق التطوير CLI MS لتكون قيم جميلة على حد سواء على مدار trippable وتبين أن ليست مثل هذا الألم للقراءة. جيد أو سيء؟ كنت أتمنى لفي الأراضي الفلسطينية المحتلة أو الانسحاب.

والحيلة هو الحال في معرفة هذا trippability جولة من أي عدد معين؟ تحويل إلى هيكل رقم عام (التي لديها حقول منفصلة عن خصائص مزدوج) والظهر، ثم قارن ما إذا كانت النتيجة مختلفة. إذا كان مختلفا، يتم استخدام القيمة الدقيقة (كما هو الحال في القيمة المتوسطة مع 6.9 - i) إذا كان هو نفسه، يتم استخدام "قيمة جميلة".

وكما لاحظ بالفعل في تعليق على Andyp، 6.90...00 هو أحادي المعامل يساوي 6.89...9467. والآن تعلمون لماذا يستخدم 0.0...8818: هو أحادي المعامل مختلفة من 0.0

وهذا <م> 15 رقم حاجز هو الثابت تلوينها ويمكن تغييرها إلا من خلال اعادة تجميع CLI، باستخدام مونو أو عن طريق الاتصال مايكروسوفت وإقناعهم لإضافة خيار لطباعة الكاملة "الدقة" (هو ليست في الحقيقة الدقة، ولكن بسبب عدم وجود كلمة أفضل). من المحتمل أن يكون من الأسهل لمجرد حساب 52 بت الدقة نفسك أو استخدام المكتبة المذكورة سابقا.

وتحرير: إذا كنت ترغب في تجربة نفسك مع نقاط العائمة IEE-754، النظر هذه الأداة على الانترنت ، الذي يظهر لك جميع الجهات ذات الصلة من نقطة عائمة.

استخدم

Console.WriteLine(String.Format("  {0:G17}", i));

وهذا سوف أعطيك كل 17 رقما لديها. افتراضيا، قيمة مزدوج يحتوي على 15 أرقام عشرية من الدقة، على الرغم من أن يتم الاحتفاظ بحد أقصى 17 رقما داخليا. {0: R}. لن دائما تعطيك 17 أرقام، وسوف تعطي 15 إذا كان الرقم يمكن أن تكون ممثلة مع أن الدقة

والتي ترجع 15 رقم إذا كان الرقم يمكن أن تكون ممثلة مع أن الدقة أو 17 رقما إذا كان الرقم لا يمكن إلا أن تكون ممثلة بأقصى قدر من الدقة. ليس هناك أي شيء يمكنك القيام به لجعل عودة مزدوجة المزيد من الأرقام التي هي الطريقة انها تنفذ. إذا كنت لا تحب أن تفعل فئة مزدوجة جديدة نفسك ...

ومخزن غير قادر مزدوج. NET في أي الأرقام أكثر من 17 لذلك كنت غير قادر على رؤية +6.89999999999999946709 في المصحح كنت انظر +6.8999999999999995. يرجى تقديم صورة للخطأ يثبت لنا.

الجواب على هذا بسيط ويمكن العثور عليه في MSDN

تذكر أن رقم الفاصلة العائمة يمكنه فقط تقريب الرقم العشري، وأن دقة رقم الفاصلة العائمة تحدد مدى دقة هذا الرقم في تقريب الرقم العشري.بشكل افتراضي، تحتوي القيمة المزدوجة على 15 رقمًا عشريًا من الدقة, ، على الرغم من الاحتفاظ بحد أقصى 17 رقمًا داخليًا.

في المثال الخاص بك، قيمة i هي 6.89999999999999946709 والتي تحتوي على الرقم 9 لجميع المواضع بين الرقم الثالث والرقم السادس عشر (تذكر حساب الجزء الصحيح من الأرقام).عند التحويل إلى سلسلة، يقوم إطار العمل بتقريب الرقم إلى الرقم الخامس عشر.

i     = 6.89999999999999 946709
digit =           111111 111122
        1 23456789012345 678901

وحاولت استنساخ النتائج الخاصة بك، ولكن عندما شاهدت 'ط' في المصحح أظهرت عنه باسم '+6.8999999999999995' ليس '6.89999999999999946709 "كما كتبت في السؤال. يمكنك توفير خطوات لإعادة إنتاج ما رأيت؟

لنرى ما يظهر المصحح لك، يمكنك استخدام DoubleConverter كما في السطر التالي من التعليمات البرمجية:

Console.WriteLine(TypeDescriptor.GetConverter(i).ConvertTo(i, typeof(string)));

وآمل أن يساعد هذا!

وتحرير: أعتقد أنا أكثر تعبت مما كنت اعتقد، وبطبيعة الحال هذا هو نفس تهيئة إلى قيمة ذهابا وإيابا (كما ذكر سابقا)

ولقد وجدت هذا حل سريع.

    double i = 10 * 0.69;
    System.Diagnostics.Debug.WriteLine(i);


    String s = String.Format("{0:F20}", i).Substring(0,20);
    System.Diagnostics.Debug.WriteLine(s + " " +s.Length );

Console.WriteLine( string.Format("رسوم الدورة هي {0:0.00}"، +cfees));

internal void DisplaycourseDetails()
        {
            Console.WriteLine("Course Code : " + cid);
            Console.WriteLine("Couse Name : " + cname);
            //Console.WriteLine("Couse Name : " + string.Format("{0:0.00}"+ cfees));
            // string s = string.Format("Course Fees is {0:0.00}", +cfees);
            // Console.WriteLine(s);
            Console.WriteLine( string.Format("Course Fees is {0:0.00}", +cfees));
        }
        static void Main(string[] args)
        {
            Course obj1 = new Course(101, "C# .net", 1100.00);
            obj1.DisplaycourseDetails();
            Course obj2 = new Course(102, "Angular", 7000.00);
            obj2.DisplaycourseDetails();
            Course obj3 = new Course(103, "MVC", 1100.00);
            obj3.DisplaycourseDetails();
            Console.ReadLine();
        }
    }
scroll top