Форматирование удваивается для вывода в C # - PullRequest
60 голосов
/ 14 сентября 2009

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

Ответы [ 10 ]

58 голосов
/ 02 ноября 2009

Проблема в том, что .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
21 голосов
/ 31 декабря 2010
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);
8 голосов
/ 03 ноября 2009

Хотя этот вопрос пока закрыт, я считаю, что стоит упомянуть, как возникло это злодеяние. В некотором смысле вы можете обвинить спецификацию C #, которая гласит, что double должен иметь точность 15 или 16 цифр (результат IEEE-754). Чуть дальше (раздел 4.1.6) указано, что реализациям разрешено использовать более высокую точность. Имейте в виду: выше , не ниже. Им даже разрешается отклоняться от IEEE-754: выражения типа x * y / z, где x * y будет давать +/-INF, но будут находиться в допустимом диапазоне после деления, не должны приводить к ошибке. Эта функция позволяет компиляторам использовать более высокую точность в архитектурах, где это приведет к повышению производительности.

Но я обещал "причину". Вот цитата (вы запросили ресурс в одном из ваших последних комментариев) из CLI Shared Source *1011*:

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

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

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

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

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

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

8 голосов
/ 14 сентября 2009

Взгляните на эту ссылку на 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
4 голосов
/ 30 октября 2009

Используйте

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

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

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

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

2 голосов
/ 01 ноября 2009

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

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

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

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

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

2 голосов
/ 31 октября 2009

Ответ на этот вопрос прост и его можно найти на MSDN

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

В вашем примере значение i равно 6.89999999999999946709, которое имеет номер 9 для всех позиций между 3-й и 16-й цифрой (не забудьте подсчитать целую часть в цифрах). При преобразовании в строку каркас округляет число до 15-й цифры.

i     = 6.89999999999999 946709
digit =           111111 111122
        1 23456789012345 678901
0 голосов
/ 15 февраля 2017

Console.WriteLine (string.Format ("Стоимость курса составляет {0: 0,00}", + сборы));

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();
        }
    }
0 голосов
/ 13 мая 2015

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

    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 );
0 голосов
/ 02 ноября 2009

Ответ - да, двойная печать не работает в .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 # имеют правильное значение, только печать повреждена.

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

...