Вопрос

Проведение быстрого эксперимента, связанного с Нарушено ли двойное умножение в .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,899999999999999946709 (а не 6,9).

по сравнению с C, который показывает точность, запрошенную форматом:

6.900000                          
  6.89999999999999946709          
+ 0.00000000000000088818          
= 6.90000000000000035527          

Что происходит?

(Microsoft .NET Framework версии 3.51 с пакетом обновления 1/Visual Studio C# 2008 Express Edition)


У меня есть опыт численных вычислений и опыт реализации интервальной арифметики — метода оценки ошибок из-за ограничений точности в сложных числовых системах — на различных платформах.Чтобы получить награду, не пытайтесь объяснять точность хранения — в данном случае это разница в один ULP от 64-битного двойника.

Чтобы получить награду, я хочу знать, как (и сможет ли) .Net отформатировать двойное значение с запрошенной точностью, как это видно в коде C.

Это было полезно?

Решение

Проблема в том, что .NET всегда округляет double до 15 значащих десятичных цифр перед применением вашего форматирования, независимо от точности, запрошенной вашим форматом, и независимо от точное десятичное значение двоичного числа.

Я предполагаю, что отладчик Visual Studio имеет свои собственные процедуры форматирования / отображения, которые напрямую обращаются к внутреннему двоичному числу, отсюда и расхождения между вашим кодом C #, вашим кодом C и отладчиком.

Нет ничего встроенного, что позволит вам получить доступ к точному десятичному значению double или дать вам возможность отформатировать double для определенного числа десятичных чисел местами, но вы можете сделать это самостоятельно, выделив внутреннее двоичное число и перестроив его как строковое представление десятичного значения.

В качестве альтернативы вы можете использовать DoubleConverter класс Джона Скита (связанный) из его " двоичной плавающей запятой и .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} " он будет производить то, что называется «туда-обратно»; значение, посмотрите на эту ссылку на 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 #, которая гласит, что double должен иметь точность 15 или 16 цифр (результат IEEE-754). Чуть дальше (раздел 4.1.6) указано, что реализациям разрешено использовать более высокую точность. Имейте в виду: выше , а не ниже. Им даже разрешено отклоняться от IEEE-754: выражения типа x * y / z , где x * y будет давать +/- INF но будет находиться в допустимом диапазоне после деления, не должно приводить к ошибке. Эта функция позволяет компиляторам использовать более высокую точность в архитектурах, где это приведет к повышению производительности.

Но я пообещал "причину". Вот цитата (вы запросили ресурс в одном из ваших последних комментариев) от CLI с общим исходным кодом , в clr / src / vm / comnumber.cpp :

  

" Чтобы дать числа, которые оба   дружественный для отображения и   Мы можем разобрать число   используя 15 цифр, а затем определить, если   это поездки туда и обратно к тому же значению. Если   это делает, мы конвертируем это число в   строка, в противном случае мы обрабатываем 17   цифры и отображать это. "

Другими словами: команда разработчиков CLI MS решила быть способной к быстрому обращению и показывать симпатичные значения, которые не так уж трудно читать. Хорошо или плохо? Я хотел бы подписаться или отказаться.

Хитрость, которую нужно сделать, чтобы выяснить эту возможность округления любого числа? Преобразование в общую структуру NUMBER (которая имеет отдельные поля для свойств типа double) и обратно, а затем сравните, отличается ли результат. Если оно отличается, то используется точное значение (как в среднем значении с 6.9 - i ), если оно одинаковое, то " симпатичное значение " используется.

Как вы уже отметили в комментарии к Andyp, 6.90 ... 00 поразрядно равен 6.89 ... 9467 . И теперь вы знаете, почему используется 0.0 ... 8818 : он поразрядно отличается от 0.0 .

Этот 15-значный барьер жестко запрограммирован и может быть изменен только путем перекомпиляции интерфейса командной строки, использования Mono или вызова Microsoft и убеждения их добавить опцию для печати полной " точности " (это не совсем точно, но из-за отсутствия лучшего слова). Вероятно, проще просто вычислить точность 52 бита самостоятельно или использовать библиотеку, упомянутую ранее.

РЕДАКТИРОВАТЬ: если вы хотите поэкспериментировать с плавающими точками IEE-754, подумайте этот онлайн-инструмент , который показывает все соответствующие части с плавающей запятой.

Использовать

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

Это даст вам все имеющиеся у него 17 цифр.По умолчанию значение Double содержит 15 десятичных цифр точности, хотя внутри поддерживается максимум 17 цифр.{0:R} не всегда даст вам 17 цифр, он даст 15, если число может быть представлено с такой точностью.

который возвращает 15 цифр, если число может быть представлено с такой точностью, или 17 цифр, если число может быть представлено только с максимальной точностью.Вы ничего не можете сделать, чтобы двойной возврат возвращал больше цифр, как это реализовано.Если тебе это не нравится, сделай новое двойное занятие самостоятельно...

Двойной код .NET не может хранить больше цифр, чем 17, поэтому вы не можете увидеть 6.899999999999999946709 в отладчике, вы увидите 6.8999999999999995.Пожалуйста, предоставьте изображение, чтобы доказать, что мы ошибаемся.

Я пытался воспроизвести ваши результаты, но когда я наблюдал «i» в отладчике, он показывался как «6.8999999999999995», а не «6.89999999999999946709», как вы написали в вопросе. Можете ли вы предоставить шаги для воспроизведения того, что вы видели?

Чтобы увидеть, что показывает отладчик, вы можете использовать DoubleConverter, как показано в следующей строке кода:

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

Надеюсь, это поможет!

Изменить: я думаю, что я более устал, чем я думал, конечно, это то же самое, что и форматирование до значения туда и обратно (как упоминалось ранее).

Ответ - да, двойная печать не работает в .NET, они печатают конечные цифры мусора.

Вы можете прочитать, как правильно его реализовать здесь .

Я должен был сделать то же самое для IronScheme.

> (* 10.0 0.69)
6.8999999999999995
> 6.89999999999999946709
6.8999999999999995
> (- 6.9 (* 10.0 0.69))
8.881784197001252e-16
> 6.9
6.9
> (- 6.9 8.881784197001252e-16)
6.8999999999999995

Примечание. Как в C, так и в C # указано правильное значение, только печать повреждена.

Обновление: я все еще ищу разговор в списке рассылки, который привел меня к этому открытию.

Я нашел это быстрое решение.

    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();
        }
    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top