在C#中输出格式化双打

运行相关的快速实验是在.NET中的双重乘法损坏? 并阅读了一些关于C#string格式的文章,我认为这是:

{ 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 

尽pipe我在debugging器中显示的值等于6.89999999999999946709(而不是6.9)。

与C相比,它显示了格式要求的精度:

 6.900000 6.89999999999999946709 + 0.00000000000000088818 = 6.90000000000000035527 

这是怎么回事?

(Microsoft .NET Framework版本3.51 SP1 / Visual Studio C#2008速成版)


我有一个数值计算的背景和经验实施区间algorithm – 一种技术,在各种平台上,由于复杂的数值系统的精度限制,估计误差。 要获得赏金,不要试图解释存储精度 – 在这种情况下,它是一个64位双精度的ULP的差异。

为了得到赏金,我想知道如何(或是否).Net可以格式化一个双精度到在C代码中可见的请求的精度。

问题在于, 应用格式之前 ,.NET总是将一个二进制数到十五个十进制数进行舍入,无论格式要求的精度如何,也不pipe二进制数的确切十进制值。

我猜想,Visual Studiodebugging器有自己的格式/显示例程,可以直接访问内部的二进制数,因此你的C#代码,你的C代码和debugging器之间的差异。

没有内置的东西可以让你访问double的精确的十进制值,或者让你将double设置为特定的小数位数,但是你可以通过select内部的二进制数字和重build它作为十进制值的string表示forms。

或者,你可以使用Jon Skeet的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 / ztypes的expression式,其中x * y将产生+/-INF但在分割之后将在有效范围内,而不必导致错误。 这个特性使得编译器可以更容易地使用更高精度的架构,从而提高性能。

但我答应了一个“理由”。 以下是clr/src/vm/comnumber.cpp 共享源CLI中的引用(您在最近的一条评论中请求了资源):

“为了给出既能显示又能循环的数字,我们用15位数字parsing数字,然后确定它是否到达相同的值,如果是这样,我们把这个NUMBER转换成一个string,否则我们重新使用17位数字并显示。

换句话说,MS的CLI开发小组决定既是可圈可点的,又显示出非常难以阅读的价值。 是好是坏? 我希望selectjoin或退出。

它find任何给定的数字的这种round-trippability的技巧? 转换为一个通用的NUMBER结构(它具有双重属性的单独字段)并返回,然后比较结果是否不同。 如果不同,则使用确切的值(如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位数字。 默认情况下,Double值包含十五个精度的十进制数字,但内部最多保留17位数字。 {0:R}并不总是给你17个数字,如果这个数字可以用这个精度来表示,它会给出15个数字。

如果该数字可以用该精度表示,则返回15个数字,如果该数字只能以最大精度表示,则返回17个数字。 没有任何事情可以做,以实现双重返回更多的数字。 如果你不喜欢自己做一个新的双人class…

.NET的double不能存储超过17的数字,所以你不能在debugging器中看到6.89999999999999946709,你会看到6.8999999999999995。 请提供一个图像来certificate我们错了。

答案很简单,可以在MSDN上find

请记住,浮点数只能接近十进制数,而浮点数的精度决定了该数字如何准确地接近十进制数。 默认情况下,Double值包含十五个精度的十进制数字 ,但内部最多保留17位数字。

在你的例子中,i的值是6.89999999999999946709,它的第3位和第16位之间的所有位置的数字都是9(记住要计算数字中的整数部分)。 转换为string时,框架将数字四舍五入到第15位。

 i = 6.89999999999999 946709 digit = 111111 111122 1 23456789012345 678901 

我试图重现你的发现,但是当我在debugging器中看到“i”时,它显示为“6.8999999999999995”而不是你在问题中写的“6.89999999999999946709”。 你能提供一些步骤来重现你所看到的吗?

要查看debugging器显示的内容,可以使用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(“Course Fees is {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(); } }