在 C# 中为输出格式化双打
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1421520/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Formatting doubles for output in C#
提问by Pete Kirkham
Running a quick experiment related to Is double Multiplication Broken in .NET?and reading a couple of articles on C# string formatting, I thought that this:
运行与.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));
}
Would be the C# equivalent of this C code:
将是此 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 );
}
However the C# produces the output:
但是 C# 产生输出:
6.9
6.90000000000000000000
+ 0.00000000000000088818
= 6.90000000000000000000
despite i showing up equal to the value 6.89999999999999946709 (rather than 6.9) in the debugger.
尽管我在调试器中显示的值等于 6.89999999999999946709(而不是 6.9)。
compared with C which shows the precision requested by the format:
与显示格式要求的精度的 C 相比:
6.900000
6.89999999999999946709
+ 0.00000000000000088818
= 6.90000000000000035527
What's going on?
这是怎么回事?
( Microsoft .NET Framework Version 3.51 SP1 / Visual Studio C# 2008 Express Edition )
(Microsoft .NET Framework 版本 3.51 SP1 / Visual Studio C# 2008 速成版)
I have a background in numerical computing and experience implementing interval arithmetic - a technique for estimating errors due to the limits of precision in complicated numerical systems - on various platforms. To get the bounty, don't try and explain about the storage precision - in this case it's a difference of one ULP of a 64 bit double.
我有数值计算的背景和在各种平台上实施区间算术的经验 - 一种估计由于复杂数值系统精度限制而导致的误差的技术。为了获得奖励,不要试图解释存储精度 - 在这种情况下,它是 64 位双精度的一个 ULP 的差异。
To get the bounty, I want to know how (or whether) .Net can format a double to the requested precision as visible in the C code.
为了获得奖励,我想知道 .Net 如何(或是否)将 double 格式设置为在 C 代码中可见的所需精度。
采纳答案by LukeH
The problem is that .NET will always round a double
to 15 significant decimal digits beforeapplying your formatting, regardless of the precision requested by your format and regardless of the exact decimal value of the binary number.
问题是 .NET在应用格式之前总是将 a 舍入double
到 15 个有效的十进制数字,而不管格式要求的精度如何,也不管二进制数的确切十进制值如何。
I'd guess that the Visual Studio debugger has its own format/display routines that directly access the internal binary number, hence the discrepancies between your C# code, your C code and the debugger.
我猜想 Visual Studio 调试器有自己的格式/显示例程,可以直接访问内部二进制数,因此 C# 代码、C 代码和调试器之间存在差异。
There's nothing built-in that will allow you to access the exact decimal value of a double
, or to enable you to format a double
to a specific number of decimal places, but you could do this yourself by picking apart the internal binary number and rebuilding it as a string representation of the decimal value.
没有任何内置功能可以让您访问 a 的确切十进制值double
,或者使您能够double
将 a格式化为特定数量的小数位,但是您可以通过选择内部二进制数并将其重建为自己来完成此操作十进制值的字符串表示形式。
Alternatively, you could use Jon Skeet's DoubleConverter
class(linked to from his "Binary floating point and .NET" article). This has a ToExactString
method which returns the exact decimal value of a double
. You could easily modify this to enable rounding of the output to a specific precision.
或者,您可以使用 Jon Skeet 的DoubleConverter
课程(链接到他的“二进制浮点和 .NET”文章)。这有一个ToExactString
方法可以返回 a 的精确十进制值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
回答by Timothy Walters
Take a look at this MSDN reference. In the notes it states that the numbers are rounded to the number of decimal places requested.
看看这个MSDN 参考。在注释中,它指出数字四舍五入到要求的小数位数。
If instead you use "{0:R}" it will produce what's referred to as a "round-trip" value, take a look at this MSDN referencefor more info, here's my code and the output:
相反,如果您使用“{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);
output
输出
6.8999999999999995
+ 0.00000000000000088818
= 6.90000000000000000000
回答by ceciliaSHARP
Use
用
Console.WriteLine(String.Format(" {0:G17}", i));
That will give you all the 17 digits it have. By default, a Double value contains 15 decimal digits of precision, although a maximum of 17 digits is maintained internally. {0:R} will not always give you 17 digits, it will give 15 if the number can be represented with that precision.
这将为您提供它所拥有的所有 17 位数字。默认情况下,Double 值包含 15 位十进制精度,但内部最多保留 17 位。{0:R} 不会总是给你 17 位数字,如果数字可以用那个精度表示,它会给出 15 位。
which returns 15 digits if the number can be represented with that precision or 17 digits if the number can only be represented with maximum precision. There isn't any thing you can to do to make the the double return more digits that is the way it's implemented. If you don't like it do a new double class yourself...
如果数字可以以该精度表示,则返回 15 位数字,如果数字只能以最大精度表示,则返回 17 位数字。您无法做任何事情来使双返回更多的数字,这就是它的实现方式。如果你不喜欢它自己做一个新的双班...
.NET's double cant store any more digits than 17 so you cant see 6.89999999999999946709 in the debugger you would see 6.8999999999999995. Please provide an image to prove us wrong.
.NET 的 double 不能存储比 17 多的数字,因此您在调试器中看不到 6.89999999999999946709,您会看到 6.8999999999999995。请提供图片以证明我们是错误的。
回答by Marcel Gosselin
The answer to this is simple and can be found on MSDN
答案很简单,可以在MSDN上找到
Remember that a floating-point number can only approximate a decimal number, and that the precision of a floating-point number determines how accurately that number approximates a decimal number. By default, a Double value contains 15 decimal digits of precision, although a maximum of 17 digits is maintained internally.
请记住,浮点数只能近似于十进制数,并且浮点数的精度决定了该数近似于十进制数的准确程度。默认情况下,Double 值包含15 位精度的十进制数字,但内部最多保留 17 位数字。
In your example, the value of i is 6.89999999999999946709 which has the number 9 for all positions between the 3rd and the 16th digit (remember to count the integer part in the digits). When converting to string, the framework rounds the number to the 15th digit.
在您的示例中,i 的值是 6.89999999999999946709,它的第 3 位和第 16 位数字之间的所有位置的数字都是 9(请记住计算数字中的整数部分)。转换为字符串时,框架会将数字四舍五入到第 15 位。
i = 6.89999999999999 946709
digit = 111111 111122
1 23456789012345 678901
回答by andyp
i tried to reproduce your findings, but when I watched 'i' in the debugger it showed up as '6.8999999999999995' not as '6.89999999999999946709' as you wrote in the question. Can you provide steps to reproduce what you saw?
我试图重现您的发现,但是当我在调试器中查看“i”时,它显示为“6.8999999999999995”,而不是您在问题中所写的“6.89999999999999946709”。你能提供步骤来重现你所看到的吗?
To see what the debugger shows you, you can use a DoubleConverter as in the following line of code:
要查看调试器向您显示的内容,您可以使用 DoubleConverter,如以下代码行所示:
Console.WriteLine(TypeDescriptor.GetConverter(i).ConvertTo(i, typeof(string)));
Hope this helps!
希望这可以帮助!
Edit: I guess I'm more tired than I thought, of course this is the same as formatting to the roundtrip value (as mentioned before).
编辑:我想我比我想象的更累,当然这与格式化为往返值相同(如前所述)。
回答by leppie
The answer is yes, double printing is broken in .NET, they are printing trailing garbage digits.
答案是肯定的,在 .NET 中重复打印被破坏,它们打印尾随垃圾数字。
You can read how to implement it correctly here.
您可以在此处阅读如何正确实施它。
I have had to do the same for IronScheme.
我不得不为 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
Note: Both C and C# has correct value, just broken printing.
注意:C 和 C# 都有正确的值,只是打印损坏。
Update: I am still looking for the mailing list conversation I had that lead up to this discovery.
更新:我仍在寻找导致此发现的邮件列表对话。
回答by Abel
Though this question is meanwhile closed, I believe it is worth mentioning how this atrocity came into existence. In a way, you may blame the C# spec, which states that a double must have a precision of 15 or 16 digits (the result of IEEE-754). A bit further on (section 4.1.6) it's stated that implementations are allowed to use higherprecision. Mind you: higher, not lower. They are even allowed to deviate from IEEE-754: expressions of the type x * y / z
where x * y
would yield +/-INF
but would be in a valid range after dividing, do not have to result in an error. This feature makes it easier for compilers to use higher precision in architectures where that'd yield better performance.
虽然这个问题同时已经结束,但我认为值得一提的是这种暴行是如何产生的。在某种程度上,您可能会责怪 C# 规范,其中规定 double 必须具有 15 或 16 位的精度(IEEE-754 的结果)。进一步(第 4.1.6 节)声明允许实现使用更高的精度。请注意:更高,而不是更低。他们甚至允许从IEEE-754偏离:类型的表达式x * y / z
,其中x * y
将产生+/-INF
,但将是一个有效的范围分割后,不必导致错误。此功能使编译器更容易在架构中使用更高的精度,从而产生更好的性能。
But I promised a "reason". Here's a quote (you requested a resource in one of your recent comments) from the Shared Source CLI, in clr/src/vm/comnumber.cpp
:
但我答应了一个“理由”。这是来自Shared Source CLI的引用(您在最近的评论中请求了资源),在clr/src/vm/comnumber.cpp
:
"In order to give numbers that are both friendly to display and round-trippable, we parse the number using 15 digits and then determine if it round trips to the same value. If it does, we convert that NUMBER to a string, otherwise we reparse using 17 digits and display that."
“为了给出既易于显示又可往返的数字,我们使用 15 位数字解析数字,然后确定它是否往返于相同的值。如果是,我们将该数字转换为字符串,否则我们使用 17 位数字重新解析并显示出来。”
In other words: MS's CLI Development Team decided to be both round-trippable and show pretty values that aren't such a pain to read. Good or bad? I'd wish for an opt-in or opt-out.
换句话说:MS 的 CLI 开发团队决定既可往返又展示漂亮的值,阅读起来不会那么痛苦。是好是坏?我希望选择加入或选择退出。
The trick it does to find out this round-trippability of any given number? Conversion to a generic NUMBER structure (which has separate fields for the properties of a double) and back, and then compare whether the result is different. If it is different, the exact value is used (as in your middle value with 6.9 - i
) if it is the same, the "pretty value" is used.
找出任何给定数字的这种往返能力的技巧是什么?转换为通用 NUMBER 结构(它具有用于 double 属性的单独字段)并返回,然后比较结果是否不同。如果不同,则使用精确值(如您的中间值6.9 - i
),如果相同,则使用“漂亮值”。
As you already remarked in a comment to Andyp, 6.90...00
is bitwise equal to 6.89...9467
. And now you know why 0.0...8818
is used: it is bitwise different from 0.0
.
正如您在对 Andyp 的评论中所说的那样,6.90...00
按位等于6.89...9467
. 现在您知道为什么0.0...8818
使用了:它与0.0
.
This 15 digits barrieris hard-coded and can only be changed by recompiling the CLI, by using Mono or by calling Microsoft and convincing them to add an option to print full "precision" (it is not really precision, but by the lack of a better word). It's probably easier to just calculate the 52 bits precision yourself or use the library mentioned earlier.
这个15 位数字屏障是硬编码的,只能通过重新编译 CLI、使用 Mono 或调用 Microsoft 并说服他们添加打印完整“精度”的选项来更改(这不是真正的精度,但由于缺少一个更好的词)。自己计算 52 位精度或使用前面提到的库可能更容易。
EDIT: if you like to experiment yourself with IEE-754 floating points, consider this online tool, which shows you all relevant parts of a floating point.
编辑:如果您喜欢尝试使用 IEE-754 浮点数,请考虑使用此在线工具,它会显示浮点数的所有相关部分。
回答by Abel
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);
回答by Suneesh
I found this quick fix.
我找到了这个快速修复。
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 );