Форматирование удваивается для вывода в C #
Запуск быстрого эксперимента, связанного с двойным умножением, разбитым в .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 # производит вывод:
- Странное поведение при приведении float в int в C #
- Как вы печатаете EXACT значение числа с плавающей запятой?
- Преобразование float в double без потери точности
- Как преобразовать строку в float?
- Преобразовать float в строку с заданной точностью и количеством десятичных цифр?
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 с пакетом обновления 1 (SP1) / Visual Studio C # 2008 Express Edition)
У меня есть опыт численных вычислений и опыт реализации интервальной арифметики – метод оценки ошибок из-за пределов точности в сложных числовых системах – на разных платформах. Чтобы получить наgradleу, не пытайтесь объяснить точность хранения – в этом случае разница в одном ULP 64-битного двойника.
Чтобы получить щедрость, я хочу знать, как (или нет) .Net может форматировать двойную требуемую точность как видимую в коде C.
- Float и двойной тип данных в Java
- Как преобразовать целое число в float в Java?
- Получение фактической ширины элемента с плавающей запятой
- Сколько двойных чисел существует между 0.0 и 1.0?
- Почему я не могу использовать значение float в качестве параметра шаблона?
- Преобразовать десятичное число в двойное?
- Android Как реализовать нижний лист из документации по материальному дизайну
- Эмуляция «double» с использованием 2 "float" s
Проблема в том, что .NET всегда будет округлять до десяти значащих десятичных цифр до применения форматирования независимо от точности, запрашиваемой вашим форматом, и независимо от точного десятичного значения двоичного числа.
Я предполагаю, что отладчик Visual Studio имеет свои собственные процедуры отображения / отображения, которые напрямую обращаются к внутреннему двоичному номеру, следовательно, расхождения между вашим кодом C #, кодом C и отладчиком.
Нет ничего встроенного, что позволит вам получить доступ к точной десятичной стоимости double
или позволить вам форматировать double
до определенного количества десятичных знаков, но вы можете сделать это сами, выделив внутреннее двоичное число и перестроить это как строковое представление десятичного значения.
Кроме того, вы можете использовать class DoubleConverter
Jon Skeet (связанный с его статьей «Binary floating point и .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}», он произведет так называемое значение «round-trip», взгляните на эту ссылку 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 цифр, а затем определяем, совершает ли он округлые значения одного значения. Если это так, мы преобразуем этот NUMBER в строку, иначе мы повторить с использованием 17 цифр и отобразить это ».
Другими словами: команда разработчиков CLI от MS решила быть как с округлением, так и с хорошими значениями, которые не так больно читать. Хорошо или плохо? Я бы хотел отказаться или отказаться.
Трюк, который он делает, чтобы узнать эту циклическую возможность любого числа? Преобразование в общую структуру NUMBER (которая имеет отдельные поля для свойств двойника) и обратно, а затем сравнить, отличается ли результат. Если оно отличается, то точное значение используется (как в вашем среднем значении с 6.9 - i
), если оно то же самое, используется «симпатичное значение».
Как вы уже отметили в комментарии к Andyp, 6.90...00
6.89...9467
равен 6.89...9467
. И теперь вы знаете, почему используется 0.0...8818
: побитно отличается от 0.0
.
Этот 15-значный барьер жестко закодирован и может быть изменен только путем перекомпиляции CLI с помощью Mono или путем вызова Microsoft и убеждения их добавить параметр для печати полной «точности» (это не очень точно, но из-за отсутствия лучшее слово). Скорее всего, проще просто вычислить точность 52 бит или использовать библиотеку, упомянутую ранее.
EDIT: если вы хотите поэкспериментировать с плавающими точками IEE-754, подумайте об этом онлайн-инструменте , в котором показаны все релевантные части плавающей запятой.
использование
Console.WriteLine(String.Format(" {0:G17}", i));
Это даст вам все 17 цифр. По умолчанию двойное значение содержит 15 десятичных цифр точности, хотя не более 17 цифр поддерживается внутри. {0: R} не всегда даст вам 17 цифр, это даст 15, если число может быть представлено с такой точностью.
который возвращает 15 цифр, если число может быть представлено с такой точностью или 17 цифр, если число может быть представлено только с максимальной точностью. Существует не что-то, что вы можете сделать, чтобы сделать двойной возврат большего количества цифр, как это было реализовано. Если вам не нравится, что он делает новый двойной class самостоятельно …
В двухмерном хранилище .NET есть больше цифр, чем 17, поэтому вы не можете увидеть 6.89999999999999946709 в отладчике, который вы увидите 6.8999999999999995. Пожалуйста, предоставьте изображение, чтобы доказать, что мы ошибаемся.
Ответ на этот вопрос прост и может быть найден на MSDN
Помните, что число с плавающей запятой может приближать только десятичное число и что точность числа с плавающей запятой определяет, насколько точно это число приближается к десятичному числу. По умолчанию двойное значение содержит 15 десятичных цифр точности , хотя не более 17 цифр поддерживается внутри.
В вашем примере значение i равно 6.89999999999999946709, которое имеет номер 9 для всех позиций между 3-й и 16-й цифрами (не забудьте подсчитать целую часть в цифрах). При преобразовании в строку фреймворк округляет число до 15-й цифры.
i = 6.89999999999999 946709 digit = 111111 111122 1 23456789012345 678901
я попытался воспроизвести ваши результаты, но когда я наблюдал «я» в отладчике, он появился как «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(); } }